import { Helper, InitPromise, MultipleShareButton, AndroidBridge, PauseSite, Fragment, Translator } from './pwa-lib.js';

class DelayPromise extends Promise {
    static async delay(delay) {
        return new Promise((resolve) => {
            setTimeout(resolve, delay);
        });
    }
}

class MyStorageManager {
    static getInstance() {
        if (Helper.isNull(MyStorageManager.instance)) {
            MyStorageManager.instance = new MyStorageManager();
        }
        return MyStorageManager.instance;
    }

    async estimate() {
        if ('storage' in navigator && 'estimate' in navigator["storage"]) {
            // We've got the real thing! Return its response.
            return navigator["storage"]["estimate"]();
        }

        if ('webkitTemporaryStorage' in navigator &&
            'queryUsageAndQuota' in navigator["webkitTemporaryStorage"]) {
            // Return a promise-based wrapper that will follow the expected interface.
            return new Promise(function (resolve, reject) {
                navigator["webkitTemporaryStorage"]["queryUsageAndQuota"](
                    function (usage, quota) {
                        resolve({"usage": usage, "quota": quota});
                    },
                );
            });
        }
        // If we can't estimate the values, return a Promise that resolves with NaN.
        return Promise.resolve({"usage": NaN, "quota": NaN});
    }

    async isPersistent(){
        if (this.canPersist()){
            return navigator["storage"]["persisted"]();
        }
        return Promise.resolve(false);
    }

    canEstimateStorage() {
        return ('storage' in navigator && 'estimate' in navigator["storage"]|| 'webkitTemporaryStorage' in navigator &&
            'queryUsageAndQuota' in navigator["webkitTemporaryStorage"]);
    }

    canPersist() {
        return (navigator["storage"] && navigator["storage"]["persist"]);
    }

    persist(){
        if (this.canPersist()){
            return navigator["storage"]["persist"]();
        }
        return Promise.resolve(false);
    }
}

MyStorageManager.instance = null;

class InstallManager {
    static init() {
        window.addEventListener('beforeinstallprompt', e => {
            e.preventDefault();
            this.setDeferredPrompt(e);
        });
    }

    static setDeferredPrompt(e){
        this.deferredPromt = e;
        if (this.canInstallListener) {
            this.canInstallListener(this.deferredPromt);
        }
    }

    static async prompt(){
        if (Helper.isNotNull(this.deferredPromt)){
            this.deferredPromt["prompt"]();
            return this.deferredPromt["userChoice"].then(r => {
                MyStorageManager.getInstance().persist();
                return r;
            });
        }
        return Promise.resolve({
            "outcome":"dismissed",
            "platform":""
        });
    }

    static isInstalled(){
        return matchMedia("(display-mode: standalone)").matches;
    }

    static setCanInstallListener(listener, callIfCanInstall) {
        this.canInstallListener = listener;
        callIfCanInstall = Helper.nonNull(callIfCanInstall, true);

        if (callIfCanInstall && Helper.nonNull(this.deferredPromt)) {
            this.canInstallListener(this.deferredPromt);
        }
    }
}

InstallManager.init();

class Matomo {

    static init() {
        Matomo.isTrackingPromise = new Promise(async (resolve) => {
            let shouldTrack = Helper.nonNull(localStorage.getItem(Matomo.LOCAL_STORAGE_KEY), "1");
            if (Helper.isNull(shouldTrack)) {
                shouldTrack = await Matomo._askIsTracking();
                localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, shouldTrack);
            }
            else {
                shouldTrack = (shouldTrack === "1");
                Matomo.setTrack(shouldTrack);
            }
            resolve(shouldTrack);
        });
        Matomo.isTrackingPromise.then(() => {
            Matomo.push(['trackPageView'], true);
            Matomo.push(['enableLinkTracking'], true);
            Matomo.push(['setTrackerUrl', Matomo.TRACK_SITE + '/piwik.php'], true);
            Matomo.push(['setSiteId', Matomo.SIDE_ID + ""], true);

            let d = document, g = d.createElement('script'), s = d.getElementsByTagName('head')[0];
            g.type = 'text/javascript';
            g.async = true;
            g.defer = true;
            g.src = Matomo.TRACK_SITE + '/piwik.js';
            s.appendChild(g);
        });
    }

    static update(title) {
        if (Helper.nonNull(Matomo.currentUrl)) {
            Matomo.push(['setReferrerUrl', Matomo.currentUrl]);
        }
        Matomo.currentUrl = window.location.pathname + window.location.search;
        Matomo.push(['setCustomUrl', Matomo.currentUrl]);
        Matomo.push(['setDocumentTitle', title]);

        // remove all previously assigned custom variables, requires Matomo (formerly Piwik) 3.0.2
        Matomo.push(['deleteCustomVariables', 'page']);
        Matomo.push(['setGenerationTimeMs', 0]);
        Matomo.push(['trackPageView']);

        // make Matomo aware of newly added content
        var content = document.getElementById('site-content');
        Matomo.push(['MediaAnalytics::scanForMedia', content]);
        Matomo.push(['FormAnalytics::scanForForms', content]);
        Matomo.push(['trackContentImpressionsWithinNode', content]);
        Matomo.push(['enableLinkTracking']);
    }

    static async _askIsTracking() {
        Matomo.isTrackingPromise = new Promise(resolve => {
            Matomo.push([function () {
                resolve(!this["isUserOptedOut"]());
            }]);
            Matomo.push([function () {
                resolve(!this["isUserOptedOut"]());
            }]);
        });
        return Matomo.isTrackingPromise;
    }

    static async query(method) {
        return fetch(Matomo.TRACK_SITE + Matomo.BASE_PATH + method, {
            "mode": "cors",
            "credentials": "include",
        }).then(res => res.text()).then(text => (new window.DOMParser()).parseFromString(text, "text/xml"));
    }

    static getTrackingPromise() {
        return Matomo.isTrackingPromise;
    }

    static async setTrack(shouldTrack) {
        Matomo.isTrackingPromise = Promise.resolve(shouldTrack);
        localStorage.setItem(Matomo.LOCAL_STORAGE_KEY, (shouldTrack === true) ? "1" : "0");

        if (shouldTrack) {
            Matomo.push(["forgetUserOptOut"], true);
        }
        else {
            Matomo.push(["optUserOut"], true);
        }
    }

    static async trackEvent(event, name, label, value){
        let ev = ["trackEvent", event, name];
        if (Helper.isNotNull(label)){
            ev.push(label);
        }
        if (Helper.isNotNull(value) && !isNaN(parseFloat(value)) && isFinite(value)){
            ev.push(value);
        }

        return this.push(ev);
    }

    static async push(arr, force) {

        if (!Array.isArray(arr)) {
            arr = [arr];
        }
        window["_paq"].push(arr);
    }
}

Matomo.currentUrl = null;

Matomo.LOCAL_STORAGE_KEY = "matomoShouldTrack";
Matomo.TRACK_SITE = "//matomo.silas.link";
Matomo.BASE_PATH = "/index.php?module=API&method=AjaxOptOut.";
Matomo.SIDE_ID = "1";

InitPromise.addPromise(() => {
    window["_paq"] = window["_paq"] || [];
    Matomo.init();
});

class MatomoShareButton extends MultipleShareButton{

    constructor(baseButton, platform, shouldLoadImg) {
        super([baseButton, (url) => {
            Matomo.trackEvent("shared", url, platform);
        }], shouldLoadImg);
    }
}

AndroidBridge.addDefinition("MatomoShareButton", MatomoShareButton);

class RotateHelper {
    rotate(element, degrees){
        let rotateText = element.innerText;
        element.removeAllChildren();

        let partDegree = degrees/rotateText.length;
        for(let i = 0; i < rotateText.length; i++){
            let child = document.createElement("span");
            child.innerText = rotateText.charAt(i);
            child.style.transform ="rotate("+(partDegree*i)+"deg)";
            child.classList.add("rotated");
            element.appendChild(child);
        }
    }
}

class ScaleHelper {
    async scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, addListener) {

        addListener = Helper.nonNull(addListener, true);
        animationDelay = Helper.nonNull(animationDelay, 0);

        let newFontSize = await this._getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay === 0);

        if (animationDelay > 0) {
            await new Promise(r => {
                setTimeout(r, animationDelay);
                fontElement.style.fontSize = newFontSize + "px";
            });
        }

        let self = this;
        let listener = function () {
            return new Promise(resolve => {
                let timeout = (typeof addListener === 'number') ? addListener : 255;
                setTimeout(() => {
                    resolve(self.scaleTo(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animationDelay, false));
                }, timeout);
            });
        };
        if (addListener !== false) {
            window.addEventListener("resize", listener);
        }
        return listener;
    }

    async scaleToFull(fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener) {
        return this.scaleTo(1, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, animDelay, addListener);
    }

    async _getNewFontSize(scale, fontElement, container, ignoreHeight, ignoreWidth, margin, fontWeight, setFontSize) {
        margin = Helper.nonNull(margin, 10);
        ignoreHeight = Helper.nonNull(ignoreHeight, false);
        ignoreWidth = Helper.nonNull(ignoreWidth, false);
        fontWeight = Helper.nonNull(fontWeight, fontElement.innerHTML.length);
        setFontSize = Helper.nonNull(setFontSize, true);

        let hasNoTransitionClass = container.classList.contains("no-transition");

        if (!hasNoTransitionClass) {
            container.classList.add("no-transition");
        }

        const numChanged = 5;
        let oldDiffIndex = 0;
        let oldDiff = [];

        for (let i = 0; i < numChanged; i++) {
            oldDiff.push(0);
        }

        let beforeFontSize = fontElement.style.fontSize;
        let currentFontSize = 1;
        let widthDiff = 0;
        let heightDiff = 0;
        let containerWidth = 0;
        let containerHeight = 0;
        do {
            currentFontSize += oldDiff[oldDiffIndex] / (fontWeight + 1);
            fontElement.style.fontSize = currentFontSize + 'px';

            let containerStyle = window.getComputedStyle(container);

            containerWidth = containerStyle.getPropertyValue("width").replace('px', '');
            containerHeight = containerStyle.getPropertyValue("height").replace('px', '');

            widthDiff = containerWidth - fontElement.offsetWidth;
            heightDiff = containerHeight - fontElement.offsetHeight;

            oldDiffIndex = (oldDiffIndex+1)%numChanged;
            let newDiff = (ignoreWidth ? heightDiff : (ignoreHeight ? widthDiff : Math.min(widthDiff, heightDiff)));
            if (newDiff === oldDiff[(oldDiffIndex+1)%numChanged]) {
                break;
            }
            oldDiff[oldDiffIndex] = newDiff;
        } while ((widthDiff > (1 - scale) * containerWidth || ignoreWidth) && (heightDiff > (1 - scale) * containerHeight || ignoreHeight));

        currentFontSize -= margin;
        fontElement.style.fontSize = ((setFontSize) ? currentFontSize + "px" : beforeFontSize);

        if (!hasNoTransitionClass) {
            await new Promise((r) => {
                setTimeout(r, 50);
            });
            container.classList.remove("no-transition");
        }

        return currentFontSize;
    }
}

class AudioChain {

    constructor(context, sourceBuffer, chainFunction) {
        this.buffer = sourceBuffer;
        this.shouldLoop = false;
        this.loopStart = null;
        this.loopEnd = null;
        this.chainFunction = chainFunction;
        this.context = context;

        this.startTime = null;
        this.pauseTime = null;
        this.source = null;

        this.running = false;
    }

    setBuffer(buffer) {
        this.buffer = buffer;
    }

    setLooping(shouldLoop, loopStart, loopEnd) {
        this.shouldLoop = shouldLoop;

        if (Helper.isNotNull(loopStart)) {
            this.loopStart = loopStart;
        }
        if (Helper.isNotNull(loopEnd)) {
            this.loopEnd = loopEnd;
        }
    }

    async start(delay, offset, duration) {
        //sind sonst null, schmeißt in Android 5 einen fehler
        delay = Helper.nonNull(delay, 0);
        offset = Helper.nonNull(offset, 0);
        //Duration darf nicht gesetzt werden
        // duration = Helper.nonNull(duration, -1);

        let source = this.context.createBufferSource();

        source.loop = this.shouldLoop;
        if (Helper.isNotNull(this.loopStart)) {
            source.loopStart = this.loopStart;
        }
        if (Helper.isNotNull(this.loopEnd)) {
            source.loopEnd = this.loopEnd;
        }
        source.buffer = this.buffer;
        await this.chainFunction(source);

        if (Helper.isNull(duration)){
            source.start(delay, offset);
        }
        else{
            source.start(delay, offset, duration);
        }
        this.startTime = (new Date()).getTime() - (Helper.nonNull(offset, 0) * 1000);
        this.source = source;
        this.running = true;
    }

    async stop(delay) {
        if (Helper.isNotNull(this.source)) {
            delay = Helper.nonNull(delay, 0);

            this.pauseTime = ((new Date()).getTime()) - this.startTime;
            this.running = false;
            return this.source.stop(delay);
        }
        return null;
    }

    async resume() {

        if (!this.running) {
            return this.start(null, Helper.nonNull(this.pauseTime, 0) / 1000.0);
        }
    }
}

class SoundManager {
    static getInstance() {
        if (Helper.isNull(SoundManager.instance)) {
            SoundManager.instance = new SoundManager();
        }
        return SoundManager.instance;
    }

    constructor() {
        this.channels = {};
        this.context = new AudioContext();
        this.context.onstatechange = function () {
            console.log("stateChange from context", arguments);
        };
        this.context.oncomplete = function () {
            console.log("onComplete from context", arguments);
        };

        window.addEventListener("visibilitychange", () => {
            this.handleVisibilityChange();
        });
    }

    isNotSuspended(){
        // return false;
        return this.context.state !== "suspended";
    }

    set(options, channel) {
        channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
        let audioObject = Helper.nonNull(this.channels[channel], {});

        if (typeof options === "string") {
            options = {audio: options};
        }

        let audio = options.audio;
        if (Helper.isNotNull(audio)) {
            audioObject.loadedPromise = fetch(audio).then(res => res.arrayBuffer()).then(arrayBuffer => {
                return new Promise((r) => this.context.decodeAudioData(arrayBuffer, r));
            }).catch(e => console.error(e));
            this.stop(channel);
        }
        audioObject.muted = Helper.nonNull(options.muted, audioObject.muted, false);
        audioObject.volume = Helper.nonNull(options.volume, audioObject.volume, 1);
        audioObject.loop = Helper.nonNull(options.loop, audioObject.loop, false);
        audioObject.timeOffset = Helper.nonNull(options.timeOffset, audioObject.timeOffset, 0);
        this.channels[channel] = audioObject;

        if (audioObject.muted) {
            this.stop(channel);
        }

        return this.channels[channel];
    }

    async resumeContext(){
        if (typeof this.context.resume === "function") {
            return this.context.resume();
        }
    }

    async play(channel, audioOrOptions) {
        this.resumeContext();
        channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
        if (Helper.isNull(audioOrOptions)) {
            audioOrOptions = {};
        } else if (!(typeof audioOrOptions === "object")) {
            audioOrOptions = {
                audio: audioOrOptions
            };
        }
        audioOrOptions.timeOffset = Helper.nonNull(audioOrOptions.timeOffset, 0);

        this.stop(channel);
        this.set(audioOrOptions, channel);

        if (!this.channels[channel].muted) {
            let buffer = await this.channels[channel].loadedPromise;
            let source = new AudioChain(this.context, buffer, (sourceNode) => {
                let gain = this.context.createGain();
                gain.gain.value = this.channels[channel].volume;

                sourceNode.connect(gain);
                gain.connect(this.context.destination);
            });

            source.setBuffer(buffer);

            //to prevent gap in mp3-files
            source.setLooping(this.channels[channel].loop, 0.3, buffer.duration - 0.3);

            this.channels[channel].source = source;
            await source.start();
        }
        return this.channels[channel];
    }

    stop(channel) {
        channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);


        let oldAudio = this.channels[channel];
        if (Helper.isNotNull(oldAudio) && Helper.isNotNull(oldAudio.source)) {
            oldAudio.source.stop();
        }
    }

    get(channel) {
        channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
        return this.channels[channel];
    }

    async resume(channel) {
        channel = Helper.nonNull(channel, SoundManager.CHANNELS.DEFAULT);
        if (Helper.isNotNull(this.channels[channel]) && !this.channels[channel].muted && Helper.isNotNull(this.channels[channel].source)) {
            return this.channels[channel].source.resume();
        }
    }

    stopAll(){
        for (let k in this.channels) {
            if (Helper.isNotNull(this.channels[k].source)) {
                this.channels[k].source.stop();
            }
        }
    }

    resumeAllIfNotMuted(){
        for (let k in this.channels) {
            if (Helper.isNotNull(this.channels[k]) && !this.channels[k].muted && Helper.isNotNull(this.channels[k].source)) {
                this.channels[k].source.resume();
            }
        }
    }

    handleVisibilityChange() {
        if (document.hidden) {
            this.stopAll();
        }
        else {
            this.resumeAllIfNotMuted();
        }
    }
}

SoundManager.CHANNELS = {
    MUSIC: "music",
    SOUND: "sound",
    DEFAULT: "default"
};

InitPromise.addPromise(() => {
    PauseSite.onPauseListeners.push(() => {
        SoundManager.getInstance().stopAll();
    });
    PauseSite.onStartListeners.push(() => {
        SoundManager.getInstance().resumeAllIfNotMuted();
    });
});

// AndroidBridge.addDefinition(() => {
    // window["soundManagerInstance"] = SoundManager.getInstance();
    // window["soundManagerInstance"]["stopAll"] = window["soundManagerInstance"].stopAll;
    // window["soundManagerInstance"]["resumeAllIfNotMuted"] = window["soundManagerInstance"].resumeAllIfNotMuted;
// });

class TabbedFragment extends Fragment {
    constructor(site) {
        super(site, 'pwaAssets/html/fragment/tabbedFragment.html');
        this.fragments = {};
        this.tabHeadingTemplate = null;
        this.tabHeadingContainer = null;
        this.tabContainer = null;
        this.tabKeys = [];
        this.currentTabIndex = 0;
    }

    onFirstStart() {
        super.onFirstStart();
        this.tabContainer = this.findBy(".tab-container");
        this.showTab(0);
    }

    addFragment(name, fragment, translationArgs, isTranslatable) {
        isTranslatable = Helper.nonNull(isTranslatable, true);
        if (translationArgs === false) {
            isTranslatable = false;
        }

        this.fragments[name] = fragment;
        const tabIndex = this.tabKeys.length;
        this.tabKeys.push(name);

        const _self = this;
        this.inflatePromise = this.inflatePromise.then(function (siteContent) {
            if (Helper.isNull(_self.tabHeadingTemplate)) {
                _self.tabHeadingTemplate = siteContent.querySelector(".tab-header-template");
            }
            const newTabHeader = Helper.cloneNode(_self.tabHeadingTemplate);
            newTabHeader.classList.add("tab-" + tabIndex);
            newTabHeader.querySelector(".tab-name").appendChild((isTranslatable) ? Translator.makePersistentTranslation(name, translationArgs) : document.createTextNode(name));
            newTabHeader.addEventListener("click", function(){
                _self.showTab(tabIndex);
            });

            if (Helper.isNull(_self.tabHeadingContainer)) {
                _self.tabHeadingContainer = siteContent.querySelector(".tab-header-container");
            }
            _self.tabHeadingContainer.appendChild(newTabHeader);
            return siteContent;
        });
    }

    showTab(index) {
        if (index >= 0 && index < this.tabKeys.length) {
            this.findBy(".tab-" + this.currentTabIndex).classList.remove("active");
            this.findBy(".tab-" + index).classList.add("active");
            this.tabContainer.removeAllChildren().appendChild(Helper.createLoadingSymbol());

            this.currentTabIndex = index;

            const _self = this;
            this.fragments[this.tabKeys[index]].inflatePromise.then(tabView => {
                if (_self.currentTabIndex === index) {
                    _self.tabContainer.removeAllChildren().appendChild(tabView);
                }
            });
        }
    }
}

export { DelayPromise, InstallManager, Matomo, MatomoShareButton, MyStorageManager, RotateHelper, ScaleHelper, AudioChain, SoundManager, TabbedFragment };
