import ImageProvider, { PanoramaView, ProvidedImage } from "./ImageProvider"
import BaseUIElement from "../../UI/BaseUIElement"
import { Utils } from "../../Utils"
import { LicenseInfo } from "./LicenseInfo"
import Constants from "../../Models/Constants"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import MapillaryIcon from "./MapillaryIcon.svelte"
import { Feature, Point } from "geojson"
import { Store, UIEventSource } from "../UIEventSource"

export class Mapillary extends ImageProvider {
    public static readonly singleton = new Mapillary()
    public readonly name = "Mapillary"

    private static readonly valuePrefix = "https://a.mapillary.com"
    public static readonly valuePrefixes = [
        Mapillary.valuePrefix,
        "http://mapillary.com",
        "https://mapillary.com",
        "http://www.mapillary.com",
        "https://www.mapillary.com",
    ]
    defaultKeyPrefixes = ["mapillary", "image"]

    /**
     * Indicates that this is the same URL
     * Ignores 'stp' parameter
     *
     * const a = "https://scontent-bru2-1.xx.fbcdn.net/m1/v/t6/An8xm5SGLt20ETziNqzhhBd8b8S5GHLiIu8N6BbyqHFohFAQoaJJPG8i5yQiSwjYmEqXSfVeoCmpiyBJICEkQK98JOB21kkJoBS8VdhYa-Ty93lBnznQyesJBtKcb32foGut2Hgt10hEMWJbE3dDgA?stp=s1024x768&ccb=10-5&oh=00_AT-ZGTXHzihoaQYBILmEiAEKR64z_IWiTlcAYq_D7Ka0-Q&oe=6278C456&_nc_sid=122ab1"
     * const b = "https://scontent-bru2-1.xx.fbcdn.net/m1/v/t6/An8xm5SGLt20ETziNqzhhBd8b8S5GHLiIu8N6BbyqHFohFAQoaJJPG8i5yQiSwjYmEqXSfVeoCmpiyBJICEkQK98JOB21kkJoBS8VdhYa-Ty93lBnznQyesJBtKcb32foGut2Hgt10hEMWJbE3dDgA?stp=s256x192&ccb=10-5&oh=00_AT9BZ1Rpc9zbY_uNu92A_4gj1joiy1b6VtgtLIu_7wh9Bg&oe=6278C456&_nc_sid=122ab1"
     * Mapillary.sameUrl(a, b) => true
     */
    static sameUrl(a: string, b: string): boolean {
        if (a === b) {
            return true
        }
        try {
            const aUrl = new URL(a)
            const bUrl = new URL(b)
            if (aUrl.host !== bUrl.host || aUrl.pathname !== bUrl.pathname) {
                return false
            }
            let allSame = true
            aUrl.searchParams.forEach((value, key) => {
                if (key === "stp") {
                    // This is the key indicating the image size on mapillary; we ignore it
                    return
                }
                if (value !== bUrl.searchParams.get(key)) {
                    allSame = false
                    return
                }
            })
            return allSame
        } catch (e) {
            console.debug("Could not compare ", a, "and", b, "due to", e)
        }
        return false
    }

    static createLink(
        location: {
            lon: number
            lat: number
        } = undefined,
        zoom: number = 17,
        pKey?: string
    ) {
        const params = {
            focus: pKey === undefined ? "map" : "photo",
            lat: location?.lat,
            lng: location?.lon,
            z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
            pKey,
        }
        const baselink = `https://www.mapillary.com/app/?`
        const paramsStr = Utils.NoNull(
            Object.keys(params).map((k) =>
                params[k] === undefined ? undefined : k + "=" + params[k]
            )
        )
        return baselink + paramsStr.join("&")
    }

    /**
     * Returns the correct key for API v4.0
     *
     * Mapillary.ExtractKeyFromURL("999924810651016") // => 999924810651016
     */
    private static ExtractKeyFromURL(value: string): number {
        let key: string

        if (value.startsWith("http")) {
            try {
                const url = new URL(value.toLowerCase())
                if (url.searchParams.has("pkey")) {
                    const pkey = Number(url.searchParams.get("pkey"))
                    if (!isNaN(pkey)) {
                        return pkey
                    }
                }
            } catch (e) {
                console.log("Could not parse value for mapillary:", value)
            }
        }
        if (value.startsWith(Mapillary.valuePrefix)) {
            key = value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1)
        } else if (value.match("[0-9]*")) {
            key = value
        }

        const keyAsNumber = Number(key)
        if (!isNaN(keyAsNumber)) {
            return keyAsNumber
        }

        return undefined
    }

    apiUrls(): string[] {
        return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
    }

    SourceIcon(
        img: { id: string; url: string },
        location?: {
            lon: number
            lat: number
        }
    ): BaseUIElement {
        let url: string = undefined
        const id = img.id
        if (id) {
            url = Mapillary.createLink(location, 16, "" + id)
        }
        return new SvelteUIElement(MapillaryIcon, { url })
    }

    async ExtractUrls(key: string, value: string): Promise<ProvidedImage[]> {
        const img = await this.PrepareUrlAsync(key, value)
        return [img]
    }

    /**
     * Download data necessary for the 360°-viewer
     * @param pkey
     * @constructor
     */
    public async getPanoramaInfo(image: {
        id: number | string
    }): Promise<Feature<Point, PanoramaView>> {
        const pkey = image.id
        const metadataUrl =
            "https://graph.mapillary.com/" +
            pkey +
            "?fields=computed_compass_angle,geometry,is_pano,thumb_2048_url,thumb_original_url&access_token=" +
            Constants.mapillary_client_token_v4
        const response = await Utils.downloadJsonCached<{
            computed_compass_angle: number
            geometry: Point

            is_pano: boolean
            thumb_2048_url: string
            thumb_original_url: string
            id: string
        }>(metadataUrl, 60 * 60)
        return {
            type: "Feature",
            geometry: response.geometry,
            properties: {
                url: response.thumb_2048_url,
                northOffset: response.computed_compass_angle,
                provider: this
            },
        }
    }

    public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
        const mapillaryId = providedImage.id
        const metadataUrl =
            "https://graph.mapillary.com/" +
            mapillaryId +
            "?fields=thumb_1024_url,thumb_original_url,captured_at,creator&access_token=" +
            Constants.mapillary_client_token_v4
        const response = await Utils.downloadJsonCached<{
            thumb_1024_url: string
            thumb_original_url: string
            captured_at
            creator: string
        }>(metadataUrl, 60 * 60)

        const license = new LicenseInfo()
        license.artist = response["creator"]["username"]
        license.license = "CC BY-SA 4.0"
        // license.license = "Creative Commons Attribution-ShareAlike 4.0 International License";
        license.attributionRequired = true
        const date = response["captured_at"]
        try {
            license.date = new Date(date)
        } catch (e) {
            console.warn(
                "Could not parse captured_at date from mapillary image. The date is:",
                date
            )
        }
        return license
    }

    private async PrepareUrlAsync(key: string, value: string): Promise<ProvidedImage> {
        const mapillaryId = Mapillary.ExtractKeyFromURL(value)
        if (mapillaryId === undefined) {
            return undefined
        }

        const metadataUrl =
            "https://graph.mapillary.com/" +
            mapillaryId +
            "?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,computed_geometry,creator,camera_type&access_token=" +
            Constants.mapillary_client_token_v4
        const response = await Utils.downloadJsonCached<{
            thumb_1024_url: string
            thumb_original_url: string
            captured_at
            compass_angle: number
            creator: string
            computed_geometry: Point
            geometry: Point
            camera_type: "equirectangular" | "spherical" | string
        }>(metadataUrl, 60 * 60)
        const url = <string>response["thumb_1024_url"]
        const url_hd = <string>response["thumb_original_url"]
        const date = new Date()
        const rotation: number = (720 - Number(response.compass_angle)) % 360
        const geometry: Point = response.computed_geometry ?? response.geometry
        date.setTime(response.captured_at)
        return <ProvidedImage>{
            id: "" + mapillaryId,
            url,
            url_hd,
            provider: this,
            date,
            key,
            rotation,
            isSpherical:
                response.camera_type === "spherical" || response.camera_type === "equirectangular",
            lat: geometry.coordinates[1],
            lon: geometry.coordinates[0],
            originalAttribute: {key, value}
        }
    }

    public visitUrl(
        image: { id: string },
        location?: { lon: number; lat: number }
    ): string | undefined {
        if (!image.id) {
            return
        }
        return Mapillary.createLink(location, 17, image.id)
    }


    /**
     * Returns true if we are in firefox strict mode (or if we are offline)
     * @private
     */
    private static async checkStrictMode(): Promise<boolean> {
        try {
            const result = await fetch("https://scontent-bru2-1.xx.fbcdn.net/m1/v/t6/Xn8-ISUUYQyBD9FyACzPFRGZnBJRqIFmnQ_yd7FU6vxFYwD21fvAcZwDQoMzsScxcQyCWeBviKpWO4nX8yf--neJDvVjC4JlQtfBYb6TrpXQTniyafSFeZeePT_NVx3H6gMjceEvXHyvBqOOcCB_xQ?stp=c2048.2048.2000.988a_s1000x1000&_nc_gid=E2oHnrAtHutVvjaIm9qDLg&_nc_oc=AdkcScR9HuKt1X_K5-GrUeR5Paj8d7MsNFFYEBSmgc0IiBey_wS3RiNJpflWIKaQzNE&ccb=10-5&oh=00_AfNJ1Ki1IeGdUMxdFUc3ZX9VYIVFxVfXZ9MUATU3vj_RJw&oe=686AF002&_nc_sid=201bca")
            console.log("Not blocked, got a forbidden", result.status)
            return false
        } catch (e) {
            console.log("Mapillary blocked - probably a scriptblocker")
            return true
        }
    }

    private static _isInStrictMode: UIEventSource<boolean> = undefined

    /**
     * Creates a store which contains true if strict tracking protection is probably enabled.
     * This will attempt to fetch a bit of content from mapillary - as that is probably the main culprit
     * Loads lazy, so will only attempt to fetch the _first time_ this method is called
     */
    public static isInStrictMode(): Store<boolean> {
        if (this._isInStrictMode === undefined) {
            this._isInStrictMode = UIEventSource.FromPromise(this.checkStrictMode())
        }
        return this._isInStrictMode
    }
}
