/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module MapLayers
 */
import { compareStrings, expectDefined } from "@itwin/core-bentley";
import { BackgroundMapProvider, BackgroundMapType, BaseMapLayerSettings, ImageMapLayerSettings, } from "@itwin/core-common";
import { Point2d } from "@itwin/core-geometry";
import { IModelApp } from "../../IModelApp";
import { request } from "../../request/Request";
import { ArcGisUtilities, MapCartoRectangle } from "../internal";
/** Values for return codes from [[MapLayerSource.validateSource]]
 * @public
 */
export var MapLayerSourceStatus;
(function (MapLayerSourceStatus) {
    /** Layer source is valid */
    MapLayerSourceStatus[MapLayerSourceStatus["Valid"] = 0] = "Valid";
    /** Authorization has failed when accessing this layer source. */
    MapLayerSourceStatus[MapLayerSourceStatus["InvalidCredentials"] = 1] = "InvalidCredentials";
    /** Provided format id could not be resolved in [[MapLayerFormatRegistry]] */
    MapLayerSourceStatus[MapLayerSourceStatus["InvalidFormat"] = 2] = "InvalidFormat";
    /** The tiling schema of the source is not supported */
    MapLayerSourceStatus[MapLayerSourceStatus["InvalidTileTree"] = 3] = "InvalidTileTree";
    /** Could not not connect to remote server using the provided URL.*/
    MapLayerSourceStatus[MapLayerSourceStatus["InvalidUrl"] = 4] = "InvalidUrl";
    /** Authorization is required to access this map layer source. */
    MapLayerSourceStatus[MapLayerSourceStatus["RequireAuth"] = 5] = "RequireAuth";
    /** Map-layer coordinate system is not supported */
    MapLayerSourceStatus[MapLayerSourceStatus["InvalidCoordinateSystem"] = 6] = "InvalidCoordinateSystem";
    /** Format is not compatible with the URL provided.
     */
    MapLayerSourceStatus[MapLayerSourceStatus["IncompatibleFormat"] = 7] = "IncompatibleFormat";
})(MapLayerSourceStatus || (MapLayerSourceStatus = {}));
/** A source for map layers. These may be catalogued for convenient use by users or applications.
 * @public
 */
export class MapLayerSource {
    formatId;
    name;
    url;
    baseMap = false;
    transparentBackground;
    userName;
    password;
    /** List of query parameters that will get appended to the source URL that should be be persisted part of the JSON representation.
     * @beta
    */
    savedQueryParams;
    /** List of query parameters that will get appended to the source URL that should *not* be be persisted part of the JSON representation.
     * @beta
    */
    unsavedQueryParams;
    constructor(formatId = "WMS", name, url, baseMap = false, transparentBackground = true, savedQueryParams) {
        this.formatId = formatId;
        this.name = name;
        this.url = url;
        this.baseMap = baseMap;
        this.transparentBackground = transparentBackground;
        this.savedQueryParams = savedQueryParams;
    }
    static fromJSON(json) {
        if (json === undefined)
            return undefined;
        return new MapLayerSource(json.formatId, json.name, json.url, json.baseMap, json.transparentBackground, json.queryParams);
    }
    async validateSource(ignoreCache) {
        return IModelApp.mapLayerFormatRegistry.validateSource({ source: this, ignoreCache });
    }
    /** @internal*/
    static fromBackgroundMapProps(props) {
        const provider = BackgroundMapProvider.fromBackgroundMapProps(props);
        const layerSettings = BaseMapLayerSettings.fromProvider(provider);
        if (undefined !== layerSettings) {
            const source = MapLayerSource.fromJSON(layerSettings);
            if (source) {
                source.baseMap = true;
                return source;
            }
        }
        return undefined;
    }
    toJSON() {
        return { url: this.url, name: this.name, formatId: this.formatId, transparentBackground: this.transparentBackground, queryParams: this.savedQueryParams };
    }
    toLayerSettings(subLayers) {
        // When MapLayerSetting is created from a MapLayerSource, sub-layers and credentials need to be set separately.
        const layerSettings = ImageMapLayerSettings.fromJSON({ ...this, subLayers });
        if (this.userName !== undefined || this.password !== undefined) {
            layerSettings?.setCredentials(this.userName, this.password);
        }
        if (this.savedQueryParams) {
            layerSettings.savedQueryParams = { ...this.savedQueryParams };
        }
        if (this.unsavedQueryParams) {
            layerSettings.unsavedQueryParams = { ...this.unsavedQueryParams };
        }
        return layerSettings;
    }
    getCredentials() {
        return this.userName && this.password ? { user: this.userName, password: this.password } : undefined;
    }
    /** Collect all query parameters
   * @beta
   */
    collectQueryParams() {
        let queryParams = {};
        if (this.savedQueryParams)
            queryParams = { ...this.savedQueryParams };
        if (this.unsavedQueryParams)
            queryParams = { ...queryParams, ...this.unsavedQueryParams };
        return queryParams;
    }
}
/** A collection of [[MapLayerSource]] objects.
 * @beta
 */
export class MapLayerSources {
    _sources;
    static _instance;
    constructor(_sources) {
        this._sources = _sources;
    }
    static getInstance() { return MapLayerSources._instance; }
    findByName(name, baseMap = false) {
        const nameTest = name.toLowerCase();
        for (const source of this._sources)
            if (source.baseMap === baseMap && source.name.toLowerCase().indexOf(nameTest) !== -1)
                return source;
        return undefined;
    }
    get layers() {
        const layers = new Array();
        this._sources.forEach((source) => {
            if (!source.baseMap)
                layers.push(source);
        });
        return layers;
    }
    get allSource() { return this._sources; }
    get bases() {
        const layers = new Array();
        this._sources.forEach((source) => {
            if (source.baseMap)
                layers.push(source);
        });
        return layers;
    }
    static getBingMapLayerSource() {
        const mapLayerSources = [];
        // We know these hard-coded sources are valid, so ! is okay here.
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "BingProvider", providerData: { mapType: BackgroundMapType.Street } }));
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "BingProvider", providerData: { mapType: BackgroundMapType.Aerial } }));
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "BingProvider", providerData: { mapType: BackgroundMapType.Hybrid } }));
        return mapLayerSources;
    }
    static getMapBoxLayerSource() {
        const mapLayerSources = [];
        // We know these hard-coded sources are valid, so ! is okay here.
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "MapBoxProvider", providerData: { mapType: BackgroundMapType.Street } }));
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "MapBoxProvider", providerData: { mapType: BackgroundMapType.Aerial } }));
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        mapLayerSources.push(MapLayerSource.fromBackgroundMapProps({ providerName: "MapBoxProvider", providerData: { mapType: BackgroundMapType.Hybrid } }));
        return mapLayerSources;
    }
    static async create(iModel, queryForPublicSources = false, addMapBoxSources = false) {
        if (!queryForPublicSources && MapLayerSources._instance)
            return MapLayerSources._instance;
        if (!iModel)
            iModel = IModelApp.viewManager.selectedView ? IModelApp.viewManager.selectedView.iModel : undefined;
        let sourceRange = MapCartoRectangle.createMaximum();
        if (iModel) {
            const projectCenter = expectDefined(iModel.projectExtents.localXYZToWorld(.5, .5, .5));
            const cartoCenter = iModel.spatialToCartographicFromEcef(projectCenter);
            const globeRange = MapCartoRectangle.createMaximum();
            const nearDelta = Point2d.create(globeRange.xLength() / 100, globeRange.yLength() / 100);
            sourceRange = MapCartoRectangle.fromRadians(cartoCenter.longitude - nearDelta.x, cartoCenter.latitude - nearDelta.y, cartoCenter.longitude + nearDelta.x, cartoCenter.latitude + nearDelta.y);
        }
        const sources = new Array();
        const urlSet = new Set();
        const addSource = ((source) => {
            if (!urlSet.has(source.url)) {
                sources.push(source);
                urlSet.add(source.url);
            }
        });
        this.getBingMapLayerSource().forEach((source) => {
            addSource(source);
        });
        if (addMapBoxSources) {
            this.getMapBoxLayerSource().forEach((source) => {
                addSource(source);
            });
        }
        if (queryForPublicSources) {
            const sourcesJson = await request(`${IModelApp.publicPath}assets/MapLayerSources.json`, "json");
            for (const sourceJson of sourcesJson) {
                const source = MapLayerSource.fromJSON(sourceJson);
                if (source)
                    addSource(source);
            }
            (await ArcGisUtilities.getSourcesFromQuery(sourceRange)).forEach((queriedSource) => addSource(queriedSource));
        }
        sources.sort((a, b) => compareStrings(a.name.toLowerCase(), b.name.toLowerCase()));
        const mapLayerSources = new MapLayerSources(sources);
        MapLayerSources._instance = mapLayerSources;
        return mapLayerSources;
    }
    static async addSourceToMapLayerSources(mapLayerSource) {
        if (!MapLayerSources._instance || !mapLayerSource) {
            return undefined;
        }
        MapLayerSources._instance._sources = MapLayerSources._instance._sources.filter((source) => {
            return !(source.name === mapLayerSource.name || source.url === mapLayerSource.url);
        });
        MapLayerSources._instance._sources.push(mapLayerSource);
        MapLayerSources._instance._sources.sort((a, b) => compareStrings(a.name.toLowerCase(), b.name.toLowerCase()));
        return MapLayerSources._instance;
    }
    static removeLayerByName(name) {
        if (!MapLayerSources._instance) {
            return false;
        }
        // For now we only rely on the name
        const lengthBeforeRemove = MapLayerSources._instance._sources.length;
        MapLayerSources._instance._sources = MapLayerSources._instance._sources.filter((source) => {
            return (source.name !== name);
        });
        return (lengthBeforeRemove !== MapLayerSources._instance._sources.length);
    }
}
//# sourceMappingURL=MapLayerSources.js.map