Source: modules/Config.js

/**
 * @module modules/Config.js
 * @name Config
 * @copyright 2023 3Liz
 * @author DHONT René-Luc
 * @license MPL-2.0
 */

import { ValidationError } from './Errors.js';
import { deepFreeze } from './config/Tools.js';
import { MetadataConfig } from './config/Metadata.js';
import { OptionsConfig } from './config/Options.js';
import { LayersConfig } from './config/Layer.js';
import { BaseLayersConfig } from './config/BaseLayer.js';
import { LocateByLayerConfig } from './config/Locate.js';
import { AttributeLayersConfig } from './config/AttributeTable.js';
import { TooltipLayersConfig } from './config/Tooltip.js';
import { EditionLayersConfig } from './config/Edition.js';
import { TimeManagerLayersConfig } from './config/TimeManager.js';
import { FormFilterConfig } from './config/FormFilter.js';
import { ThemesConfig } from './config/Theme.js';
import { DatavizOptionsConfig, DatavizLayersConfig } from './config/Dataviz.js';
import { buildLayerTreeConfig, LayerTreeGroupConfig } from './config/LayerTree.js';
import { buildLayersOrder } from './config/LayersOrder.js';

/**
 * @typedef WfsFeatureType
 * @type {object}
 * @property {string} Name  - The layer typename.
 * @property {string} Title - The layer title.
 * @property {string} SRS   - The layer CRS code.
 * @property {number[]} LatLongBoundingBox - The layer bounding box in the layer CRS
 */

/**
 * @class
 * @name Config
 */
export class Config {

    /**
     * @param {object} cfg               - the lizmap config object
     * @param {object} wmsCapabilities   - the WMS capabilities
     * @param {object} [wfsCapabilities] - the WFS capabilities
     */
    constructor(cfg, wmsCapabilities, wfsCapabilities) {
        if (!cfg || typeof cfg !== "object") {
            throw new ValidationError('The config is not an Object! It\'s '+(typeof cfg));
        }

        if (Object.getOwnPropertyNames(cfg).length == 0) {
            throw new ValidationError('The config is empty!');
        }

        if (!wmsCapabilities || typeof wmsCapabilities !== "object") {
            throw new ValidationError('The WMS Capabilities is not an Object! It\'s '+(typeof wmsCapabilities));
        }

        if (Object.getOwnPropertyNames(wmsCapabilities).length == 0) {
            throw new ValidationError('The WMS Capabilities is empty!');
        }

        this._theConfig = null;
        this._theWmsCapabilities = null;
        this._theWfsCapabilities = null;
        this._options = null;
        this._layers = null;
        this._layerTree = null;
        this._baselayers = null;
        this._layersOrder = null;
        this._hasMetadata = true;
        this._metadata = null;
        this._hasLocateByLayer = true;
        this._locateByLayer = null;
        this._hasAttributeLayers = true;
        this._attributeLayers = null;
        this._hasTimemanagerLayers = true;
        this._timemanagerLayers = null;
        this._hasRelations = true;
        this._hasPrintTemplates = true;
        this._hasTooltipLayers = true;
        this._tooltipLayers = null;
        this._hasEditionLayers = true;
        this._editionLayers = null;
        this._hasFormFilterLayers = true;
        this._formFilterLayers = null;
        this._hasLoginFilteredLayers = true;
        this._hasThemes = true;
        this._themes = null;
        this._hasDatavizConfig = true;
        this._datavizLayers = null;
        this._datavizOptions = null;

        const theConfig = deepFreeze(cfg);

        // checking config
        const mandatoryConfigProperties = [
            'options',
            'layers',
            'datavizLayers' // needed for locale property to build plot
        ];
        for (const prop of mandatoryConfigProperties) {
            if (!theConfig.hasOwnProperty(prop)) {
                throw new ValidationError('No `' + prop + '` in the config!');
            }
        }

        const theWmsCapabilities = deepFreeze(wmsCapabilities);

        // checking WMS Capabilities
        const mandatoryWmsCapabilitiesgProperties = [
            'Capability', //needed for  layers tree
        ];
        for (const prop of mandatoryWmsCapabilitiesgProperties) {
            if (!theWmsCapabilities.hasOwnProperty(prop)) {
                throw new ValidationError('No `' + prop + '` in the WMS Capabilities!');
            }
        }
        this._theConfig = theConfig;
        this._theWmsCapabilities = theWmsCapabilities;
        if (wfsCapabilities) {
            this._theWfsCapabilities = deepFreeze(wfsCapabilities);
        }

        const optionalConfigProperties = [
            'metadata',
            'locateByLayer',
            'attributeLayers',
            'timemanagerLayers',
            'relations',
            'printTemplates',
            'tooltipLayers',
            'editionLayers',
            'formFilterLayers',
            'loginFilteredLayers',
            'themes'
        ];
        for (const prop of optionalConfigProperties) {
            if (!theConfig.hasOwnProperty(prop)
                || Object.getOwnPropertyNames(theConfig[prop]).length == 0) {
                this['_has'+prop.charAt(0).toUpperCase() + prop.slice(1)] = false;
            }
        }

        // check datavizConfig
        if ((!theConfig.datavizLayers.hasOwnProperty('layers')
            || Object.getOwnPropertyNames(theConfig.datavizLayers.layers).length == 0)
            && (!theConfig.datavizLayers.hasOwnProperty('dataviz')
            || Object.getOwnPropertyNames(theConfig.datavizLayers.dataviz).length == 0)) {
            this._hasDatavizConfig = false;
        }
    }

    /**
     * Config options
     * @type {OptionsConfig}
     */
    get options() {
        if (this._options != null) {
            return this._options;
        }
        this._options = new OptionsConfig(this._theConfig.options);
        return this._options;
    }

    /**
     * Config layers
     * @type {LayersConfig}
     */
    get layers() {
        if (this._layers != null) {
            return this._layers;
        }
        this._layers = new LayersConfig(this._theConfig.layers);
        return this._layers;
    }

    /**
     * Root tree layer group
     * @type {LayerTreeGroupConfig}
     */
    get layerTree() {
        if (this._layerTree == null) {
            this._layerTree = buildLayerTreeConfig(this._theWmsCapabilities.Capability.Layer, this.layers);
        }
        return this._layerTree;
    }

    /**
     * Config base layers
     * @type {BaseLayersConfig}
     */
    get baseLayers() {
        if (this._baselayers != null) {
            return this._baselayers;
        }
        let baseLayersCfg = {};
        if (this._theConfig.hasOwnProperty('baseLayers')) {
            baseLayersCfg = this._theConfig.baseLayers;
        }
        let baseLayerTreeItem = null;
        for (const layerTreeItem of this.layerTree.getChildren()) {
            if ( layerTreeItem.name.toLowerCase() == 'baselayers') {
                baseLayerTreeItem = layerTreeItem;
                break;
            }
        }
        let hiddenTreeItem = null;
        for (const layerTreeItem of this.layerTree.getChildren()) {
            if ( layerTreeItem.name.toLowerCase() == 'hidden') {
                hiddenTreeItem = layerTreeItem;
                break;
            }
        }
        this._baselayers = new BaseLayersConfig(baseLayersCfg, this._theConfig.options, this.layers, baseLayerTreeItem, hiddenTreeItem);
        return this._baselayers;
    }

    /**
     * Layer names displaying order like in QGIS
     *
     * The first one in this list is the top one in the map
     * The last one in this list is the bottom one in the map
     * @type {string[]}
     */
    get layersOrder() {
        if (this._layersOrder == null) {
            this._layersOrder = buildLayersOrder(this._theConfig, this.layerTree);
        }
        return [...this._layersOrder];
    }

    /**
     * The list of format for file export
     * @type {string[]}
     */
    get vectorLayerResultFormat() {
        const formats = [];
        if ( this._theWfsCapabilities == null ){
            return formats;
        }

        for (const request of this._theWfsCapabilities.Capability.Request) {
            if (request.name != 'GetFeature') {
                continue;
            }
            return request.ResultFormat
        }
        return formats;
    }

    /**
     * The list of WFS feature type
     * @type {WfsFeatureType[]}
     */
    get vectorLayerFeatureTypeList() {
        const featureTypes = [];
        if ( this._theWfsCapabilities == null ){
            return featureTypes;
        }
        if ( this._theWfsCapabilities.FeatureTypeList &&
            this._theWfsCapabilities.FeatureTypeList.FeatureType
        ) {
            return this._theWfsCapabilities.FeatureTypeList.FeatureType;
        }
        return featureTypes;
    }

    /**
     * Config metadata
     * @type {MetadataConfig}
     */
    get metadata() {
        if (this._metadata != null) {
            return this._metadata;
        }
        if (this._hasMetadata) {
            this._metadata = new MetadataConfig(this._theConfig.metadata);
        } else {
            this._metadata = new MetadataConfig();
        }
        return this._metadata;
    }

    /**
     * Locate by layer config is defined
     * @type {boolean}
     */
    get hasLocateByLayer() {
        return this._hasLocateByLayer;
    }

    /**
     * Config locateByLayer
     * @type {LocateByLayerConfig|null}
     */
    get locateByLayer() {
        if (this._hasLocateByLayer) {
            if (this._locateByLayer != null) {
                return this._locateByLayer;
            }
            this._locateByLayer = new LocateByLayerConfig(this._theConfig.locateByLayer);
        }
        return this._locateByLayer;
    }

    /**
     * Attribute layers config is defined
     * @type {boolean}
     */
    get hasAttributeLayers() {
        return this._hasAttributeLayers;
    }

    /**
     * Config attribueLayers
     * @type {AttributeLayersConfig|null}
     */
    get attributeLayers() {
        if (this._hasAttributeLayers) {
            if (this._attributeLayers != null) {
                return this._attributeLayers;
            }
            this._attributeLayers = new AttributeLayersConfig(this._theConfig.attributeLayers);
        }
        return this._attributeLayers;
    }

    /**
     * Time manager config is defined
     * @type {boolean}
     */
    get hasTimemanagerLayers() {
        return this._hasTimemanagerLayers;
    }

    /**
     * Config timemanagerLayers
     * @type {AttributeLayersConfig|null}
     */
    get timemanagerLayers() {
        if (this._hasTimemanagerLayers) {
            if (this._timemanagerLayers != null) {
                return this._timemanagerLayers;
            }
            this._timemanagerLayers = new TimeManagerLayersConfig(this._theConfig.timemanagerLayers);
        }
        return this._timemanagerLayers;
    }

    /**
     * Relations config is defined
     * @type {boolean}
     */
    get hasRelations() {
        return this._hasRelations;
    }

    /**
     * Print templates config is defined
     * @type {boolean}
     */
    get hasPrintTemplates() {
        return this._hasPrintTemplates;
    }

    /**
     * Tooltip layers config is defined
     * @type {boolean}
     */
    get hasTooltipLayers() {
        return this._hasTooltipLayers;
    }

    /**
     * Config tooltipLayers
     * @type {TooltipLayersConfig|null}
     */
    get tooltipLayers() {
        if (this._hasTooltipLayers) {
            if (this._tooltipLayers != null) {
                return this._tooltipLayers;
            }
            this._tooltipLayers = new TooltipLayersConfig(this._theConfig.tooltipLayers);
        }
        return this._tooltipLayers;
    }

    /**
     * Edition layers config is defined
     * @type {boolean}
     */
    get hasEditionLayers() {
        return this._hasEditionLayers;
    }

    /**
     * Config editionLayers
     * @type {EditionLayersConfig|null}
     */
    get editionLayers() {
        if (this._hasEditionLayers) {
            if (this._editionLayers != null) {
                return this._editionLayers;
            }
            this._editionLayers = new EditionLayersConfig(this._theConfig.editionLayers);
        }
        return this._editionLayers;
    }

    /**
     * Form filter layers config is defined
     * @type {boolean}
     */
    get hasFormFilterLayers() {
        return this._hasFormFilterLayers;
    }

    /**
     * Config formFilterLayers
     * @type {FormFilterConfig|null}
     */
    get formFilterLayers() {
        if (this.hasFormFilterLayers) {
            if (this._formFilterLayers != null) {
                return this._formFilterLayers;
            }
            this._formFilterLayers = new FormFilterConfig(this._theConfig.formFilterLayers);
        }
        return this._formFilterLayers;
    }

    /**
     * Login filtered layers config is defined
     * @type {boolean}
     */
    get hasLoginFilteredLayers() {
        return this._hasLoginFilteredLayers;
    }

    /**
     * Themes config is defined
     * @type {boolean}
     */
    get hasThemes() {
        return this._hasThemes;
    }

    /**
     * Config themes
     * @type {ThemesConfig|null}
     */
    get themes() {
        if (this.hasThemes) {
            if (this._themes != null) {
                return this._themes;
            }
            this._themes = new ThemesConfig(this._theConfig.themes);
        }
        return this._themes;
    }

    /**
     * Dataviz locale
     * @type {string}
     */
    get datavizLocale() {
        return this._theConfig.datavizLayers.locale;
    }

    /**
     * Dataviz config is defined
     * @type {boolean}
     */
    get hasDatavizConfig() {
        return this._hasDatavizConfig;
    }

    /**
     * Config datavizLayers
     * @type {DatavizLayersConfig|null}
     */
    get datavizLayers() {
        if (this._hasDatavizConfig) {
            if (this._datavizLayers != null) {
                return this._datavizLayers;
            }
            this._datavizLayers = new DatavizLayersConfig(this._theConfig.datavizLayers.layers);
        }
        return this._datavizLayers;
    }

    /**
     * Config datavizOptions
     * @type {DatavizOptionsConfig|null}
     */
    get datavizOptions() {
        if (this._hasDatavizConfig) {
            if (this._datavizOptions != null) {
                return this._datavizOptions;
            }
            this._datavizOptions = new DatavizOptionsConfig(this._theConfig.datavizLayers.dataviz);
        }
        return this._datavizOptions;
    }
}