"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.BingMapsImageryLayerProvider = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const Request_1 = require("../../../request/Request");
const IModelApp_1 = require("../../../IModelApp");
const internal_1 = require("../../internal");
/** Represents one range of geography and tile zoom levels for a bing data provider
 * @internal
 */
class Coverage {
    constructor(_lowerLeftLatitude, _lowerLeftLongitude, _upperRightLatitude, _upperRightLongitude, _minimumZoomLevel, _maximumZoomLevel) {
        this._lowerLeftLatitude = _lowerLeftLatitude;
        this._lowerLeftLongitude = _lowerLeftLongitude;
        this._upperRightLatitude = _upperRightLatitude;
        this._upperRightLongitude = _upperRightLongitude;
        this._minimumZoomLevel = _minimumZoomLevel;
        this._maximumZoomLevel = _maximumZoomLevel;
    }
    overlaps(quadId, tilingScheme) {
        const range = quadId.getLatLongRangeDegrees(tilingScheme);
        if (quadId.level < this._minimumZoomLevel)
            return false;
        if (quadId.level > this._maximumZoomLevel)
            return false;
        if (range.low.x > this._upperRightLongitude)
            return false;
        if (range.low.y > this._upperRightLatitude)
            return false;
        if (range.high.x < this._lowerLeftLongitude)
            return false;
        if (range.high.y < this._lowerLeftLatitude)
            return false;
        return true;
    }
}
/** Represents the copyright message and an array of coverage data for one of bing's data providers (HERE for example).
 * @internal
 */
class BingAttribution {
    constructor(copyrightMessage, _coverages) {
        this.copyrightMessage = copyrightMessage;
        this._coverages = _coverages;
    }
    matchesTile(tile, tilingScheme) {
        const quadId = internal_1.QuadId.createFromContentId(tile.contentId);
        for (const coverage of this._coverages) {
            if (coverage.overlaps(quadId, tilingScheme))
                return true;
        }
        return false;
    }
}
// in deployed applications, we can only make https requests, but the Bing Maps metadata request returns templates with "http:".
// This function fixes those.
function replaceHttpWithHttps(originalUrl) {
    return originalUrl.startsWith("http:") ? "https:".concat(originalUrl.slice(5)) : originalUrl;
}
/** @internal */
class BingMapsImageryLayerProvider extends internal_1.MapLayerImageryProvider {
    constructor(settings) {
        super(settings, true);
        this._urlBase = settings.url;
        this._zoomMax = 0;
        this._tileHeight = this._tileWidth = 0;
        this._mapTilingScheme = new internal_1.WebMercatorTilingScheme();
    }
    get tileWidth() { return this._tileWidth; }
    get tileHeight() { return this._tileHeight; }
    tileXYToQuadKey(tileX, tileY, zoomLevel) {
        // from C# example in bing documentation https://msdn.microsoft.com/en-us/library/bb259689.aspx
        let quadKey = "";
        // Root tile is not displayable. Returns 0 for _GetMaximumSize(). Should not end up here.
        (0, core_bentley_1.assert)(0 !== zoomLevel);
        for (let i = zoomLevel; i > 0; i--) {
            let digit = 0x30; // '0'
            const mask = 1 << (i - 1);
            if ((tileX & mask) !== 0) {
                digit++;
            }
            if ((tileY & mask) !== 0) {
                digit++;
                digit++;
            }
            quadKey = quadKey.concat(String.fromCharCode(digit));
        }
        return quadKey;
    }
    // construct the Url from the desired Tile
    async constructUrl(row, column, zoomLevel) {
        // From the tile, get a "quadKey" the Microsoft way.
        const quadKey = this.tileXYToQuadKey(column, row, zoomLevel);
        const subdomain = this._urlSubdomains[(row + column) % this._urlSubdomains.length];
        // from the template url, construct the tile url.
        let url = this._urlTemplate.replace("{subdomain}", subdomain);
        url = url.replace("{quadkey}", quadKey);
        return url;
    }
    // gets the attributions that match the tile set.
    getMatchingAttributions(tiles) {
        const matchingAttributions = new Array();
        if (!this._attributions || !tiles)
            return matchingAttributions;
        const unmatchedSet = this._attributions.slice();
        for (const tile of tiles) {
            if (tile instanceof internal_1.MapTile) {
                // compare to the set of Bing attributions that we have not yet matched.
                for (const attribution of unmatchedSet) {
                    if (attribution && attribution.matchesTile(tile, this._mapTilingScheme)) {
                        matchingAttributions.push(attribution);
                    }
                }
            }
        }
        return matchingAttributions;
    }
    addLogoCards(cards, vp) {
        const tiles = IModelApp_1.IModelApp.tileAdmin.getTilesForUser(vp)?.selected;
        const matchingAttributions = this.getMatchingAttributions(tiles);
        const copyrights = [];
        for (const match of matchingAttributions)
            copyrights.push(match.copyrightMessage);
        let copyrightMsg = "";
        for (let i = 0; i < copyrights.length; ++i) {
            if (i > 0)
                copyrightMsg += "<br>";
            copyrightMsg += copyrights[i];
        }
        cards.appendChild(IModelApp_1.IModelApp.makeLogoCard({ iconSrc: `${IModelApp_1.IModelApp.publicPath}images/bing.svg`, heading: "Microsoft Bing", notice: copyrightMsg }));
    }
    // initializes the BingImageryProvider by reading the templateUrl, logo image, and attribution list.
    async initialize() {
        // get the template url
        const bingRequestUrl = this._urlBase.replace("{bingKey}", this._settings.accessKey ? this._settings.accessKey.value : "");
        try {
            const bingResponseProps = await (0, Request_1.request)(bingRequestUrl, "json");
            const thisResourceSetProps = bingResponseProps.resourceSets[0];
            const thisResourceProps = thisResourceSetProps.resources[0];
            this._zoomMax = thisResourceProps.zoomMax;
            this._tileHeight = thisResourceProps.imageHeight;
            this._tileWidth = thisResourceProps.imageWidth;
            this._urlTemplate = replaceHttpWithHttps(thisResourceProps.imageUrl.replace("{culture}", "en-US")); // NEEDSWORK - get locale from somewhere.
            this._urlSubdomains = thisResourceProps.imageUrlSubdomains;
            // read the list of Bing's data suppliers and the range of data they provide. Used in calculation of copyright message.
            this.readAttributions(thisResourceProps.imageryProviders);
            // Bing sometimes provides tiles that have nothing but a camera icon in the middle of them when you ask
            // for tiles at zoom levels where they don't have data. Their application stops you from zooming in when that's the
            // case, but we can't stop - the user might want to look at design data a closer zoom. So we intentionally load such
            // a tile, and then compare other tiles to it, rejecting them if they match.
            this.loadTile(0, 0, this._zoomMax - 1).then((tileData) => {
                if (tileData !== undefined)
                    this._missingTileData = tileData.data;
            });
        }
        catch {
            throw new core_bentley_1.BentleyError(core_bentley_1.IModelStatus.BadModel, "Error in Bing Server communications");
        }
    }
    // reads the list of Bing data providers and the map range for which they each provide data.
    readAttributions(attributionProps) {
        for (const thisAttributionProps of attributionProps) {
            const copyrightMessage = thisAttributionProps.attribution;
            const coverages = new Array();
            for (const thisCoverageProps of thisAttributionProps.coverageAreas) {
                const thisCoverage = new Coverage(thisCoverageProps.bbox[0], thisCoverageProps.bbox[1], thisCoverageProps.bbox[2], thisCoverageProps.bbox[3], thisCoverageProps.zoomMin, thisCoverageProps.zoomMax);
                coverages.push(thisCoverage);
            }
            const thisAttribution = new BingAttribution(copyrightMessage, coverages);
            if (!this._attributions)
                this._attributions = new Array();
            this._attributions.push(thisAttribution);
        }
    }
}
exports.BingMapsImageryLayerProvider = BingMapsImageryLayerProvider;
//# sourceMappingURL=BingImageryProvider.js.map