class InitPromise
{
    static addPromise(promise)
    {
        if (typeof promise === 'function')
        {
            let func = promise;
            promise = InitPromise.mainPromise.then(function(app){
                return (func(app));
            });
        }
        InitPromise.promises.push(promise);
    }

    static resolve(app)
    {
        InitPromise.mainResolver(app);
        return InitPromise.mainPromise.then(function(){
            return Promise.all(InitPromise.promises);
        });
    }
}

InitPromise.promises = [];
InitPromise.mainPromise = new Promise(function(resolver){
    InitPromise.mainResolver = resolver;
});

class AndroidBridge {

    static addDefinition(definition, object) {
        if (typeof definition !== "function") {
            if (typeof definition === "string"){
                let parts = definition.split(".");
                for (let i = parts.length-1; i >= 1; i--) {
                    let newObject = {};
                    newObject[parts[i]] = object;
                    object = newObject;
                }
                definition = parts[0];
                // console.log("parts for", definition, parts, object);
            }
            let textDefinition = definition;
            definition = () => {
                // console.log("defining", textDefinition, object);
                window[textDefinition] = object;
            };
        }
        AndroidBridge.definitions.push(definition);
    }

    static applyDefinitions() {
        for (let i = 0; i < AndroidBridge.definitions.length; i++) {
            AndroidBridge.definitions[i]();
        }
        return Promise.resolve();
    }
}

AndroidBridge.definitions = [];
AndroidBridge.addDefinition("InitPromise.addPromise", InitPromise.addPromise);

class MenuAction {
    constructor(title, callback, showFor, order) {
        this.title = Helper.nonNull(title, null);
        this.callback = callback;
        this.showFor = Helper.nonNull(showFor, MenuAction.SHOW_FOR_MEDIUM);
        this.order = Helper.nonNull(order, 1000);
        this._liClass = "";

        this._menu = null;
        this._activated = true;
        this._visible = true;
        this.id = MenuAction.maxId++;
        this._icon = null;
        this._shouldTranslate = true;
        this._copies = [];
    }

    setTitle(title)
    {
        this.title = title;
    }

    setShouldTranslate(shouldTranslate)
    {
        this._shouldTranslate = shouldTranslate;
    }

    getShowFor(){
        return this.showFor;
    }

    getTitle()
    {
        return this.title;
    }

    getShouldTranslate()
    {
        return this._shouldTranslate;
    }

    remove(removeCopies) {
        removeCopies = Helper.nonNull(removeCopies, false);
        if (Helper.isNotNull(this._menu)) {
            console.log(this._menu);
            this._menu.removeAction(this);
            this._menu = null;
        }
        if (removeCopies)
        {
            for (let i = 0; i < this._copies.length; i++) {
                this._copies[i].remove();
            }
        }
    }

    getMenu() {
        return this._menu;
    }

    setMenu(value) {
        this._menu = value;
    }

    getVisible() {
        return this._visible;
    }

    setVisible(value) {
        if (value !== this._visible) {
            this._visible = value;
            this.redraw();
        }
    }

    getActivated() {
        return this._activated;
    }

    getIcon() {
        return this._icon;
    }

    setIcon(value) {
        this._icon = value;
    }

    getId()
    {
        return this.id;
    }

    redraw() {
        if (Helper.isNotNull(this._menu)) {
            this._menu.updateAction(this);
        }
    }
    copy(instance){
        let copy = Helper.nonNull(instance, new MenuAction());
        copy.title = this.title;
        copy.callback = this.callback;
        copy.showFor = this.showFor;
        copy.order = this.order;
        copy._liClass = this._liClass;

        copy._activated = this._activated;
        copy._visible = this._visible;
        copy._icon = this._icon;
        copy._shouldTranslate = this._shouldTranslate;

        copy._menu = null;
        copy.id = MenuAction.maxId++;
        this._copies.push(copy);
        return copy;
    }

    redrawMenu()
    {
        if (Helper.isNotNull(this._menu)) {
            this._menu.redraw();
        }
    }
}
MenuAction.maxId = 0;

MenuAction.SHOW_ALWAYS = "always";
MenuAction.SHOW_FOR_MEDIUM = "medium";
MenuAction.SHOW_FOR_LARGE = "large";
MenuAction.SHOW_NEVER = "never";

class OpenSubmenuAction extends MenuAction {
    constructor(title, menu, showFor, order) {
        super(title, function (action) {
            action.getSubmenu().toggle();
            action.redraw();
        }, showFor, order);

        this.submenu = menu;
        menu.setParentAction(this);
    }

    getSubmenu() {
        return this.submenu;
    }

    copy(instance) {
        instance = super.copy(Helper.nonNull(instance, new OpenSubmenuAction(null, this.submenu.copy())));
        return instance;
    }
}

class Menu {
    constructor(parentElementSelector) {
        this.actions = [];
        this.submenus = [];
        if (typeof parentElementSelector === 'string') {
            this.parentElements = document.querySelectorAll(parentElementSelector);
        }
        else if (Array.isArray(parentElementSelector)) {
            this.parentElements = parentElementSelector;
        }
        else {
            this.parentElements = [parentElementSelector];
        }
    }

    copy(instance) {
        instance = Helper.nonNull(instance, new Menu([]));

        instance.actions = [];
        for (let i = 0, n = this.actions.length; i < n; i++) {
            instance.actions.push(this.actions[i].copy());
        }

        instance.submenus = [];
        for (let i = 0, n = this.submenus.length; i < n; i++) {
            instance.submenus.push(this.submenus[i].copy());
        }

        return instance;
    }

    addAction(action) {
        if (Helper.includesNot(this.actions, action)) {
            this.actions.push(action);
            this.redraw();
            action.setMenu(this);
            if (action instanceof OpenSubmenuAction) {
                this.submenus.push(action.getSubmenu());
            }
        }
    }

    draw() {
        if (Helper.isNotNull(this.parentElements)) {
            this.sortActions();

            let actionElements = [];
            for (let i = 0; i < this.actions.length; i++) {
                let element = this.renderAction(this.actions[i]);
                this.actions[i]._htmlElement = element;
                actionElements.push(element);
            }
            for (let i = 0, n = this.parentElements.length; i < n; i++) {
                this.parentElements[i].removeAllChildren();
                for (let i2 = 0, n2 = actionElements.length; i2 < n2; i2++) {
                    this.parentElements[i].appendChild(Helper.cloneNode(actionElements[i2]));
                }
                this.parentElements[i].onclick = this._getOnClickListener();
            }
        }
    }

    _getOnClickListener() {
        let menu = this;
        return function (event) {
            let _element = event.target;
            if (_element.matches('.action') || _element.matches('.action *')) {
                // while (!_element.matches('.action > a')) {
                //     _element = _element.parentNode;
                // }
                _element = _element.closest(".action");
                let actionId = parseInt(_element.dataset["id"]);
                for (let i = 0, n = menu.actions.length; i < n; i++) {
                    if (menu.actions[i].id === actionId) {
                        if (typeof menu.actions[i].callback === 'function' && menu.actions[i].getActivated()) {
                            menu.actions[i].callback(menu.actions[i], event);
                        }
                        return menu.actions[i];
                    }
                }
                for (let i = 0, n = menu.submenus.length; i < n; i++) {
                    if (menu.submenus[i].click(actionId, event)) {
                        return menu.submenus[i];
                    }
                }
            }
            return null;
        };
    }

    /** @protected */
    renderAction(action) {
        let aElement = document.createElement("a");
        if (typeof action.callback === 'string') {
            aElement.href = action.callback;
        }

        if (Helper.isNotNull(action.getIcon())) {
            let iconElement = document.createElement("img");
            iconElement.src = action.getIcon();
            iconElement.classList.add('action-image');
            if (action.getShouldTranslate()) {
                iconElement.dataset["translationTitle"] = action.title;
            }
            aElement.appendChild(iconElement);
        }
        let title = action.getTitle();
        if (action.getShouldTranslate()) {
            title = Translator.makePersistentTranslation(title);
        }
        else {
            title = document.createTextNode(title);
        }
        aElement.appendChild(title);

        return this.renderLiElement(aElement, action)
    }

    /** @protected */
    renderLiElement(aElement, action) {
        let liElement = document.createElement("li");
        liElement.classList.add('action');
        if (action._liClass.trim() !== "") {
            liElement.classList.add(action._liClass);
        }
        liElement.appendChild(aElement);
        liElement.dataset["id"] = action.id;
        if (Helper.isNotNull(action.getIcon())) {
            liElement.classList.add("img");
        }

        if (!action.getVisible()) {
            liElement.classList.add("hidden");
        }

        if (action instanceof OpenSubmenuAction) {
            action.getSubmenu().draw();
            liElement.appendChild(action.getSubmenu().getParentElement());
            liElement.classList.add("is-dropdown-submenu-parent");
            liElement.classList.add("opens-right");
        }
        return liElement;
    }

    /** @private */
    sortActions() {
        this.actions = this.actions.sort(function (first, second) {
            return first.order - second.order;
        });
    }

    _getElementsForAction(action) {
        let elements = [];
        for (let i = 0; i < this.parentElements.length; i++) {
            let elem = this.parentElements[i].querySelector("[data-id=\"" + action.getId() + "\"]");
            Helper.isNull(elem) || elements.push(elem);
        }
        return elements
    }

    updateAction(action) {
        let oldElements = this._getElementsForAction(action);
        if (oldElements.length === 0) {
            return;
        }

        let element = this.renderAction(action);
        action._htmlElement = element;

        for (let i = 0; i < oldElements.length; i++) {
            oldElements[i].replaceWith(Helper.cloneNode(element));
        }
    }

    removeAction(action) {
        let index = this.actions.indexOf(action);
        if (index > 0) {
            this.actions.splice(index, 1);
            let oldElements = this._getElementsForAction(action);
            for (let i = 0, n = oldElements.length; i < n; i++) {
                oldElements[i].remove();
            }
            if (action instanceof OpenSubmenuAction) {
                let index = this.submenus.indexOf(action.getSubmenu());
                this.submenus.splice(index, 1);
            }
        }
    }

    redraw() {
        this.draw();
    }
}

Menu.SHOW_ALWAYS = "always";
Menu.SHOW_FOR_MEDIUM = "medium";
Menu.SHOW_FOR_SMEDIUM = "smedium";
Menu.SHOW_FOR_LARGE = "large";
Menu.SHOW_NEVER = "never";

class Submenu extends Menu
{
    constructor()
    {
        let menuElement = document.createElement("ul");
        menuElement.classList.add("menu");
        menuElement.classList.add("vertical");
        menuElement.classList.add("submenu");
        menuElement.classList.add("is-dropdown-submenu");
        menuElement.classList.add("first-sub");
        super(menuElement);

        this.parentAction = null;
        this.isOpen = false;
    }

    

    copy(instance)
    {
        instance = super.copy(Helper.nonNull(instance, new Submenu()));
        instance.parentElements = [];
        for (let i = 0, n = this.parentElements.length; i < n; i++) {
            instance.parentElements.push(Helper.cloneNode(this.parentElements[i]));
        }
        instance.parentAction = this.parentAction;
        instance.isOpen = this.isOpen;
        return instance;
    }

    setParentAction(action)
    {
        this.parentAction = action;
    }

    draw()
    {
        super.draw();
        if (Helper.isNotNull(this.parentElements))
        {
            let self = this;
            for (let i = 0; i < this.parentElements.length; i++) {
                let closeListener = document.createElement("div");
                closeListener.classList.add("close-listener");
                closeListener.onclick = function(e){
                    console.log(e);
                    self.close();
                };
                this.parentElements[i].insertBefore(closeListener, this.parentElements[i].firstElementChild);
            }
        }
    }

    getParentElement()
    {
        return this.parentElements[0];
    }

    _getOnClickListener()
    {
        return function () {};
    }

    click(actionId, event)
    {
        for (let i = 0, n = this.actions.length; i < n; i++) {
            if (this.actions[i].id === actionId)
            {
                if (typeof this.actions[i].callback === 'function' && this.actions[i].getActivated()) {
                    this.actions[i].callback(this.actions[i], event);
                }
                this.close();
                return true;
            }
        }
        return false;
    }

    toggle()
    {
        if (this.isOpen)
        {
            this.close();
        }
        else
        {
            this.open();
        }
    }

    open()
    {
        this.isOpen = true;
        for (let i = 0, n = this.parentElements.length; i < n; i++) {
            this.parentElements[i].classList.add("js-dropdown-active");
        }
        if (Helper.isNotNull(this.parentAction))
        {
            this.parentAction.redraw();
        }
    }
    close()
    {
        this.isOpen = false;
        for (let i = 0, n = this.parentElements.length; i < n; i++) {
            this.parentElements[i].classList.remove("js-dropdown-active");
        }
        if (Helper.isNotNull(this.parentAction))
        {
            this.parentAction.redraw();
        }
    }
}

class TranslatorDB {
    constructor() {
        this._indexedDB = indexedDB || mozIndexedDB || webkitIndexedDB || msIndexedDB;
        this._version = 3;

        let self = this;
        this._dbPromise = new Promise(function (resolve, reject) {
            let request = self._indexedDB.open("Translator", self._version);
            request.onupgradeneeded = function (event) {
                let db = event.target.result;
                self._upgradeDb(db);
            };
            request.onsuccess = function (event) {
                let db = event.target.result;
                resolve(db);
            };
            request.onerror = function (event) {
                reject(event);
            };
        }).catch(function(e){
            console.error(e);
        });
    }

    _upgradeDb(db) {
        try {
            db.deleteObjectStore("currentLang");
            db.deleteObjectStore("translations");
        }
        catch (e) {
            console.warn(e);
        }
        let currentLangObjectStore = db.createObjectStore("currentLang", {"keyPath": "id"});
        let translationsObjectStore = db.createObjectStore("translations", {"keyPath": ["lang","key"]});
        translationsObjectStore.createIndex("lang", "lang", {"unique": false});
    }

    setLanguage(lang) {
        this._dbPromise.then(function (db) {
            let transaction = TranslatorDB._openTransaction(["currentLang"], "readwrite", db);
            let currentLangObjectStore = transaction.objectStore("currentLang");
            currentLangObjectStore.put({"id": 1, "lang": lang});
        }).catch(function(e){
            console.error(e);
        });
    }

    saveTranslationsForLang(lang, translations) {
        return this._dbPromise.then(function (db) {
            return new Promise(function (resolve) {
                let transaction = TranslatorDB._openTransaction(["translations"], "readwrite", db);
                let translationsObjectStore = transaction.objectStore("translations");
                for (let k in translations) {
                    translationsObjectStore.put({"lang": lang, "key": k, "translation": translations[k]});
                }
                transaction.oncomplete = function () {
                    resolve();
                };
            });
        }).catch(function(e){
            // console.error(e);
        });
    }

    loadTranslationsForLang(lang) {
        return this._dbPromise.then(function (db) {
            return new Promise(function (resolve) {
                let transaction = TranslatorDB._openTransaction(["translations"], "readonly", db);
                let translationsObjectStore = transaction.objectStore("translations");
                let index = translationsObjectStore.index("lang");
                let request = index.openCursor(IDBKeyRange.only(lang));

                let translations = {};
                request.onsuccess = function (e) {
                    let cursor = e.target.result;
                    if (cursor) {

                        let translation = cursor.value;
                        translations[translation["key"]] = translation["translation"];
                        cursor.continue();
                    }
                };
                transaction.oncomplete = function(){
                    resolve(translations);
                };
            });
        }).catch(function(e){
            console.error(e);
            return {};
        });
    }

    getLanguage() {
        return this._dbPromise.then(function (db) {
            return new Promise(function (resolve) {
                let transaction = TranslatorDB._openTransaction(["currentLang"], "readonly", db);
                let currentLangObjectStore = transaction.objectStore("currentLang");
                let req = currentLangObjectStore.get(1);
                req.onsuccess = function (e) {
                    let data = e.currentTarget.result;
                    if (data)
                    {
                        resolve(data["lang"]);
                    }
                    else
                    {
                        resolve(null);
                    }
                };
                req.onerror = function (e) {
                    resolve(null);
                };
            });
        }).catch(function(e){
            // console.error(e);
        });
    }

    static _openTransaction(name, transactionMode, db) {
        let transaction = null;
        try {
            transaction = db.transaction(name, transactionMode);
        }
        catch (e) {
            console.warn(e);
            transaction = db.transaction(name);
        }
        return transaction;
    }
}

class Translator {
    constructor() {
        this._translations = [];
        this._db = new TranslatorDB();
        this._currentLanguage = null;
        this._supportedLanguages = Translator.supportedLanguages;
        this._baseLanguage = Translator.baseLanguage;
        this._languageBasePath = Translator.languageBasePath;
        this._markUntranslatedTranslations = Translator.markUntranslatedTranslations;
        this._markTranslations = Translator.markTranslations;

        let self = this;
        this._initPromise = this.loadBaseLanguage().then(function () {
            return self.loadUserLanguage();
        });
    }

    _loadLanguage(language) {
        let self = this;
        return fetch(Helper.basePath(this._languageBasePath + language + ".json")).then(function (result) {
            return result.json();
        }).then(function (res) {
            self._translations[language] = Object.assign(res, self._translations[language]);
            self._db.saveTranslationsForLang(language, self._translations[language]);
        }).catch(function (err) {
            console.error("could not load lang " + language + " because of error: ", err);
        });
    }

    loadBaseLanguage() {
        let self = this;
        return this._loadLanguage(this._baseLanguage).then(function () {
            self._currentLanguage = self._baseLanguage;
            if (typeof document !== 'undefined') {
                document.getElementsByTagName("html")[0].setAttribute("lang", self._baseLanguage);
            }
        });
    };

    static setLanguage(language) {
        let instance = Translator.getInstance();
        if (instance) {
            return instance.setLanguage(language);
        }
    }

    setLanguage(language) {
        if (this._currentLanguage === language) {
            this.updateTranslations();
            return Promise.resolve();
        }

        if (this._supportedLanguages.indexOf(language) === -1) {
            return Promise.resolve();
        }

        this._currentLanguage = language;
        if (typeof localStorage !== 'undefined') {
            localStorage.setItem("language", language);
        }
        this._db.setLanguage(language);

        let self = this;
        return this._loadLanguage(language).then(function () {
            if (typeof document !== 'undefined') {
                document.getElementsByTagName("html")[0].setAttribute("lang", language);
            }
            self.updateTranslations();
        });
    }

    static translate(key, args) {
        let instance = Translator.getInstance();
        if (instance) {
            return instance.translate(key, args);
        }
        return "";
    }

    translate(key, args) {
        if (typeof key === 'object' && Helper.isNotNull(key)) {
            key = this.addDynamicTranslation(key);
        }

        let translation = null;
        if (Helper.isNotNull(this._translations[this._currentLanguage]) && Helper.isNotNull(this._translations[this._currentLanguage][key])) {
            translation = this._translations[this._currentLanguage][key];
        }

        if (Helper.isNull(translation)) {
            if (Translator.logMissingTranslations) {
                console.warn("missing translation for language " + this._currentLanguage + " and key " + key);
            }
            if (Helper.isNotNull(this._translations[this._baseLanguage])) {
                translation = this._translations[this._baseLanguage][key];
            }

            if (Helper.isNull(translation)) {
                if (Translator.logMissingTranslations) {
                    console.error("missing base translation for key " + key + ". FIX IT");
                }
                translation = key;
            }
            if (this._markUntranslatedTranslations) {
                translation = "&gt;&gt;" + translation + "&lt;&lt;";
            }
        }

        if (this._markTranslations) {
            translation = "$" + translation + "$";
        }

        if (args !== undefined) {
            translation = translation.format(args);
        }

        return translation;
    }

    static addDynamicTranslation(trans) {
        let instance = Translator.getInstance();
        if (instance) {
            return instance.addDynamicTranslation(trans);
        }
    }

    addDynamicTranslation(trans) {
        let key = trans["key"];
        delete trans["key"];
        for (let lang in trans) {
            if (trans.hasOwnProperty(lang)) {
                if (Helper.isNull(this._translations[lang])) {
                    this._translations[lang] = {};
                }
                this._translations[lang][key] = trans[lang];
            }
        }
        return key;
    }

    updateTranslations() {
        if (typeof document !== 'undefined') {
            let elements = document.querySelectorAll("[data-translation]");
            for (let i = 0, max = elements.length; i < max; i++) {
                if (elements[i].dataset["translation"] != "") {
                    try {
                        elements[i].innerHTML = this.translate(elements[i].dataset["translation"], (elements[i].dataset["translationArgs"] !== undefined) ? JSON.parse(elements[i].dataset["translationArgs"]) : undefined);
                    }
                    catch (err) {
                        console.error("wrong configured translation: " + err);
                    }
                }
                for (let k in elements[i].dataset) {
                    if (k.startsWith("translation") && !k.endsWith("Args")) {
                        try {
                            elements[i][k.substr(11).toLowerCase()] = this.translate(elements[i].dataset[k], (elements[i].dataset[k + "Args"] !== undefined) ? JSON.parse(elements[i].dataset[k + "Args"]) : undefined);
                        }
                        catch (err) {
                            console.error("wrong configured translation: " + err);
                        }
                    }
                }
            }
        }
    }

    loadUserLanguage() {
        let userLanguage = localStorage.getItem("language");
        if (Helper.isNull(userLanguage) || this._supportedLanguages.indexOf(userLanguage) === -1) {
            let userLanguages = [];
            if (Helper.isNotNull(navigator.languages)) {
                userLanguages = navigator.languages.slice(0); //.slice(0) klont das Array. Behebt einen Bug in Firefox
            }

            if (navigator.language !== undefined) {
                userLanguages.push(navigator.language);
            }
            //sicherstellen, dass überhaupt eine Sprache gefunden wird
            userLanguages.push(this._baseLanguage);

            if (userLanguages !== undefined) {
                for (let i = 0, numLanguages = userLanguages.length; i < numLanguages; i++) {
                    if (this._supportedLanguages.indexOf(userLanguages[i]) !== -1) {
                        userLanguage = userLanguages[i];
                        break;
                    }
                }
            }
        }
        return this.setLanguage(userLanguage.toLowerCase())
    }

    static makePersistentTranslation(key, args, tag) {
        tag = Helper.nonNull(tag, "span");
        if (typeof key === 'object') {
            key = Translator.addDynamicTranslation(key);
        }

        if (typeof document !== 'undefined') {
            let htmlElem = document.createElement(tag);
            htmlElem.dataset["translation"] = key;
            if (args !== undefined) {
                htmlElem.dataset["translationArgs"] = JSON.stringify(args);
            }
            htmlElem.innerHTML = Translator.translate(key, args);
            return htmlElem;
        }
    }

    static generateChangeLanguageMenuAction() {
        let submenu = new Submenu();
        submenu.addAction(new MenuAction("en", function () {
            Translator.getInstance().setLanguage("en");
        }));
        submenu.addAction(new MenuAction("de", function () {
            Translator.getInstance().setLanguage("de");
        }));
        return new OpenSubmenuAction("current-lang", submenu, Menu.SHOW_ALWAYS)
    }

    static init() {
        Translator.instance = new Translator();
        // Translator.loadBaseLanguage().then(function () {
        //     Translator.loadUserLanguage();
        // });
    }

    static getInstance() {
        return Translator.instance;
    }
}
Translator.logMissingTranslations = false;

Translator.instance = null;

Translator.baseLanguage = "en";
Translator.supportedLanguages = [
    "de",
    "en"
];
Translator.markUntranslatedTranslations = true;
Translator.markTranslations = false;
Translator.languageBasePath = "js/lang/";
Translator.currentLanguage = null;
Translator.translations = {};


AndroidBridge.addDefinition("Translator.setLanguage", Translator.setLanguage);

class Helper {
    static init() {
        Helper.heightMmToPxFactor = null;
        Helper.widthMmToPxFactor = null;
    }

    static includesNot(array, value, fromIndex) {
        return -1 === array.indexOf(value, fromIndex);
    }

    static includes(array, value, fromIndex) {
        return !Helper.includesNot(array, value, fromIndex);
    }

    static isSet() {
        if (arguments.length > 0) {
            const object = arguments[0];
            let keys = Array.prototype.slice.call(arguments, 1);
            return (Helper.isNotNull(object) && (keys.length === 0 || Helper.isSet.apply(null, [object[keys[0]]].concat(keys.slice(1)))));
        }
        return false;
    }

    static isNull(variable) {
        return (variable === null || variable === undefined);
    }

    static isNotNull(variable) {
        return !Helper.isNull(variable);
    }

    static nonNull(val1, val2) {
        for (let i = 0; i < arguments.length; i++) {
            if (Helper.isNotNull(arguments[i])) {
                return arguments[i];
            }
        }
        return null;
    }

    static notEmpty(value) {
        return !Helper.empty(value);
    }

    static buildQuery(values) {
        let queryStrings = [];
        for (let k in values) {
            queryStrings.push(encodeURIComponent(k) + "=" + encodeURIComponent(values[k]));
        }
        return "?" + queryStrings.join("&");
    }

    static empty(value) {
        return (Helper.isNull(value) || (typeof value === 'string' && value.trim() === ""))
    }

    static inflateElementsFromString(string) {
        let template = document.createElement('template');
        template.innerHTML = string;
        return template.content.childNodes;
    }

    static createLoadingSymbol() {
        let svgNS = "http://www.w3.org/2000/svg";

        let loader = document.createElement("div");
        loader.className = 'loader';

        let svg = document.createElementNS(svgNS, "svg");
        svg.setAttribute('viewBox', "0 0 32 32");
        svg.setAttribute("widh", "32");
        svg.setAttribute("height", "32");

        let circle = document.createElementNS(svgNS, "circle");
        circle.setAttribute("id", "spinner");
        circle.setAttribute("cx", "16");
        circle.setAttribute("cy", "16");
        circle.setAttribute("r", "14");
        circle.setAttribute("fill", "none");

        svg.appendChild(circle);
        loader.appendChild(svg);

        return loader;
    }

    static basePath(url) {
        return SystemSettings.getBasePath() + url;
    }

    static isMobileApple() {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    }

    static isMobile() {
        return (navigator.userAgent.match(/Android|BlackBerry|Opera Mini|IEMobile/i) !== null || Helper.isMobileApple() || (typeof window.orientation !== "undefined" || window.orientation === false || window.orientation === null));
    }

    static select(e) {
        let range = document.createRange();
        range.selectNodeContents(e);
        let sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }

    static format(number, leadingZeros) {
        number = "" + number;
        while (number.length < leadingZeros) {
            number = "0" + number;
        }
        return number;
    }

    static cloneNode(srcNode) {
        let destNode = srcNode.cloneNode(true);
        destNode.onclick = srcNode.onclick;
        return destNode;
    }

    static encodeToBase(stringToEncode, base) {
        let encodedString = "";
        let charlength = Math.floor(Math.log(265) / Math.log(base));
        for (let i = 0; i <  stringToEncode.length; i++) {
            let value = stringToEncode.charCodeAt(i).toString(base);
            let joinLength = value.length % charlength;
            if (joinLength > 0) {
                let joinArray = new Array(charlength + 1 - (joinLength)); //+1, da join nur zwischen elemente einfügt
                value = joinArray.join("0") + value;
            }
            encodedString += value;
        }
        return encodedString;
    }

    static decodeToBase(stringToDecode, base) {
        let charlength = Math.floor(Math.log(265) / Math.log(base));
        let values = stringToDecode.match(new RegExp(".{1," + charlength + "}", "g")) || [];
        let encodedString = "";
        for (let i = 0, n = values.length; i < n; i++) {
            encodedString += String.fromCharCode(parseInt(values[i], base));
        }
        return encodedString;
    }

    static toggleVisibility(elem) {
        if (elem.style.display === "none") {
            elem.style.display = "";
            return true;
        }
        else {
            elem.style.display = "none";
            return false;
        }
    }

    static print(content) {
        let printContent = document.getElementById("print-content");
        if (content instanceof Element) {
            printContent.removeAllChildren();
            printContent.appendChild(content);
        }
        else {
            printContent.innerHTML = content;
        }
        window.print();
    }

    static strftime(sFormat, date, useUTC) {
        if (!(date instanceof Date)) date = new Date(date);
        useUTC = Helper.nonNull(useUTC, false);
        let nDay = (useUTC) ? date.getUTCDay() : date.getDay(),
            nDate = (useUTC) ? date.getUTCDate() : date.getDate(),
            nMonth = (useUTC) ? date.getUTCMonth() : date.getMonth(),
            nYear = (useUTC) ? date.getUTCFullYear() : date.getFullYear(),
            nHour = (useUTC) ? date.getUTCHours() : date.getHours(),
            aDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
            aMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
            aDayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
            isLeapYear = function () {
                if ((nYear & 3) !== 0) return false;
                return nYear % 100 !== 0 || nYear % 400 === 0;
            },
            getThursday = function () {
                let target = new Date(date);
                target.setDate(nDate - ((nDay + 6) % 7) + 3);
                return target;
            },
            zeroPad = function (nNum, nPad) {
                return ('' + (Math.pow(10, nPad) + nNum)).slice(1);
            };

        return sFormat.replace(/%[a-z]/gi, function (sMatch) {
            return {
                '%a': Translator.makePersistentTranslation(aDays[nDay].slice(0, 3)).outerHTML,
                '%A': Translator.makePersistentTranslation(aDays[nDay]).outerHTML,
                '%b': Translator.makePersistentTranslation(aMonths[nMonth].slice(0, 3)).outerHTML,
                '%B': Translator.makePersistentTranslation(aMonths[nMonth]).outerHTML,
                '%c': date.toUTCString(),
                '%C': Math.floor(nYear / 100),
                '%d': zeroPad(nDate, 2),
                '%e': nDate,
                '%f': zeroPad(date.getTime() % 1000, 4),
                '%F': date.toISOString().slice(0, 10),
                '%G': getThursday().getFullYear(),
                '%g': ('' + getThursday().getFullYear()).slice(2),
                '%H': zeroPad(nHour, 2),
                '%I': zeroPad((nHour + 11) % 12 + 1, 2),
                '%j': zeroPad(aDayCount[nMonth] + nDate + ((nMonth > 1 && isLeapYear()) ? 1 : 0), 3),
                '%k': '' + nHour,
                '%l': (nHour + 11) % 12 + 1,
                '%m': zeroPad(nMonth + 1, 2),
                '%M': zeroPad(date.getMinutes(), 2),
                '%p': (nHour < 12) ? 'AM' : 'PM',
                '%P': (nHour < 12) ? 'am' : 'pm',
                '%s': Math.round(date.getTime() / 1000),
                '%S': zeroPad(date.getSeconds(), 2),
                '%u': nDay || 7,
                '%V': (function () {
                    let target = getThursday(),
                        n1stThu = target.valueOf();
                    target.setMonth(0, 1);
                    let nJan1 = target.getDay();
                    if (nJan1 !== 4) target.setMonth(0, 1 + ((4 - nJan1) + 7) % 7);
                    return zeroPad(1 + Math.ceil((n1stThu - target) / 604800000), 2);
                })(),
                '%w': '' + nDay,
                '%x': date.toLocaleDateString(),
                '%X': date.toLocaleTimeString(),
                '%y': ('' + nYear).slice(2),
                '%Y': nYear,
                '%z': date.toTimeString().replace(/.+GMT([+-]\d+).+/, '$1'),
                '%Z': date.toTimeString().replace(/.+\((.+?)\)$/, '$1')
            }[sMatch] || sMatch;
        });
    }

    static cloneJson(obj) {
        // https://stackoverflow.com/questions/4120475/how-to-create-and-clone-a-json-object/17502990#17502990
        let i;

        // basic type deep copy
        if (Helper.isNull(obj) || typeof obj !== 'object') {
            return obj
        }
        // array deep copy
        if (obj instanceof Array) {
            let cloneA = [];
            for (i = 0; i < obj.length; ++i) {
                cloneA[i] = Helper.cloneJson(obj[i]);
            }
            return cloneA;
        }
        if (obj instanceof Date)
        {
            return new Date(obj.getTime());
        }
        // object deep copy
        let cloneO = {};
        for (i in obj) {
            cloneO[i] = Helper.cloneJson(obj[i]);
        }
        return cloneO;
    }

    static htmlspecialcharsDecode(text) {
        const map = {
            '&amp;': '&',
            '&#038;': "&",
            '&lt;': '<',
            '&gt;': '>',
            '&quot;': '"',
            '&#039;': "'",
            '&#8217;': "’",
            '&#8216;': "‘",
            '&#8211;': "–",
            '&#8212;': "—",
            '&#8230;': "…",
            '&#8221;': '”'
        };

        if (Helper.isNotNull(text) && typeof text.replace === "function") {
            return text.replace(/\&[\w\d\#]{2,5}\;/g, function (m) {
                return map[m];
            });
        }
        return text;
    }

    static formDataFromObject(obj) {
        let formData = new FormData();
        for (let k in obj) {
            formData.set(k, obj[k]);
        }
        return formData;
    }

    static scaleContentRecursive(element, content) {

        let elementStyle = window.getComputedStyle(element);
        let contentStyle = window.getComputedStyle(content);

        if (contentStyle.height > elementStyle.height || contentStyle.width > elementStyle.width) {
            return Helper.scaleDownContentRecursive(element, content);
        }
    }

    static scaleDownContentRecursive(element, content) {
        Helper.convertChildrenToRelativeRecursive(element);

        let elementStyle = window.getComputedStyle(element);
        let contentStyle = window.getComputedStyle(content);

        let runs = 0;
        let fontSize = parseFloat(contentStyle.getPropertyValue("font-size"));
        let width = contentStyle.width;
        let height = contentStyle.height;
        while (contentStyle.height > elementStyle.height || contentStyle.width > elementStyle.width) {
            fontSize *= 0.95;

            if (height > elementStyle.height) {
                height *= 0.95;
            }
            if (width > contentStyle.width) {
                width *= 0.95;
            }
            content.style["font-size"] = fontSize + "px";
            content.style["max-height"] = height + "px";
            content.style["max-width"] = width + "px";

            runs++;
            if (runs > 2000) {
                console.log("breaked");
                break;
            }
        }
        Helper.convertToRelative(content);

        contentStyle = window.getComputedStyle(content);
        content.style["font-size"] = (parseFloat(contentStyle.getPropertyValue("font-size")) / parseFloat(document.documentElement.clientHeight) * 100) + "vh";
    }

    static convertChildrenToRelativeRecursive(element) {
        let children = element.childNodes;
        for (let i = 0, n = children.length; i < n; i++) {
            if (children[i] instanceof Element) {
                Helper.convertToRelative(children[i]);
                Helper.convertChildrenToRelativeRecursive(children[i]);
            }
        }
    }

    static convertToRelative(element) {
        let hasTransitionClass = (element.classList.contains("no-transtition"));

        element.classList.add("no-transition");

        let parent = element.parentNode;

        console.log(element);
        let elementStyle = window.getComputedStyle(element);
        let parentStyle = window.getComputedStyle(parent);

        let fontSize = parseFloat(elementStyle.getPropertyValue("font-size")) / parseFloat(parentStyle.getPropertyValue("font-size"));

        let maxHeight = elementStyle.height;
        let maxWidth = elementStyle.width;

        let pHeight = parentStyle.height;
        let pWidth = parentStyle.width;

        let relativeAttributes = element.style;
        relativeAttributes['max-height'] = Math.floor(maxHeight / pHeight * 100) + "%";
        relativeAttributes['margin-left'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-left')) / pWidth * 100) + "%";
        relativeAttributes['margin-right'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-right')) / pWidth * 100) + "%";
        relativeAttributes['margin-top'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-top')) / pHeight * 100) + "%";
        relativeAttributes['margin-bottom'] = Math.floor(parseFloat(elementStyle.getPropertyValue('margin-bottom')) / pHeight * 100) + "%";
        relativeAttributes['max-width'] = Math.floor(maxWidth / pWidth * 100) + "%";
        relativeAttributes["font-size"] = fontSize + "em";
        // console.log(relativeAttributes);
        // element.css(relativeAttributes);

        if (!hasTransitionClass) {
            element.classList.remove("no-transition");
        }
    }

    static isChrome() {
        let isChromium = window.chrome,
            winNav = window.navigator,
            vendorName = winNav.vendor,
            isOpera = winNav.userAgent.indexOf("OPR") > -1,
            isIEedge = winNav.userAgent.indexOf("Edge") > -1,
            isIOSChrome = winNav.userAgent.match("CriOS");

        if (isIOSChrome) {
            return true;
        } else {
            return isChromium !== null &&
                typeof isChromium !== "undefined" &&
                vendorName === "Google Inc." &&
                isOpera === false &&
                isIEedge === false;
        }
    }

    static getIndexedObject(array, keyValue) {
        let obj = {};
        for (let i = 0, n = array.length; i < n; i++) {
            obj[array[i][keyValue]] = array[i];
        }
        return obj;
    }

    static invertKeyValues(obj) {
        let new_obj = {};

        for (let prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                new_obj[obj[prop]] = prop;
            }
        }

        return new_obj;
    }

    static toArray(object) {
        let res = [];
        for (let k in object) {
            res.push(object[k]);
        }
        return res;
    }
}
Helper.init();

class SystemSettings {
    static setBasePath(basePath) {
        SystemSettings._basePath = basePath;
    }

    static getBasePath() {
        return SystemSettings._basePath;
    }

    static set(key, value) {
        SystemSettings._settings[key] = value;
    }

    static get(key, defaultValue) {
        return Helper.nonNull(SystemSettings._settings[key], defaultValue);
    }

    static has(key){
        return Helper.nonNull(SystemSettings._settings[key]);
    }
}

SystemSettings.setBasePath("/");
SystemSettings._settings = {};

class ThemeManager {
    static init() {
        ThemeManager.loadCurrentTheme();
    }

    static changeCurrentTheme(newTheme) {
        let theme = null;
        if (typeof newTheme === 'string') {
            let themes = ThemeManager.themes.filter(function (theme) {
                return theme._name === newTheme;
            });
            if (themes.length > 0) {
                theme = themes[0];
            }
        }
        else if (ThemeManager.themes.indexOf(newTheme) !== -1) {
            theme = newTheme;
        }
        if (Helper.isNotNull(theme)) {
            localStorage.setItem("currentTheme", theme._name);
            let themePromise = new Promise(function (resolve) {
                document.querySelector("nav.top-bar").addEventListener("transitionend", function(){
                    resolve();
                });
            });
            document.body.className = theme._className;
            ThemeManager.currentTheme = theme;
            for (let i = 0, n = ThemeManager.changeListeners.length; i < n; i++) {
                ThemeManager.changeListeners[i](ThemeManager.currentTheme, themePromise);
            }
        }
    }

    static addTheme(theme) {
        ThemeManager.themes.push(theme);
    }

    static loadCurrentTheme() {
        ThemeManager.changeCurrentTheme(localStorage.getItem("currentTheme"));
        if (Helper.isNull(ThemeManager.currentTheme)) {
            let className = document.body.className;
            let themes = ThemeManager.themes.filter(function (theme) {
                return theme._className === className;
            });
            if (themes.length > 0) {
                ThemeManager.changeCurrentTheme(themes[0]);
            }
            else if (ThemeManager.themes.length > 0) {
                ThemeManager.changeCurrentTheme(ThemeManager.themes[0]);
            }
        }
    }

    static generateChangeThemeMenuAction() {
        return new MenuAction(ThemeManager.currentTheme._name, function (action) {
            let currentThemeIndex = ThemeManager.themes.indexOf(ThemeManager.currentTheme);
            let nextIndex = (currentThemeIndex + 1) % ThemeManager.themes.length;
            ThemeManager.changeCurrentTheme(ThemeManager.themes[nextIndex]);
            action.title = ThemeManager.currentTheme._name;
            action._menu.redraw();
        }, Menu.SHOW_ALWAYS)
    }

    static addChangeListener(listener) {
        ThemeManager.changeListeners.push(listener);
    }

    static getCurrentTheme(){
        return ThemeManager.currentTheme;
    }
}

ThemeManager.currentTheme = null;
ThemeManager.themes = [];
ThemeManager.changeListeners = [];

AndroidBridge.addDefinition("ThemeManager", {
    "addChangeListener": ThemeManager.addChangeListener,
    "getCurrentTheme": ThemeManager.getCurrentTheme,
});

class CookieCompliance {

    static async showIfNeeded(cookieContainer) {
        let cookieCompliance = new CookieCompliance(cookieContainer);
        return cookieCompliance.showIfNeeded();
    }

    constructor(cookieContainerId) {
        this.cookieContainerId = cookieContainerId;
        this.dropCookie = true;
        this.cookieDuration = 365 * 10;
        this.cookieName = 'complianceCookie';
        this.cookieValue = 'true';
    }

    async showIfNeeded() {
        if (CookieCompliance.checkCookie(this.cookieName) !== this.cookieValue) {
            return this.show();
        }
        return Promise.resolve();
    }

    removeMe() {
        this.createCookie(this.cookieName, this.cookieValue, this.cookieDuration);
    }

    createCookie(name, value, days) {
        let expires;
        if (Helper.isNotNull(days)) {
            const date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toGMTString();
        }
        else {
            expires = "";
        }
        if (this.dropCookie) {
            document.cookie = name + "=" + value + expires + "; path=/";
        }
    }

    eraseCookie(name) {
        this.createCookie(name, "", -1);
    }

    static checkCookie(name) {
        const nameEQ = name + "=";
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            let c = cookies[i];
            while (c.charAt(0) === ' ') {
                c = c.substring(1, c.length);
            }
            if (c.indexOf(nameEQ) === 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }
        return null;
    }

    show() {
        let cookieCompliance = this;
        const cookieMessage = document.getElementById(this.cookieContainerId);
        cookieMessage.style.display = 'block';
        return new Promise(r => {
            cookieMessage.querySelector("#close-cookie-msg").onclick = function () {
                cookieCompliance.removeMe();
                cookieMessage.remove();
                r();
            };
        });
    }
}

class ActionBarMenu extends Menu {
    static init() {
        function parseStyleToObject(str) {
            let styleObject = {};

            if (typeof str !== 'string') {
                return styleObject;
            }

            str = str.trim().slice(1, -1); // browsers re-quote string style values

            if (!str) {
                return styleObject;
            }

            styleObject = str.split('&').reduce(function (ret, param) {
                const parts = param.replace(/\+/g, ' ').split('=');
                let key = parts[0];
                let val = parts[1];
                key = decodeURIComponent(key);

                // missing `=` should be `null`:
                // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
                val = val === undefined ? null : decodeURIComponent(val);

                if (!ret.hasOwnProperty(key)) {
                    ret[key] = val;
                } else if (Array.isArray(ret[key])) {
                    ret[key].push(val);
                } else {
                    ret[key] = [ret[key], val];
                }
                return ret;
            }, {});

            return styleObject;
        }

        let cssStyle = document.getElementsByClassName('foundation-mq');
        if (cssStyle.length === 0) {
            return;
        }
        let queries = [];
        cssStyle = parseStyleToObject(window.getComputedStyle(cssStyle[0]).getPropertyValue('font-family'));
        for (let key in cssStyle) {
            if (cssStyle.hasOwnProperty(key)) {
                queries.push({
                    _name: key,
                    value: 'only screen and (min-width: ' + cssStyle[key] + ')'
                });
            }
        }

        window.addEventListener('resize', function () {
            if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
                ActionBarMenu.currentMenu.updateToggleButton();
            }
        });
        let responsiveMenu = document.getElementById("responsive-menu");
        document.getElementById("responsive-menu-toggle").onclick = function () {
            if (window.getComputedStyle(responsiveMenu).getPropertyValue('display') === 'none') {
                responsiveMenu.style.display = 'block';
            }
            else if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
                ActionBarMenu.currentMenu.close();

            }
        };
        responsiveMenu.firstElementChild.addEventListener("click", function (e) {
                if (e.target === responsiveMenu.firstElementChild && Helper.isNotNull(ActionBarMenu.currentMenu)) {
                    ActionBarMenu.currentMenu.close();
                }
            }
        );
        ActionBarMenu.queries = queries;
    }

    static _getCurrentSize() {
        let matched;

        for (let i = 0; i < ActionBarMenu.queries.length; i++) {
            let query = ActionBarMenu.queries[i];

            if (matchMedia(query.value).matches) {
                matched = query;
            }
        }

        if (typeof matched === 'object') {
            return matched._name;
        } else {
            return matched;
        }
    }

    static filterVisibleElements(elements) {
        let visibleElements = [];
        for (let i = 0, n = elements.length; i < n; i++) {
            if (!elements[i].classList.contains("hidden")) {
                visibleElements.push(elements[i]);
            }
        }
        return visibleElements;
    }

    renderLiElement(aElement, action) {
        let liElement = super.renderLiElement(aElement, action);
        liElement.classList.add(action.getShowFor());
        return liElement;
    }

    updateToggleButton() {
        let size = ActionBarMenu._getCurrentSize();
        let firstParentElement = this.parentElements[0];
        if ((size === "medium" || size === "smedium" || size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_LARGE)).length > 0 ||
            (size === "smedium" || size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_MEDIUM)).length > 0 ||
            (size === "small") && ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_FOR_SMEDIUM)).length > 0 ||
            ActionBarMenu.filterVisibleElements(firstParentElement.getElementsByClassName(Menu.SHOW_NEVER)).length > 0) {
            document.getElementById("responsive-menu-toggle").style.display = 'block';
        } else {
            document.getElementById("responsive-menu-toggle").style.display = 'none';
            if (Helper.isNotNull(ActionBarMenu.currentMenu)) {
                ActionBarMenu.currentMenu.close();
            }
        }
    }

    _getOnClickListener() {
        let superListener = super._getOnClickListener();
        return function (event) {
            let action = superListener(event);
            if (!(action instanceof OpenSubmenuAction) && Helper.isNotNull(ActionBarMenu.currentMenu)) {
                ActionBarMenu.currentMenu.close();
            }
        }
    }

    draw(parentElement) {
        let returnValue = super.draw(parentElement);
        this.updateToggleButton();

        ActionBarMenu.currentMenu = this;
        return returnValue;
    }

    close() {
        document.getElementById("responsive-menu").style.display = 'none';
        for (let i = 0, n = this.submenus.length; i < n; i++) {
            this.submenus[i].close();
        }
    }

    removeAction(action) {
        let res = super.removeAction(action);
        this.updateToggleButton();
        return res;
    }
}

ActionBarMenu.queries = [];
ActionBarMenu.currentMenu = null;
ActionBarMenu.init();

class ViewInflater {
    static inflate(viewUrl, parentUrls) {
        parentUrls = Helper.nonNull(parentUrls, []).slice(0);

        let resultPromise = Promise.resolve();
        if (viewUrl instanceof Element) {
            resultPromise = Promise.resolve(viewUrl);
        }
        else {
            if (parentUrls.indexOf(viewUrl) !== -1) {
                return Promise.reject("views are in a circuit! cannot resolve view for url " + parentUrls[0] + "! url " + viewUrl + " is in stack before!");
            }
            parentUrls.push(viewUrl);
            resultPromise = fetch(Helper.basePath(viewUrl), {credentials: "same-origin"}).then(function (result) {
                return result.text();
            }).then(function (htmlText) {
                let doc = (new DOMParser()).parseFromString(htmlText, "text/html");
                if (Helper.isNull(doc)) {
                    doc = document.implementation.createHTMLDocument('');
                    doc.body.innerHTML = htmlText;
                }
                return doc.body.firstChild
            });
        }

        return resultPromise.then(function (parentElement) {
            let promises = [];
            let childViews = parentElement.querySelectorAll("[data-view]");

            for (let i = 0, n = childViews.length; i < n; i++) {
                promises.push(ViewInflater.inflate(childViews[i].dataset["view"], parentUrls).then(function (element) {
                    childViews[i].replaceWith(element);
                }));
            }
            return Promise.all(promises).then(function () {
                return parentElement;
            });
        });
    }
}

class Context {
    constructor(view) {
        let self = this;
        this._siteContent = null;
        this.firstStart = true;
        this.inflatePromise = new Promise(function (resolver) {
            self.inflatePromiseResolver = resolver;
        });
        this.fragments = {};

        if (Helper.isNotNull(view)) {
            this.inflateView(view);
        }
    }

    onConstruct() {
        let results = [];
        for (let k in this.fragments) {
            results.push(this.fragments[k].onConstruct.apply(this.fragments[k], arguments));
            results.push(this.fragments[k].inflatePromise);
        }
        return Promise.all(results);
    }

    onStart() {
        if (this.firstStart) {
            this.onFirstStart();
            this.firstStart = false;
        }

        for (let k in this.fragments) {
            let fragment = this.fragments[k];
            fragment.onStart.apply(this.fragments[k], arguments);
            this.fragments[k].inflatePromise.then(function (fragmentView) {
                if (fragment.isActive()) {
                    fragmentView.classList.remove("hidden");
                }
                else {
                    fragmentView.classList.add("hidden");
                }
            });
        }
    }

    onFirstStart() {
        // for (let k in this.fragments) {
        //     this.fragments[k].onFirstStart.apply(this.fragments[k], arguments);
        // }
    }

    onPause() {
        for (let k in this.fragments) {
            this.fragments[k].onPause.apply(this.fragments[k], arguments);
        }
    }

    onDestroy() {
        for (let k in this.fragments) {
            this.fragments[k].onDestroy.apply(this.fragments[k], arguments);
        }
    }

    addFragment(viewQuery, fragment) {
        this.fragments[viewQuery] = fragment;
        this.inflatePromise = this.inflatePromise.then(function (siteContent) {
            return fragment.inflatePromise.then(function (fragmentView) {
                siteContent.querySelector(viewQuery).appendChild(fragmentView);
                return siteContent;
            });
        });
    }

    /** @protected */
    inflateView(link) {
        let self = this;
        this.inflatePromiseResolver(ViewInflater.inflate(link).then(function (siteContent) {
            self._siteContent = siteContent;
            return siteContent;
        }));

        return this.inflatePromise;
    }

    findBy(query, all, asPromise) {
        all = Helper.nonNull(all, false);
        asPromise = Helper.nonNull(asPromise, false);

        let getVal = function (root) {
            let res = null;
            if (all) {
                res = root.querySelectorAll(query);
                if (root.matches(query)) {
                    res.push(root);
                }
            }
            else {
                if (root.matches(query)) {
                    res = root;
                }
                else {
                    res = root.querySelector(query);
                }
            }
            return res;
        };

        if (asPromise) {
            return this.inflatePromise.then(function (rootView) {
                return getVal(rootView);
            });
        }
        return getVal(this._siteContent);
    }
}

class AbstractSite extends Context {
    constructor(siteManager, view, deepLink) {
        super(view);

        this.isVisible = false;
        this.siteManager = siteManager;
        this.isFinishing = false;
        this.actionMenu = null;
        this.url = "";
        this.deepLink = deepLink;
        this.startArgs = {};
        this.title = siteManager.getDefaultTitle();
    }

    setTitle(titleElement, title) {
        if (typeof titleElement === "string") {
            title = titleElement;
            titleElement = document.createTextNode(titleElement);
        }
        this.title = {
            element: titleElement
        };
        this.title["title"] = Helper.nonNull(title, this.title["title"]);

        if (this.isVisible) {
            this.siteManager.updateTitle();
        }
    }

    startStartsite() {
        return this.startSite(this.siteManager.getStartSiteName());
    }

    inflateView(link) {
        let self = this;
        return super.inflateView(link).then(function (res) {
            let promises = [];
            for (let i = 0, n = self.fragments.length; i < n; i++) {
                promises.push(self.fragments[i].inflatePromise);
            }
            return Promise.all(promises).then(function () {
                return res;
            });
        });
    }

    onConstruct(args) {
        this.startArgs = args;
        if (Helper.isNotNull(this.deepLink)) {
            this.setUrlFromParams(args);
        }
        return super.onConstruct(args);
    }

    onStart(args) {
        this.isVisible = true;
        let res = super.onStart(args);
        this.actionMenu.redraw();
        return res;
    }

    onPause(args) {
        super.onPause(args);
        this.isVisible = false;
    }

    finish(result) {
        if (!this.isFinishing) {
            this.isFinishing = true;
            this.siteManager.endSite(this, result);
        }
    }

    startSite(siteName, args) {
        return this.siteManager.startSite(siteName, args);
    }

    toForeground() {
        this.siteManager.toForeground(this);
    }

    finishAndStartNext(siteName, startParams, finishResult) {
        this.startSite(siteName, startParams);
        this.finish(finishResult);
    }

    createActionBarMenu(menu) {
        let defaultActions = this.siteManager.getDefaultActions();
        for (let i = 0, n = defaultActions.length; i < n; i++) {
            menu.addAction(defaultActions[i].copy());
        }
        return menu;
    }

    setUrl(url) {
        this.url = url;
        this.siteManager.updateUrl(this);
    }

    setUrlFromParams(params) {
        this.setUrl(this.deepLink + Helper.buildQuery(params));
    }

    updateUrlParams(params) {
        this.startArgs = Object.assign(this.startArgs, params);
        this.setUrlFromParams(this.startArgs);
    }

    getUrl() {
        return this.url;
    }

    getFullUrl() {
        return Helper.basePath(this.url);
    }

    onBackPressed() {
    }

    addListener(event, selector, listenerFunction) {
        this.siteManager.addListener(this, event, selector, listenerFunction);
    }

    addKeyListener(keycode, listenerFunction)
    {
        this.siteManager.addKeyListener(this, keycode, listenerFunction);
    }

    addKeyAndEventListener(keycode, event, selector, listenerFunction) {
        this.siteManager.addKeyAndEventListener(this, keycode, event, selector, listenerFunction);
    }
}

class SiteContainer {
    constructor(site, finishResolver) {
        this._site = site;
        this._siteContent = null;
        this._pauseParameters = {};
        this._startParameters = {};
        this._finishResolver = finishResolver;
    }
    getSite() {
        return this._site;
    }

    setSite(site) {
        if (site instanceof AbstractSite) {
            this._site = site;
        }
    }

    getSiteContent() {
        return this._siteContent;
    }

    setSiteContent(value) {
        this._siteContent = value;
    }

    getPauseParameters() {
        return this._pauseParameters;
    }

    setPauseParameters(value) {
        this._pauseParameters = value;
    }

    getStartParameters() {
        return this._startParameters;
    }

    setStartParameters(value) {
        this._startParameters = value;
    }

    getFinishResolver() {
        return this._finishResolver;
    }

    setFinishResolver(value) {
        this._finishResolver = value;
    }
}

class SiteManager {
    constructor(siteDivId, actionBarMenuSelector, app) {
        this.siteDiv = document.getElementById(siteDivId);
        this.siteContainerStack = [];
        this.currentSiteContainerToShow = null;
        this.actionBarMenuSelector = Helper.nonNull(actionBarMenuSelector, '.action-bar');

        this.siteStartingPromise = Promise.resolve();
        this.defaultActions = [];
        this.startSiteName = null;
        this.app = app;

        this.titleElement = document.querySelector(".top-bar-title");

        const defaultTitleElem = document.createElement("span");
        while (this.titleElement.childNodes.length > 0) {
            const child = this.titleElement.firstChild;
            child.remove();
            defaultTitleElem.appendChild(child);
        }

        this.defaultTitle = {
            element: defaultTitleElem,
            title: document.title
        };

        let siteManager = this;
        window.onpopstate = function (e) {
            if (siteManager.siteContainerStack.length >= 1) {
                let site = siteManager.siteContainerStack[siteManager.siteContainerStack.length - 1].getSite();
                if (site.onBackPressed() !== false) {
                    siteManager.endSite(site);
                }
            }
        };
    }

    getDefaultTitle() {
        return this.defaultTitle;
    }

    setStartSiteName(startSiteName) {
        this.startSiteName = startSiteName;
    }

    getStartSiteName() {
        return this.startSiteName;
    }

    addDefaultAction(action) {
        this.defaultActions.push(action);
    }

    getDefaultActions() {
        return this.defaultActions;
    }

    getCurrentSite(){
        if (Helper.isNotNull(this.currentSiteContainerToShow)){
            return this.currentSiteContainerToShow.getSite();
        }
        return null;
    }

    async findSite(filter){
        for (let i = this.siteContainerStack.length-1; i >= 0; i--) {
            if (await filter(this.siteContainerStack[i].getSite())){
                return this.siteContainerStack[i].getSite();
            }
        }
        return null;
    }

    async startSite(siteConstructor, paramsPromise) {
        if (!(siteConstructor.prototype instanceof AbstractSite)) {
            throw {
                "error": "wrong class given! Expected AbstractSite, given " + siteConstructor.name
            };
        }

        let site = new siteConstructor(this);
        let resolver = {};
        let finishPromise = new Promise(function (resolve, reject) {
            resolver.resolve = resolve;
            resolver.reject = reject;
        });
        let siteContainer = new SiteContainer(site, resolver);
        this.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());

        this.siteStartingPromise = Promise.resolve(paramsPromise).then(async (params) => {
            siteContainer.setStartParameters(params);
            await Promise.all([site.onConstruct(params), site.inflatePromise]);

            site.actionMenu = site.createActionBarMenu(this.buildActionBarMenu());
            return this.show(siteContainer);
        }).catch((e) => {
            console.error("site start error for site ", siteConstructor.name, e);
        });

        return finishPromise;
    }

    endSite(site, result) {
        let manager = this;
        this.siteStartingPromise.then(function () {
            let index = manager.findContainerIndexBySite(site);
            let container = manager.siteContainerStack.splice(index, 1);
            container = container[0];

            let showSiteContainer = null;
            if (container === manager.currentSiteContainerToShow) {
                manager.currentSiteContainerToShow.getSite().onPause();
                manager.currentSiteContainerToShow = null;
                let newSiteContainerIndex = manager.siteContainerStack.length - 1;
                if (newSiteContainerIndex < 0) {

                    manager.showAppEndedMessage();
                    app.endApp();
                    // manager.startSite(manager.startSiteName);
                    return;
                }
                manager.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());
                showSiteContainer = manager.siteContainerStack[newSiteContainerIndex];
            }
            container.getSite().onDestroy();

            Promise.resolve(result).then(function (resValue) {
                container.getFinishResolver().resolve(resValue);
                if (Helper.isNotNull(showSiteContainer)) {
                    manager.show(showSiteContainer);
                }
            });
        });
    }

    addListener(site, event, _selector, listener) {
        this.siteDiv.addEventListener(event, function (_event) {
            let _element = _event.target;
            if (site.isVisible && _element.matches(_selector)) {
                listener(_element, _event);
            }
        });
    }

    addKeyAndEventListener(site, keycode, event, selector, listener) {
        this.addListener(site, event, selector, listener);
        this.addKeyListener(site, keycode, listener);
    }

    addKeyListener(site, keycode, listener) {
        window.addEventListener("keydown", function (e) {
            if (site.isVisible && e.which === keycode) {
                listener(this, e);
            }
        });
    }

    toForeground(site) {
        let index = this.findContainerIndexBySite(site);
        let container = this.siteContainerStack.splice(index, 1);
        container = container[0];

        this.show(container);
    }

    refreshCurrentSite() {
        return this.show(this.currentSiteContainerToShow);
    }

    /** @private */
    show(siteContainer) {
        if (Helper.isNotNull(this.currentSiteContainerToShow)) {
            this.currentSiteContainerToShow.setPauseParameters(this.currentSiteContainerToShow.getSite().onPause());
            this.currentSiteContainerToShow.setSiteContent(this.siteDiv.innerHTML);
        }
        this.siteDiv.removeAllChildren().appendChild(Helper.createLoadingSymbol());

        let siteManager = this;
        this.currentSiteContainerToShow = siteContainer;
        if (-1 === this.siteContainerStack.indexOf(siteContainer)) {
            this.siteContainerStack.push(siteContainer);
        }

        return siteContainer.getSite().inflatePromise.then(function (data) {
            siteContainer.getSite().actionMenu.redraw();
            siteManager.siteDiv.removeAllChildren().appendChild(data);
            siteManager.updateTitle();
            Translator.getInstance().updateTranslations();
            return data;
        }).then(function (data) {
            siteContainer.getSite().onStart(siteContainer.getPauseParameters());
            history.pushState({
                'siteName': siteContainer.getSite().constructor.name,
                'siteData': data.outerHTML,
                'stackPosition': siteManager.siteContainerStack.length - 1
            }, siteContainer.getSite().constructor.name, siteContainer.getSite().getFullUrl());
        });
    }

    updateUrl(site) {
        if (Helper.isNotNull(this.currentSiteContainerToShow) && this.currentSiteContainerToShow.getSite() === site) {
            let self = this;
            history.replaceState({
                'siteName': site.constructor.name,
                'siteData': site._siteContent.outerHTML,
                'stackPosition': self.siteContainerStack.length - 1
            }, site.constructor.name, site.getFullUrl());
        }
    }

    getCurrentSite() {
        if (this.currentSiteContainerToShow != null)
            return this.currentSiteContainerToShow.getSite();
    }

    redrawCurrentActionBar() {
        if (this.currentSiteContainerToShow != null)
            this.currentSiteContainerToShow.getSite().actionMenu.redraw();
    }

    updateTitle() {
        let title = this.getCurrentSite().title;
        this.titleElement.removeAllChildren().appendChild(title.element);
        document.title = Helper.nonNull(title.title, this.defaultTitle.title);
    }

    /** @private */
    findContainerIndexBySite(site) {
        for (let i = 0, n = this.siteContainerStack.length; i < n; i++) {
            if (this.siteContainerStack[i].getSite() === site) {
                return i;
            }
        }
        return -1;
    }

    /** @private */
    findContainerBySite(site) {
        let index = this.findContainerIndexBySite(site);
        if (index === -1) {
            return null;
        }
        return this.siteContainerStack[index];
    }

    /** @private */
    showAppEndedMessage() {
        this.siteDiv.removeAllChildren().appendChild(Translator.makePersistentTranslation("The app has ended! Please close the window."));
    }

    /** @private */
    buildActionBarMenu() {
        return new ActionBarMenu(this.actionBarMenuSelector);
    }
}

class PauseSite extends AbstractSite {
    onConstruct(args) {
        let pausedElement = null;

        if (Helper.isSet(args, "url")) {
            pausedElement = args["url"];
        }
        else {
            pausedElement = document.createElement("div");
            pausedElement.innerHTML = "Paused...";
        }

        this.inflateView(pausedElement);
    }

    onPause() {
        console.log("onPause");
        for (let i = 0; i < PauseSite.onStartListeners.length; i++) {
            if (typeof PauseSite.onStartListeners[i] === "function") {
                PauseSite.onStartListeners[i]();
            }
        }
        return super.onPause();
    }

    onStart() {
        console.log("onStart");
        for (let i = 0; i < PauseSite.onPauseListeners.length; i++) {
            if (typeof PauseSite.onPauseListeners[i] === "function") {
                PauseSite.onPauseListeners[i]();
            }
        }
        return super.onPause();
    }
}

PauseSite.onPauseListeners = [];
PauseSite.onStartListeners = [];

class App {
    constructor() {
        this._siteManager = null;
        this._actionBarMenuSelector = '.action-bar';
        this._basePath = SystemSettings.getBasePath();
        this._siteContentId = 'site-content';
        this._deepLinks = new Map();
        this._defaultActions = [];
        this._addThemeAction = false;
        this._showCookieCompliance = true;
        this._startSite = null;
        this._appEndListener = () => {
            return this.startSite(this._startSite);
        };
    }

    getSiteManager()
    {
        return this._siteManager;
    }

    addDefaultAction(action) {
        this._defaultActions.push(action);
    }

    setAddThemeAction(addThemeAction) {
        this._addThemeAction = addThemeAction;
    }

    getSiteContentId() {
        return this._siteContentId;
    }

    setSiteContentId(value) {
        this._siteContentId = value;
    }

    getActionBarMenuSelector() {
        return this._actionBarMenuSelector;
    }

    setActionBarMenuSelector(value) {
        this._actionBarMenuSelector = value;
    }

    getBasePath() {
        return this._basePath;
    }

    setBasePath(value) {
        this._basePath = value;
    }

    addDeepLink(alias, site) {
        this._deepLinks.set(alias.toLowerCase(), site);
    }

    setShowCookieCompliance(cookieCompliance)
    {
        this._showCookieCompliance = cookieCompliance;
    }

    refreshCurrentSite()
    {
        this._siteManager.refreshCurrentSite();
    }

    pause(elementToShow){
        this.startSite(PauseSite, {"url": elementToShow});
    }

    resume(){
        const currentSite = this._siteManager.getCurrentSite();
        if (currentSite instanceof PauseSite)
        {
            currentSite.finish();
        }
    }

    _resolveDeepLink(deepLink) {
        deepLink = deepLink.toLowerCase();
        if (this._deepLinks.has(deepLink)) {
            return this._deepLinks.get(deepLink);
        }
        return null;
    }

    _getDeepLink() {
        let deepLink = "";
        if (window.location.pathname.search(this._basePath) === 0) {
            deepLink = window.location.pathname.substr(this._basePath.length).trim();
        }
        if (deepLink.charAt(0) === '/') {
            deepLink = deepLink.substr(1).trim(); 
        }
        if (deepLink.charAt(deepLink.length - 1) === '/') {
            deepLink = deepLink.substr(0, deepLink.length - 2).trim();
        }
        if (deepLink.length === 0 && window.location.hash) {
            deepLink = window.location.hash.substr(1).trim();
        }

        return this._resolveDeepLink(deepLink);
    }

    _addDeepLinksListener() {
        let app = this;
        let elements = document.getElementsByClassName("deep-link");
        for (let i = 0, n = elements.length; i < n; i++) {
            elements[i].addEventListener("click", function (e) {
                e.preventDefault();
                app._siteManager.startSite(Helper.nonNull(app._resolveDeepLink(this.dataset["siteName"]), app._startSite), App._extractParams(this.dataset["siteArgs"]));
                return true;
            });
        }
    }

    removeDefaultAction(action)
    {
        let index = this._defaultActions.indexOf(action);
        if (index >= 0)
        {
            this._defaultActions[index].remove(true);
            this._defaultActions.splice(index, 1);
        }
    }
    
    startSite(site, parameter)
    {
        return this._siteManager.startSite(site, parameter);
    }

    start(fallbackStartSite) {
        SystemSettings.setBasePath(this._basePath);
        let startSite = Helper.nonNull(this._getDeepLink(), fallbackStartSite);
        let startParams = App._getStartParams();
        this._startSite = fallbackStartSite;

        Translator.init();
        ThemeManager.init();
        if (this._addThemeAction) {
            this.addDefaultAction(ThemeManager.generateChangeThemeMenuAction());
        }
        this._siteManager = new SiteManager(this._siteContentId, this._actionBarMenuSelector);
        this._siteManager.defaultActions = this._defaultActions;
        this._siteManager.setStartSiteName(fallbackStartSite);
        this._siteManager.startSite(startSite, startParams);
        this._addDeepLinksListener();

        if (this._showCookieCompliance)
        {
            this._cookieClosePromise = CookieCompliance.showIfNeeded('cookie-compliance');
        }
    }

    getCurrentSite(){
        return this._siteManager.getCurrentSite();
    }

    async endApp(){
        if (typeof this._appEndListener === "function"){
            this._appEndListener();
        }
    }

    setAppEndListener(appEndListener){
        this._appEndListener = appEndListener;
    }

    async findSite(filter){
        return this._siteManager.findSite(filter);
    }

    static _extractParams(paramString) {
        if (Helper.isNull(paramString)) {
            return null;
        }
        let result = {}, tmp = [];
        let items = paramString.split("&");
        for (let index = 0; index < items.length; index++) {
            tmp = items[index].split("=");
            if (tmp[0].trim().length > 0) {
                result[tmp[0]] = decodeURIComponent(tmp[1]);
            }
        }
        return result;
    }

    static _getStartParams() {
        return App._extractParams(window.location.search.substr(1));
    }
}

class ChainAble{
    constructor()
    {
        this.promise = Promise.resolve();
    }

    _chain(func){
        if (typeof func !== "function")
        {
            return this._chain(function(){
                return Promise.resolve(func);
            });
        }
        else
        {
            this.promise = this.promise.then(func);
        }
        return this;
    }
}

class Dialog {
    constructor(content, title) {
        this.resolver = null;
        this.content = null;
        this.backgroundElement = null;
        this.cancelable = true;
        this.title = Helper.nonNull(title, "");
        this.translatable = true;
        this.additionalClasses = "";
        this.buttons = [];
        this.result = null;

        if (Helper.isNotNull(content)) {
            this.setContent(content);
        }
    }

    setTitle(title) {
        this.title = title;
        return this;
    }

    setTranslatable(translatable) {
        this.translatable = translatable;
    }

    setAdditionalClasses(classes) {
        this.additionalClasses = classes;
    }

    getTitle() {
        return this.title;
    }

    setCancelable(cancelable) {
        this.cancelable = (cancelable === true);
        return this;
    }

    async setContent(content) {
        this.contentPromise = Promise.resolve(content);
        this.content = await this.contentPromise;
        return this;
    }

    addButton(elementOrText, listenerOrResult, shouldClose) {
        shouldClose = Helper.nonNull(shouldClose, true);

        let button = null;
        if (typeof elementOrText === "string") {
            button = document.createElement("button");
            button.classList.add("button");
            button.classList.add("right");
            button.appendChild(Translator.makePersistentTranslation(elementOrText));
        }
        else {
            button = elementOrText;
        }

        let self = this;
        if (typeof listenerOrResult !== "function") {
            let result = listenerOrResult;
            listenerOrResult = function () {
                self.result = result;
            };
        }

        let callback = null;
        if (shouldClose) {
            callback = function (e) {
                if (Helper.isNotNull(listenerOrResult)) {
                    listenerOrResult(e);
                }
                self.close();
            };
        }
        else {
            callback = listenerOrResult;
        }

        if (Helper.isNotNull(callback)) {
            button.addEventListener("click", callback);
        }
        this.buttons.push(button);
    }

    async show() {

        let titleElement = document.createElement("span");
        titleElement.classList.add("title");
        if (this.translatable && this.title !== "") {
            titleElement.appendChild(Translator.makePersistentTranslation(this.title));
        }
        else {
            titleElement.innerHTML = this.title;
        }

        let titleBar = document.createElement("div");
        titleBar.appendChild(titleElement);

        let contentContainer = document.createElement("div");
        contentContainer.classList.add("content-container");

        let modalDialog = document.createElement("div");
        modalDialog.className = this.additionalClasses;
        modalDialog.classList.add("modal");
        modalDialog.appendChild(titleBar);
        modalDialog.appendChild(contentContainer);

        let buttonBar = document.createElement("div");
        buttonBar.classList.add("modal-button-container");

        for (let i = 0, n = this.buttons.length; i < n; i++) {
            buttonBar.appendChild(this.buttons[i]);
        }

        await this.contentPromise;
        if (!(this.content instanceof Node)) {
            this.content = (this.translatable) ? Translator.makePersistentTranslation(this.content) : document.createTextNode(this.content);
        }
        contentContainer.appendChild(this.content);

        this.backgroundElement = document.createElement("div");
        this.backgroundElement.classList.add("background");
        this.backgroundElement.appendChild(modalDialog);

        this.backgroundElement.querySelector(".modal").appendChild(buttonBar);
        this.backgroundElement.style.display = "block";

        let self = this;
        if (this.cancelable) {
            let closeButton = document.createElement("span");
            closeButton.classList.add("close");
            closeButton.innerHTML = "&times;";

            titleBar.appendChild(closeButton);
            closeButton.addEventListener("click", function () {
                self.close();
            });
            window.addEventListener("click", function (e) {
                if (e.target === self.backgroundElement) {
                    self.close();
                }
            });
        }

        document.body.appendChild(this.backgroundElement);
        Translator.getInstance().updateTranslations();

        return new Promise(function (resolve) {
            self.resolver = resolve;
        });
    }

    close() {
        if (Helper.isNotNull(this.backgroundElement)) {
            this.backgroundElement.style.display = "none";
            this.backgroundElement.remove();
            this.backgroundElement = null;
        }
        if (Helper.isNotNull(this.resolver)) {
            this.resolver(this.result);
        }
    }

    addDefaultButton(){
        this.addButton("confirm-button");
    }
}

class ConfirmDialog extends Dialog {
    constructor(content, title) {
        super(content, title);
    }

    async show() {
        this.addButton("confirm-button", true);
        this.addButton("cancel-button", false);

        return super.show();
    }


    close() {
        if (Helper.isNull(this.result))
        {
            this.result = false;
        }
        return super.close();
    }
}

class FlashMessenger {
    static deleteMessage(_idNumber, _delayInMilliSeconds) {
        _delayInMilliSeconds = Helper.nonNull(_delayInMilliSeconds, 0);
        setTimeout(function () {
            let elem = document.getElementById("flashMessage" + _idNumber);
            elem.fadeOut(.2).then(function () {
                elem.remove();
            });
        }, _delayInMilliSeconds);
    }

    static addMessage(messageType, messageText, timeToShow, translate){

        let translationArgs = null;
        if (Helper.isNull(messageText) || typeof messageText === "object")
        {
            translationArgs = messageText;
            messageText = messageType;
            messageType = FlashMessenger.MESSAGE_TYPE_SUCCESS;
            translate = true;
        }
        translate = Helper.nonNull(translate, false);

        let id = FlashMessenger.messageCount;

        let wrapper = document.createElement("div");

        let _flashMessage = document.createElement("div");
        _flashMessage.className = "flashMessage " + messageType;
        _flashMessage.id = "flashMessage" + id;
        _flashMessage.style.opacity = '0';
        _flashMessage.addEventListener("click", function () {
            FlashMessenger.deleteMessage(id);
        });
        _flashMessage.appendChild((translate) ? Translator.makePersistentTranslation(messageText, translationArgs, "span") : document.createTextNode(messageText));

        wrapper.appendChild(_flashMessage);
        document.getElementById("flashMessageContainer").appendChild(wrapper);
        _flashMessage.fadeIn();
        timeToShow = Helper.nonNull(timeToShow, FlashMessenger.defaultTimeToShow);
        if (timeToShow > 0) {
            FlashMessenger.deleteMessage(FlashMessenger.messageCount, timeToShow);
        }
        FlashMessenger.messageCount++;
    }
}

FlashMessenger.messageCount = 0;
FlashMessenger.defaultTimeToShow = 3500;
FlashMessenger.LENGTH_SHORT= 1000;

FlashMessenger.MESSAGE_TYPE_SUCCESS = 'success';
FlashMessenger.MESSAGE_TYPE_ERROR = 'error';
FlashMessenger.MESSAGE_TYPE_DEFAULT = 'default';
FlashMessenger.MESSAGE_TYPE_INFO = 'info';
FlashMessenger.MESSAGE_TYPE_WARNING = 'warning';

class AbstractService{
    constructor(gapiHandler)
    {
        this.gapiHandler = gapiHandler;
    }
}

class AbstractGapiResponse extends ChainAble{
    constructor(gameService, params)
    {
        super();
        this.gameService = gameService;
        if (Helper.isNotNull(params))
        {
            this.setValues(params);
        }
    }
    setValues(params){}
    load(){}
}

class Achievement extends AbstractGapiResponse {
    setHiddenIconUrl(hiddenIconUrl)
    {
        this.hiddenIconUrl = hiddenIconUrl;
    }

    setValues(params) {
        let self = this;
        this._chain(params)._chain(function (res) {
            self.achievementType = res["achievementType"];
            self.description = res["description"];
            self.experiencePoints = res["experiencePoints"];
            self.id = res["id"];
            self.initialState = res["initialState"];
            self.isRevealedIconUrlDefault = res["isRevealedIconUrlDefault"];
            self.isUnlockedIconUrlDefault = res["isUnlockedIconUrlDefault"];
            self.name = res["name"];
            self.revealedIconUrl = res["revealedIconUrl"];
            self.unlockedIconUrl = res["unlockedIconUrl"];

            self.achievementState = res["achievementState"];
            // self.experiencePoints = res["experiencePoints"]; //oben bereits gesetzt
            // self.id = res["id"]; //oben bereits gesetzt
            self.lastUpdatedTimestamp = res["lastUpdatedTimestamp"];
        });
        return this.promise;
    }

    show(elem) {
        let self = this;
        this._chain(function () {
            let achievementImg = elem.querySelector(".achievement-img");
            let achievementTitle = elem.querySelector(".achievement-title");
            let achievementDescription = elem.querySelector(".achievement-description");
            let achievementLastTimestamp = elem.querySelector(".achievement-lastTimestamp");
            let achievementXp = elem.querySelector(".achievement-xp");

            if (self.achievementState === "UNLOCKED") {
                achievementImg.src = self.unlockedIconUrl;
                achievementLastTimestamp.innerText = Helper.strftime("%d %a %Y", parseInt(self.lastUpdatedTimestamp));
            }
            else if (self.achievementState === "REVEALED") {
                achievementImg.src = self.revealedIconUrl;
            }
            else {
                achievementImg.src = self.hiddenIconUrl;
                achievementTitle.appendChild(Translator.makePersistentTranslation("achievement-hidden-title"));
                achievementDescription.appendChild(Translator.makePersistentTranslation("achievement-hidden-description"));
                return;
            }

            achievementTitle.innerText = self.name;
            achievementDescription.innerText = self.description;
            achievementXp.innerText = self.experiencePoints+" XP";
        });
        return this.promise;
    }
}

class AchievementList extends AbstractGapiResponse {
    constructor(gameService, params) {
        super(gameService, params);
        this.removedAchievementIds = [];
    }

    setHiddenImgLink(hiddenImgLink) {
        this.hiddenIconUrl = hiddenImgLink;
    }

    setRemovedAchievementIds(removedAchievementIds) {
        this.removedAchievementIds = removedAchievementIds;
    }

    load(maxResults, pageToken, language) {
        return this.setValues(Promise.all([this.gameService.getAchievements(maxResults, pageToken, null, language), this.gameService.getPlayerAchievements(null, maxResults, pageToken, null, language)]).then(function (res) {
                if (Helper.isNotNull(res[0]["items"]) && Helper.isNotNull(res[1]["items"])) {
                    for (let i = 0, n = Math.min(res[0]["items"].length, res[1]["items"].length); i < n; i++) {
                        for (let key in res[1]["items"][i]) {
                            res[0]["items"][i][key] = res[1]["items"][i][key];
                        }
                    }
                }
                return res[0];
            })
        );
    }

    setValues(params) {
        let self = this;
        this._chain(params)._chain(function (res) {
            let promises = [];

            self.nextPageToken = res["nextPageToken"];

            self.items = [];
            if (Helper.isNotNull(res["items"])) {
                for (let i = 0, n = res["items"].length; i < n; i++) {
                    if (self.removedAchievementIds.indexOf(res["items"][i]["id"]) === -1) {

                        let achievement = new Achievement(self.gameService, res["items"][i]);
                        achievement.setHiddenIconUrl(self.hiddenIconUrl);
                        self.items.push(achievement);
                        promises.push(achievement.promise);
                    }
                }
            }
            return Promise.all(promises);
        });
        return this.promise;
    }

    show(elem) {
        let self = this;
        this._chain(function () {
            let nextButton = elem.querySelector("#achievement-list-next-button");
            let achievementTemplate = elem.querySelector("#achievement-list-achievement-template");
            let parentAchievementTemplate = achievementTemplate.parentElement;
            parentAchievementTemplate.removeAllChildren();
            achievementTemplate.id = "";

            let promises = [];
            for (let i = 0, n = self.items.length; i < n; i++) {
                let achievementElem = Helper.cloneNode(achievementTemplate);
                promises.push(self.items[i].show(achievementElem));
                parentAchievementTemplate.appendChild(achievementElem);
            }
            return Promise.all(promises);
        });
        return this.promise;
    }
}

class GameService extends AbstractService {
    getLeaderboards(maxResults, pageToken, language, consistencyToken) {
        language = Helper.nonNull(language, Translator.currentLanguage);
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["leaderboards"]["list"]({
                    "maxResults": maxResults,
                    "consistencyToken": consistencyToken,
                    "pageToken": pageToken,
                    "language": language
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    getScoresAroundPlayer(leaderboardId, collection, timeSpan, maxResults, pageToken, resultsAbove, returnTopIfAbsent, consistencyToken, language) {
        language = Helper.nonNull(language, Translator.currentLanguage);
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["scores"]["listWindow"]({
                    "collection": collection,
                    "leaderboardId": leaderboardId,
                    "timeSpan": timeSpan,
                    "consistencyToken": consistencyToken,
                    "language": language,
                    "maxResults": maxResults,
                    "pageToken": pageToken,
                    "resultsAbove": resultsAbove,
                    "returnTopIfAbsent": returnTopIfAbsent
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    getAchievements(maxResults, pageToken, consistencyToken, language) {
        language = Helper.nonNull(language, Translator.currentLanguage);
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["achievementDefinitions"]["list"]({
                    "consistencyToken": consistencyToken,
                    "language": language,
                    "maxResults": maxResults,
                    "pageToken": pageToken,
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    getPlayerAchievements(playerId, maxResults, pageToken, consistencyToken, language) {
        playerId = Helper.nonNull(playerId, "me");
        language = Helper.nonNull(language, Translator.currentLanguage);
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["achievements"]["list"]({
                    "playerId": playerId,
                    "consistencyToken": consistencyToken,
                    "language": language,
                    "maxResults": maxResults,
                    "pageToken": pageToken,
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    submitScore(leaderboardId, score) {
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["scores"]["submit"]({
                    "leaderboardId": leaderboardId,
                    "score": score
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    revealAchievement(achievementId) {
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["achievements"]["reveal"]({
                    "achievementId": achievementId
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        });
    }

    unlockAchievement(achievementId, showIsNewAchievement) {
        return this.gapiHandler.promise.then(function () {
            return new Promise(function (resolve) {
                gapi["client"]["games"]["achievements"]["unlock"]({
                    "achievementId": achievementId
                })["execute"](function (response) {
                    resolve(response);
                });
            });
        }).then(function(response){
            if (showIsNewAchievement && response["newlyUnlocked"])
            {
                FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("new-unlocked-achievement"));
            }
        });
    }
}

GameService.COLLECTION_PUBLIC = "PUBLIC";
GameService.COLLECTION_SOCIAL = "SOCIAL";
GameService.COLLECTION_SOCIAL_1P = "SOCIAL_1P";

GameService.TIME_SPAN_ALL_TIME = "ALL_TIME";
GameService.TIME_SPAN_DAILY = "DAILY";
GameService.TIME_SPAN_WEEKLY = "WEEKLY";

class GapiPlayer extends AbstractGapiResponse{
    setValues(params)
    {
        let self = this;
        this._chain(params)._chain(function(res){
            self.playerId = res["playerId"];
            self.displayName = res["displayName"];
            self.avatarImageUrl = res["avatarImageUrl"];
            self.experienceInfo = res["experienceInfo"];
            // self.bannerUrlPortrait = res["scoreValue"];
            // self.formattedScore = res["formattedScore"];
            // self.timeSpan = res["timeSpan"];
            // self.writeTimestampMillis = res["writeTimestampMillis"];
            // self.scoreTag = res["scoreTag"];
        });
        return this.promise;
    }
}

class Score extends AbstractGapiResponse
{
    setValues(params)
    {
        let self = this;
        this._chain(params)._chain(function(res){
            self.player = new GapiPlayer(self.gameService, res["player"]);
            self.scoreRank = res["scoreRank"];
            self.formattedScoreRank = res["formattedScoreRank"];
            self.scoreValue = res["scoreValue"];
            self.formattedScore = res["formattedScore"];
            self.timeSpan = res["timeSpan"];
            self.writeTimestampMillis = res["writeTimestampMillis"];
            self.scoreTag = res["scoreTag"];

            return self.player.promise;
        });
        return this.promise;
    }
}

class Leaderboard extends AbstractGapiResponse {
    setValues(params) {
        let self = this;
        this.promise = this.promise.then(function () {
            return Promise.resolve(params);
        }).then(function (res) {
            let promises = [];

            self.nextPageToken = res["nextPageToken"];
            self.previousPageToken = res["prevPageToken"];
            self.numScores = res["numScores"];
            self.playerScore = new Score(self.gameService, res["playerScore"]);

            promises.push(self.playerScore.promise);

            self.items = [];
            if (Helper.isNotNull(res["items"])) {
                for (let i = 0, n = res["items"].length; i < n; i++) {
                    self.items.push(new Score(self.gameService, res["items"][i]));
                    promises.push(self.items[i].promise);
                }
            }

            return Promise.all(promises);
        });
        return this.promise;
    }

    setLeaderboardId(leaderboardId) {
        this.leaderboardId = leaderboardId;
    }

    load(maxResults, pageToken, resultsAbove) {
        return this.setValues(this.gameService.getScoresAroundPlayer(this.leaderboardId, this.collection, this.timeSpan, maxResults, pageToken, resultsAbove));
    }

    setCollection(collection) {
        this.collection = collection;
    }

    setTimeSpan(timeSpan) {
        this.timeSpan = timeSpan;
    }

    setDisplayName(name)
    {
        this.displayName = name;
    }

    show(elem, buildTimeSelect, buildCollectionSelect, isTime) {
        buildCollectionSelect = Helper.nonNull(buildCollectionSelect, true);
        buildTimeSelect = Helper.nonNull(buildTimeSelect, true);
        isTime = Helper.nonNull(isTime, false);
        let args = arguments;

        let self = this;
        this._chain(function () {
            let prevButton = elem.querySelector("#leaderboard-previous-button");
            let nextButton = elem.querySelector("#leaderboard-next-button");
            let scoreTemplate = elem.querySelector("#leaderboard-score-template");
            let parentScoreElem = scoreTemplate.parentElement;
            parentScoreElem.removeAllChildren();

            let timeElem = self._buildTime(buildTimeSelect);
            elem.querySelector("#leaderboard-time-span").removeAllChildren().appendChild(timeElem);

            let collectionElem = self._buildCollection(buildCollectionSelect);
            elem.querySelector("#leaderboard-collection").removeAllChildren().appendChild(collectionElem);

            timeElem.onchange = function () {
                self._updateWithDataFromElem(elem, parentScoreElem, scoreTemplate, args);
            };

            collectionElem.onchange = function () {
                self._updateWithDataFromElem(elem, parentScoreElem, scoreTemplate, args);
            };

            for (let i = 0, n = self.items.length; i < n; i++) {
                let score = self.items[i];
                let scoreElem = Helper.cloneNode(scoreTemplate);
                scoreElem.querySelector(".leaderboard-rank").innerText = score.scoreRank;
                scoreElem.querySelector(".leaderboard-player-img").src = score.player.avatarImageUrl;
                scoreElem.querySelector(".leaderboard-player-name").innerText = score.player.displayName;
                scoreElem.querySelector(".leaderboard-points").innerText = (isTime)?Helper.strftime("%H:%M:%S", new Date(parseInt(score.scoreValue)), true):score.formattedScore;
                scoreElem.id = "";

                if (Helper.isNotNull(self.playerScore) && Helper.isNotNull(self.playerScore.player) && score.player.playerId === self.playerScore.player.playerId) {
                    scoreElem.classList.add("leaderboard-current-player");
                }
                parentScoreElem.appendChild(scoreElem);
            }
            if (self.items.length === 0)
            {
                parentScoreElem.appendChild(Translator.makePersistentTranslation("no-scores-available"));
            }

        });
        return this.promise;
    }

    _buildCollection(buildCollectionSelect) {
        if (buildCollectionSelect) {
            let select = document.createElement("select");
            let publicOption = document.createElement("option");
            publicOption.value = GameService.COLLECTION_PUBLIC;
            publicOption.innerHTML = Translator.translate(GameService.COLLECTION_PUBLIC);
            publicOption.dataset["translate"] = GameService.COLLECTION_PUBLIC;

            if (GameService.COLLECTION_PUBLIC === this.collection) {
                publicOption.selected = true;
            }

            let socialOption = document.createElement("option");
            socialOption.value = GameService.COLLECTION_SOCIAL;
            socialOption.innerHTML = Translator.translate(GameService.COLLECTION_SOCIAL);
            socialOption.dataset["translate"] = GameService.COLLECTION_SOCIAL;

            if (GameService.COLLECTION_SOCIAL === this.collection) {
                socialOption.selected = true;
            }

            select.appendChild(socialOption);
            select.appendChild(publicOption);
            return select;
        }
        else {
            return Translator.makePersistentTranslation(this.collection);
        }
    }

    _updateWithDataFromElem(rootElem, scoreTable, scoreTemplate, args) {
        let loadingSymbol = Helper.createLoadingSymbol();
        rootElem.replaceWith(loadingSymbol);
        scoreTable.removeAllChildren().appendChild(scoreTemplate);

        let collectionElem = rootElem.querySelector("#leaderboard-collection select");
        if (Helper.isNotNull(collectionElem)) {
            this.collection = collectionElem.options[collectionElem.selectedIndex].value;
        }

        let timeSpanElem = rootElem.querySelector("#leaderboard-time-span select");
        if (Helper.isNotNull(timeSpanElem)) {
            this.timeSpan = timeSpanElem.options[timeSpanElem.selectedIndex].value;
        }

        this.load();
        this.show.apply(this, args).then(function () {
            loadingSymbol.replaceWith(rootElem);
        });
    }

    _buildTime(buildTimeSelect) {
        if (buildTimeSelect) {
            let select = document.createElement("select");
            let allTime = document.createElement("option");
            allTime.value = GameService.TIME_SPAN_ALL_TIME;
            allTime.innerHTML = Translator.translate(GameService.TIME_SPAN_ALL_TIME);
            allTime.dataset["translate"] = GameService.TIME_SPAN_ALL_TIME;

            if (GameService.TIME_SPAN_ALL_TIME === this.timeSpan) {
                allTime.selected = true;
            }

            let weeklyOption = document.createElement("option");
            weeklyOption.value = GameService.TIME_SPAN_WEEKLY;
            weeklyOption.innerHTML = Translator.translate(GameService.TIME_SPAN_WEEKLY);
            weeklyOption.dataset["translate"] = GameService.TIME_SPAN_WEEKLY;

            if (GameService.TIME_SPAN_WEEKLY === this.timeSpan) {
                weeklyOption.selected = true;
            }

            let dailyOption = document.createElement("option");
            dailyOption.value = GameService.TIME_SPAN_DAILY;
            dailyOption.innerHTML = Translator.translate(GameService.TIME_SPAN_DAILY);
            dailyOption.dataset["translate"] = GameService.TIME_SPAN_DAILY;

            if (GameService.TIME_SPAN_DAILY === this.timeSpan) {
                dailyOption.selected = true;
            }

            select.appendChild(dailyOption);
            select.appendChild(weeklyOption);
            select.appendChild(allTime);
            return select;
        }
        else {
            return Translator.makePersistentTranslation(this.timeSpan);
        }
    }
}

class GapiHandler {
    constructor() {
        this.loginCallbacks = [];
        this.games = new GameService(this);
    }

    init() {
        this.promise = new Promise(function (resolver) {
            let scriptElem = document.createElement("script");
            scriptElem.src = "https://apis.google.com/js/api.js";
            scriptElem.async = true;
            scriptElem.defer = true;
            scriptElem.onload = resolver;
            scriptElem.onreadystatechange = function () {
                if (this.readyState === "complete") {
                    resolver();
                }
            };
            document.body.appendChild(scriptElem);
        }).then(function () {
            window["gapi"] = gapi;
        });
        return this.promise;
    }

    load(libs) {
        this.promise = this.promise.then(function () {
            return new Promise(function (resolver) {
                gapi["load"](libs, resolver);
            });
        });
        return this.promise;
    }

    initClient(apiKey, clientId, scope, discoveryDocs, fetchBasicProfile, uxMode) {
        scope = Helper.nonNull(scope, "profile");
        fetchBasicProfile = Helper.nonNull(fetchBasicProfile, false);
        discoveryDocs = Helper.nonNull(discoveryDocs, []);
        uxMode = Helper.nonNull(uxMode,"popup");
        let self = this;
        this.promise = this.promise.then(function () {
            return gapi["client"]["init"]({
                "apiKey": apiKey,
                "discoveryDocs": discoveryDocs,
                "clientId": clientId,
                "scope": scope,
                "fetch_basic_profile":fetchBasicProfile,
                "ux_mode":uxMode
            });
        }).then(function () {
            let authInstance = gapi["auth2"]["getAuthInstance"]();
            authInstance["isSignedIn"]["listen"](function (isLoggedIn) {
                for (let i = 0, n = self.loginCallbacks.length; i < n; i++) {
                    self.loginCallbacks[i](isLoggedIn);
                }
            });
        });
        return this.promise;
    }

    loadClient(lib, version)
    {
        this.promise = this.promise.then(function () {
            return new Promise(function (resolver) {
                gapi["client"]["load"](lib, version, resolver);
            });
        });
        return this.promise;
    }

    addLoginCallback(callback) {
        this.loginCallbacks.push(callback);
        let authInstance = gapi["auth2"]["getAuthInstance"]();
        callback(authInstance["isSignedIn"]["get"]());
    }

    addLoginAction(app, callback) {
        let self = this;
        callback = Helper.nonNull(callback, function(){
            self.login();
        });
        this.loginAction = new MenuAction("login", callback, MenuAction.SHOW_NEVER, 10001);
        app.addDefaultAction(this.loginAction);
        ActionBarMenu.currentMenu.addAction(this.loginAction.copy());
    }

    removeLoginAction(app) {
        app.removeDefaultAction(this.loginAction);
    }

    login() {
        this.promise = this.promise.then(function () {
            let authInstance = gapi["auth2"]["getAuthInstance"]();
            return authInstance["signIn"]();
        });
        return this.promise;
    }

    logout() {
        this.promise = this.promise.then(function () {
            let authInstance = gapi["auth2"]["getAuthInstance"]();
            return authInstance["signOut"]();
        });
        return this.promise;
    }

    isLoggedIn() {
        return this.promise.then(function () {
            let authInstance = gapi["auth2"]["getAuthInstance"]();
            return authInstance["isSignedIn"]["get"]();
        })
    }
}

class MyDb {
    constructor(dbName, version) {
        let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
        this._conn = indexedDB.open(dbName, version);

        let myDB = this;
        this._conn.onupgradeneeded = function (upgradeEvent) {
            myDB.upgrade(myDB._conn.result, upgradeEvent.oldVersion, upgradeEvent.newVersion, upgradeEvent);
        };
        this.queryPromise = new Promise(function (resolve) {
            myDB._conn.onsuccess = function (e) {
                myDB._db = myDB._conn.result;
                resolve(e);
            };
        });
    }

    openTransaction(name, transactionMode, callback) {
        let myDb = this;
        if (typeof transactionMode === 'function' && Helper.isNull(callback)) {
            callback = transactionMode;
            transactionMode = "read";
        }

        return this.queryPromise.then(function () {
            let res = null;
            try {
                res = myDb._conn.result.transaction(name, transactionMode);
            }
            catch (e) {
                console.warn(e);
                res = myDb._conn.result.transaction(name);
            }
            callback(res);
        });
    }

    openStore(name, transactionMode, callback) {
        if (typeof transactionMode === 'function' && Helper.isNull(callback)) {
            callback = transactionMode;
            transactionMode = "readonly";
        }
        return this.openTransaction(name, transactionMode, function (t) {
            callback(t.objectStore(name));
        });
    }

    saveObj(obj, objectStore) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, "readwrite", function (store) {
                let request = store.put(obj);
                request.onsuccess = resolve;
                request.onerror = function (e) {
                    throw {
                        "type": "indexed-db-error",
                        "event": e
                    }
                };
            });
        });
    }

    saveMany(manyObj, objectStore) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, "readwrite", function (store) {
                let promises = [];
                for (let i = 0, n = manyObj.length; i < n; i++) {
                    promises.push(new Promise(function (resolveInner) {
                        let request = store.put(manyObj[i]);
                        request.onsuccess = resolveInner;
                        request.onerror = function (e) {
                            throw {
                                "type": "indexed-db-error",
                                "event": e
                            }
                        };
                    }));
                }
                resolve(Promise.all(promises));
            });
        });
    }

    load(key, objectStore) {
        let self = this;
        return new Promise( (resolve, reject) => {
            self.openStore(objectStore, function (store) {
                let request = store.get(key);
                request.onsuccess = function (e) {
                    resolve(e.currentTarget.result);
                };
                request.onerror = function (e) {
                    console.warn(e);
                    throw {
                        "type": "indexed-db-load-error",
                        "event": e
                    }
                };
            }).catch(e => {
                console.warn(e);
                reject(e);
            });
        });
    }
    
    loadAll(objectStore, query, count)
    {
        let self = this;
        return new Promise((resolve, reject)  => {
            self.openStore(objectStore, function (store) {
                let request = store.getAll(query, count);
                request.onsuccess = function (e) {
                    resolve(e.currentTarget.result);
                };
                request.onerror = function (e) {
                    console.warn(e);
                    throw {
                        "type": "indexed-db-load-error",
                        "event": e
                    }
                };
            }).catch(e => {
                console.warn(e);
                reject(e);
            });
        });
    }

    loadMany(index, value, objectStore, limit, direction) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, function (store) {
                let indexRequest = store.index(index);
                indexRequest.onerror = function (e) {
                    throw {
                        "type": "indexed-db-index-error",
                        "event": e
                    }
                };
                let request = indexRequest.openCursor(value, direction);
                request.onerror = function (e) {
                    throw {
                        "type": "indexed-db-index-error",
                        "event": e
                    }
                };
                let objects = [];
                let numberResults = 0;
                request.onsuccess = function (e) {
                    let cursor = e.target.result;
                    if (cursor) {
                        objects.push(cursor.value);
                        numberResults++;
                        if (Helper.isNull(limit) || numberResults < limit) {
                            cursor.continue();
                            return;
                        }
                    }
                    resolve(objects);
                };
            });
        });
    }

    remove(id, objectStore) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, "readwrite", function (store) {
                let deleteRequest = store.delete(id);
                deleteRequest.onerror = function (e) {
                    throw {
                        "type": "indexed-db-delete-error",
                        "event": e
                    }
                };
                deleteRequest.onsuccess = function (e) {
                    resolve();
                };
            });
        });
    }

    removeMany(ids, objectStore) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, "readwrite", function (store) {
                let promises = [];
                for (let i = 0, n = ids.length; i < n; i++) {
                    let deleteRequest = store.delete(ids[i]);
                    deleteRequest.onerror = function (e) {
                        throw {
                            "type": "indexed-db-delete-error",
                            "event": e
                        }
                    };
                    promises.push(new Promise(function (resolve) {
                        deleteRequest.onsuccess = function () {
                            resolve();
                        };
                    }));
                }
                resolve(Promise.all(promises));
            });
        });
    }

    removeWithIndex(index, value, objectStore) {
        let self = this;
        return new Promise(function (resolve) {
            self.openStore(objectStore, "readwrite", function (store) {
                let indexRequest = store.index(index);
                indexRequest.onerror = function (e) {
                    throw {
                        "type": "indexed-db-index-error",
                        "event": e
                    }
                };
                let request = indexRequest.openCursor(value);
                request.onerror = function (e) {
                    throw {
                        "type": "indexed-db-index-error",
                        "event": e
                    }
                };
                request.onsuccess = function (e) {
                    let cursor = e.target.result;
                    if (cursor) {
                        cursor.delete();
                        cursor.continue();
                    }
                    else {
                        resolve();
                    }
                };
            });
        });
    }

    removeAll(objectStore){
        return new Promise((resolve) => {
            this.openStore(objectStore, "readwrite", (store) => {
                let req = store.clear();
                req.onerror = (e) => {
                    throw {
                        "type":"indexed-db-index-error",
                        "event": e
                    }
                };
                req.onsuccess = resolve;
            });
        })
    }

    upgrade(db) {
    };
}

class Prioritised {
    constructor(resultPromises, callback) {
        this.result = null;
        this.prioResult = -1;
        this.callback = null;
        this.hasResult = false;
        this.highestPrio = -1;

        let self = this;
        this.callbackCalledPromise = new Promise(function (resolver) {
            self.callbackCalledPromiseResolver = resolver;
        });

        this.endPromise = new Promise(function (resolver) {
            self.endPromiseResolver = resolver;
        });

        this.setCallback(callback);

        for (let k in resultPromises) {
            this.registerResultPromise(k, resultPromises[k]);
        }
    }

    async registerResultPromise(prio, promise) {
        this.highestPrio = Math.max(prio, this.highestPrio);
        let res = await Promise.resolve(promise);
        this._setResult(prio, res);
    }

    _setResult(prio, result) {
        if (this.prioResult <= prio || !this.hasResult) {
            this.hasResult = true;

            this.prioResult = prio;
            this.result = result;
            if (Helper.isNotNull(this.callback)) {
                this.callback(result, prio, this);
                this.callbackCalledPromiseResolver();
            }

            if (prio == this.highestPrio) {
                this.endPromiseResolver();
            }
        }
    }

    setCallback(callback) {
        this.callback = callback;
        if (this.hasResult && Helper.isNotNull(callback)) {
            callback(this.result, this.prioResult, this);
            this.callbackCalledPromiseResolver();
        }
    }

    getCallbackCalledPromise() {
        return this.callbackCalledPromise;
    }

    getEndPromise() {
        return this.endPromise;
    }
}

class ScriptLoader {
    static loadScript(scriptSrc) {
        if (Helper.isNotNull(ScriptLoader.scriptPromises[scriptSrc])) {
            return ScriptLoader.scriptPromises[scriptSrc];
        }
        else {
            let scriptPromise = new Promise(function (resolve) {
                let script = document.createElement("script");
                script.src = Helper.basePath(scriptSrc);
                script.onload = resolve;

                document.body.appendChild(script);
            });
            ScriptLoader.scriptPromises[scriptSrc] = scriptPromise;
            return scriptPromise;
        }
    }
    static loadCss(cssFile, media){
        if (Helper.isNotNull(ScriptLoader.cssPromises[cssFile])) {
            return ScriptLoader.cssPromises[cssFile];
        }
        else {
            media = Helper.nonNull(media, "all");
            let cssPromise = new Promise(function (resolve) {
                let link = document.createElement("link");
                link.rel='stylesheet';
                link.type="text/css";
                link.href = Helper.basePath(cssFile);
                link.media = media;
                link.onload = resolve;

                document.head.appendChild(link);
            });
            ScriptLoader.cssPromises[cssFile] = cssPromise;
            return cssPromise;
        }
    }
}

ScriptLoader.scriptPromises = {};
ScriptLoader.cssPromises = {};

class ShareButton {
    constructor(deviceType, icon, callback, shouldLoadImg)
    {
        this._deviceType = deviceType;
        this._icon = icon;
        this._callback = callback;

        if (Helper.nonNull(shouldLoadImg, false)){
            this._icon = ViewInflater.inflate(this._icon);
        }
    }

    shouldShowFor(deviceType)
    {
        return (deviceType === (deviceType & this._deviceType))
    }

    getIcon()
    {
        return this._icon;
    }

    getCallback()
    {
        return this._callback;
    }
}
ShareButton.TYPE_DESKTOP = 1;
ShareButton.TYPE_MOBILE_APPLE = 2;
ShareButton.TYPE_MOBILE_LEFTOVER = 4;
ShareButton.TYPE_MOBILE = ShareButton.TYPE_MOBILE_APPLE+ShareButton.TYPE_MOBILE_LEFTOVER;
ShareButton.TYPE_ALL = ShareButton.TYPE_DESKTOP+ShareButton.TYPE_MOBILE;

AndroidBridge.addDefinition(() => {
    window["ShareButton"] = ShareButton;
    window["ShareButton"]["TYPE_ALL"] = ShareButton.TYPE_ALL;

});

class CopyShareButton extends ShareButton
{
    constructor(icon, shouldLoadImg) {
        super(ShareButton.TYPE_ALL, icon, function (link) {
            try{
                let elementToCopy = document.createElement("div");
                elementToCopy.style.position = "fixed";
                elementToCopy.style.top = 0;
                elementToCopy.style.left = 0;
                elementToCopy.style.width = "0em";
                elementToCopy.style.height = "0em";
                elementToCopy.style.padding = 0;
                elementToCopy.style.border = "none";
                elementToCopy.style.outline = "none";
                elementToCopy.style.boxShadow = "none";
                elementToCopy.style.backgroundElement = "transparent";
                elementToCopy.style.color = "transparent";
                elementToCopy.innerText = link;
                document.body.appendChild(elementToCopy);
                Helper.select(elementToCopy);
                if (document.execCommand("copy")) {
                    FlashMessenger.addMessage(FlashMessenger.MESSAGE_TYPE_SUCCESS, Translator.translate("copied"), 1800);
                }
                elementToCopy.remove();
            } catch(e) {
                console.error(e);
            }
        }, shouldLoadImg);
    }
}

class MultipleShareButton extends ShareButton{
    constructor(deviceType, icon, callbacks, shouldLoadImg)
    {
        if (Array.isArray(deviceType) && deviceType[0] instanceof ShareButton){
            let btn = deviceType[0];
            callbacks = deviceType;
            deviceType = btn._deviceType;
            icon = btn._icon;
            shouldLoadImg = Helper.nonNull(shouldLoadImg, icon);
        }

        super(deviceType, icon, function (link, element, event) {
            if (!Array.isArray(callbacks)){
                callbacks = [callbacks];
            }
            for (let i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof ShareButton){
                    callbacks[i].getCallback()(link, element, event);
                }
                else {
                    console.log(callbacks, i);
                    callbacks[i](link, element, event);
                }
            }
        }, shouldLoadImg);
    }
}

class ShareManager {
    static init() {
        ShareManager.shareButtons = [];
    }

    static addShareButton(shareButton) {
        ShareManager.shareButtons.push(shareButton);
    }

    static generateDefaultShareElement(shareUrl) {
        return ShareManager.generateShareElement(shareUrl, ShareManager.getDefaultGenerateCallback());
    }

    static generateDefaultShareElementForButtons(shareUrl, buttons) {
        return ShareManager.generateShareElementForButtons(shareUrl, buttons, ShareManager.getDefaultGenerateCallback());
    }

    static generateShareElement(shareUrl, generateCallback) {
        return ShareManager.generateShareElementForButtons(shareUrl, ShareManager.shareButtons, generateCallback);
    }

    static generateShareElementForButtons(shareUrl, buttons, generateCallback) {
        let shareButtonElement = document.createElement("div");
        let currentDeviceType = ShareManager.getCurrentDeviceType();
        for (let i = 0, n = buttons.length; i < n; i++) {
            if (buttons[i].shouldShowFor(currentDeviceType)) {
                let elem = generateCallback(buttons[i], shareUrl);
                elem.onclick = function (event) {
                    buttons[i].getCallback()(shareUrl, this, event);
                };
                shareButtonElement.appendChild(elem);
            }
        }
        return shareButtonElement;
    }

    static getCurrentDeviceType() {
        if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
            return ShareButton.TYPE_MOBILE_APPLE;
        }
        else if ((navigator.userAgent.match(/Android|BlackBerry|Opera Mini|IEMobile/i) !== null || (typeof window.orientation !== "undefined"))) {
            return ShareButton.TYPE_MOBILE_LEFTOVER;
        }
        else {
            return ShareButton.TYPE_DESKTOP;
        }
    }

    static getDefaultGenerateCallback() {
        return function (button) {
            let linkElement = document.createElement("a");
            linkElement.classList.add("share-icon");
            let iconUrl = button.getIcon();
            if (typeof iconUrl === "string") {
                let iconElement = document.createElement("img");
                iconElement.src = Helper.basePath(button.getIcon());
                iconElement.classList.add("share-icon");
                linkElement.appendChild(iconElement);
            }
            else {
                Promise.resolve(iconUrl).then(elem => {
                    linkElement.appendChild(elem);
                });
            }

            return linkElement;
        }
    }
}

ShareManager.init();

AndroidBridge.addDefinition("ShareManager.addShareButton", ShareManager.addShareButton);

class SmsShareButton extends ShareButton
{
    constructor(icon, shouldLoadImg) {
        super(ShareButton.TYPE_MOBILE, icon, function (link) {
            let linkToOpen = "";
            if (ShareManager.getCurrentDeviceType() === ShareButton.TYPE_MOBILE_APPLE) {
                linkToOpen = "sms:&body="+encodeURIComponent(link);
            }
            else {
                linkToOpen = "sms:?&body=" + encodeURIComponent(link);
            }
            window.open(linkToOpen, '_blank', "noopener");
        }, shouldLoadImg);
    }
}

class TelegramShareButton extends ShareButton {
    constructor(icon, shouldLoadImg) {
        super(ShareButton.TYPE_ALL, icon, function (link) {
            let linkToOpen = "https://t.me/share/url?url="+encodeURIComponent(link);
            window.open(linkToOpen, '_blank', "noopener");
        }, shouldLoadImg);
    }
}

class WhatsappShareButton extends ShareButton {
    constructor(icon, shouldLoadImg) {
        super(ShareButton.TYPE_ALL, icon, function (link) {
            let linkToOpen = "";
            if (ShareManager.getCurrentDeviceType() === ShareButton.TYPE_DESKTOP) {
                linkToOpen = "https://web.whatsapp.com/send?text="+encodeURIComponent(link);
            }
            else {
                linkToOpen = "whatsapp://send?text=" + encodeURIComponent(link);
            }
            window.open(linkToOpen, '_blank', "noopener");
        }, shouldLoadImg);
    }
}

class SimpleWS{
    static _initMe(ws, url, protocols) {
        ws._connectionPromise = new Promise(function (resolve) {
            ws._ws = new WebSocket(url, protocols);
            ws._ws.onopen = function () {
                resolve();
                for (let i = 0, n = ws._openListeners.length; i < n; i++) {
                    if (Helper.isNotNull(ws._openListeners[i])) {
                        ws._openListeners[i]();
                    }
                }
            };

            ws._ws.onerror = function (err) {
                resolve(Promise.reject("WS-Error"));
                console.error(err);
                for (let i = 0, n = ws._errorListeners.length; i < n; i++) {
                    if (Helper.isNotNull(ws._errorListeners[i])) {
                        ws._errorListeners[i](err);
                    }
                }
            };

            ws._ws.onclose = function (e) {
                console.log("ws-close");
                for (let i = 0, n = ws._closeListeners.length; i < n; i++) {
                    if (Helper.isNotNull(ws._closeListeners[i])) {
                        ws._closeListeners[i](e, ws._shouldReconnectAfterClose);
                    }
                }
                if (ws._shouldReconnectAfterClose) {
                    setTimeout(function () {
                        SimpleWS._initMe(ws, url, protocols);
                    }, 200);
                }
            };

            ws._ws.onmessage = function (message) {
                if (message.data === "pong") {
                    ws.ping();
                    return;
                }

                let jsonMessage = JSON.parse(message.data);
                for (let i = 0, n = ws._messageListeners.length; i < n; i++) {
                    if (ws._messageListeners[i]) {
                        ws._messageListeners[i](jsonMessage, message);
                    }
                }
            };
        });
    }

    constructor(url, protocols, shouldReconnectAfterClose) {
        this._shouldReconnectAfterClose = Helper.nonNull(shouldReconnectAfterClose, true);
        this._ws = null;

        this._openListeners = [];
        this._errorListeners = [];
        this._messageListeners = [];
        this._closeListeners = [];

        this._connectionPromise = Promise.resolve();
        this._pong = null;
        this._ping = null;

        SimpleWS._initMe(this, url, protocols);
        this.activatePingPong();
    }

    ping(interval, answerTime) {
        interval = Helper.nonNull(interval, 5000);
        answerTime = Helper.nonNull(answerTime, 5000);
        if (this._pong !== null) {
            clearTimeout(this._pong);
        }
        if (this._ping !== null) {
            clearTimeout(this._ping);
        }

        let self = this;
        this._ping = setTimeout(function () {
            self.send({'action':"ping"});
            self._pong = setTimeout(function () {
                self._ws.close();
            }, answerTime);
        }, interval);
    }

    activatePingPong() {
        this.ping();
    }

    send(jsonMessage) {
        let ws = this;
        return this._connectionPromise.then(function () {
            ws._ws.send(JSON.stringify(jsonMessage));
        });
    }

    connectedPromise()
    {
        let self = this;
        return new Promise(function(resolve){
            self.addOpenListener(function(){
                resolve();
            }, true);
        })
    }

    promiseSend(jsonMessage)
    {
        let self = this;
        return this.connectedPromise().then(function(){
            return self.send(jsonMessage);
        });
    }

    close() {
        this._shouldReconnectAfterClose = false;
        this._ws.close();
    }

    addErrorListener(listener) {
        if (typeof listener === 'function') {
            return this._errorListeners.push(listener) - 1;
        }
        return -1;
    }

    removeErrorListener(listenerId) {
        if (listenerId < this._errorListeners.length && listenerId >= 0) {
            this._errorListeners[listenerId] = null;
        }
    }

    addCloseListener(listener, callNowIfClosed) {
        callNowIfClosed = Helper.nonNull(callNowIfClosed, false);
        if (typeof listener === 'function') {
            if (callNowIfClosed && this.status() !== WebSocket.OPEN) {
                listener();
            }
            return this._closeListeners.push(listener) - 1;
        }
        return -1;
    }

    removeCloseListener(listenerId) {
        if (listenerId < this._closeListeners.length && listenerId >= 0) {
            this._closeListeners[listenerId] = null;
        }
    }

    addOpenListener(listener, callNowIfOpen) {
        callNowIfOpen = Helper.nonNull(callNowIfOpen, false);
        if (typeof listener === 'function') {
            if (callNowIfOpen && this.status() === WebSocket.OPEN) {
                listener();
            }
            return this._openListeners.push(listener) - 1;
        }
        return -1;
    }

    removeOpenListener(listenerId) {
        if (listenerId < this._openListeners.length && listenerId >= 0) {
            this._openListeners[listenerId] = null;
        }
    }

    addMessageListener(listener) {
        if (typeof listener === 'function') {
            return this._messageListeners.push(listener) - 1;
        }
        return -1;
    }

    removeMessageListener(listenerId) {
        if (listenerId < this._messageListeners.length && listenerId >= 0) {
            this._messageListeners[listenerId] = null;
        }
    }

    status() {
        return this._ws.readyState;
    }
}

class Fragment extends Context
{
    constructor(site, view)
    {
        super(view);
        this.site = site;
        this.active = true;
    }

    getSite()
    {
        return this.site;
    }

    isActive()
    {
        return this.active;
    }
}

class Theme
{
    constructor(name, className, icon)
    {
        this._name = name;
        this._className = className;
        this._icon = icon;
    }
}

class DBTranslator extends Translator {
    _loadLanguage(language) {
        let self = this;
        return this._db.loadTranslationsForLang(language).then(function (res) {
            self._translations[language] = Object.assign(res, self._translations[language]);
        }).catch(function (err) {
            console.error("could not load lang " + language + " because of error: ", err);
        });
    }

    loadUserLanguage() {
        this._currentLanguage = null;
        let self = this;
        return this._db.getLanguage().then(function (userLanguage) {
            if (Helper.isNull(userLanguage) || self._supportedLanguages.indexOf(userLanguage) === -1) {
                let userLanguages = [];
                if (Helper.isNotNull(navigator.languages)) {
                    userLanguages = navigator.languages.slice(0); //.slice(0) klont das Array. Behebt einen Bug in Firefox
                }

                if (navigator.language !== undefined) {
                    userLanguages.push(navigator.language);
                }
                //sicherstellen, dass überhaupt eine Sprache gefunden wird
                userLanguages.push(self._baseLanguage);

                if (userLanguages !== undefined) {
                    for (let i = 0, numLanguages = userLanguages.length; i < numLanguages; i++) {
                        if (self._supportedLanguages.indexOf(userLanguages[i]) !== -1) {
                            userLanguage = userLanguages[i];
                            break;
                        }
                    }
                }
            }
            return self.setLanguage(userLanguage.toLowerCase())
        });
    }
}

function applyPolyfills() {
    if (!String.prototype.format) {
        String.prototype["format"] = function (args) {
            return this.replace(/{(\d+)}/g, function (match, number) {
                return args[number] !== undefined
                    ? args[number]
                    : match
                    ;
            });
        };
    }
    Object["assign"] = Helper.nonNull(Object["assign"], function (base, obj) {
        base = Helper.nonNull(base, {});
        if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
            return base;

        // if (obj instanceof Date) {
        //     temp = new obj.constructor(); //or new Date(obj);
        // }
        // else {
        //     temp = obj.constructor();
        // }

        for (let key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                obj['isActiveClone'] = null;
                base[key] = obj[key];
                delete obj['isActiveClone'];
            }
        }

        return base;
    });

    if (typeof window !== 'undefined') {
        if (Helper.isNotNull(window["Node"]) && !window["Node"]["prototype"]["removeAllChildren"]) {
            Node.prototype["removeAllChildren"] = function () {
                while (this.firstChild) {
                    this.removeChild(this.firstChild);
                }
                return this;
            };
        }

        if (HTMLElement) {
            HTMLElement.prototype["fadeOut"] = Helper.nonNull(HTMLElement.prototype["fadeOut"],
                function (time, effect, delay) {
                    time = Helper.nonNull(time, 0.5);
                    effect = Helper.nonNull(effect, "ease-in-out");
                    delay = Helper.nonNull(delay, 0);
                    this.style.transition = "opacity " + time + "s " + effect + " " + delay + "s";
                    let elem = this;
                    let animPromise = new Promise(function (resolve) {
                        let transEndLis = function (e) {
                            elem.removeEventListener("transitionend", transEndLis);
                            elem.removeEventListener("transitioncancel", transCancelledLis);
                            elem.style.opacity = null;
                            elem.style.transition = null;
                            resolve(true, e);
                        };

                        let transCancelledLis = function (e) {
                            elem.removeEventListener("transitionend", transEndLis);
                            elem.removeEventListener("transitioncancel", transCancelledLis);
                            elem.style.opacity = null;
                            elem.style.transition = null;
                            resolve(false, e);
                        };
                        elem.addEventListener("transitionend", transEndLis);
                        elem.addEventListener("transitioncancel", transCancelledLis);
                        //Fallback
                        setTimeout(() => {
                            resolve(false);
                        }, (time + delay) * 1000);
                    });

                    //Nach Seitenneuzeichnen, damit chrome das immer macht (und FF auch)
                    requestAnimationFrame(function () {
                        requestAnimationFrame(function () {
                            elem.style.opacity = 0;
                        });
                    });
                    return animPromise
                });

            HTMLElement.prototype["fadeIn"] = Helper.nonNull(HTMLElement.prototype["fadeIn"], function (time, effect, delay) {
                time = Helper.nonNull(time, 0.5);
                effect = Helper.nonNull(effect, "ease-in-out");
                delay = Helper.nonNull(delay, 0);
                this.style.transition = "opacity " + time + "s " + effect + " " + delay + "s";

                let elem = this;
                let animPromise = new Promise(function (resolve) {
                    let transEndLis = function (e) {
                        elem.removeEventListener("transitionend", transEndLis);
                        elem.removeEventListener("transitioncancel", transCancelledLis);
                        elem.style.opacity = null;
                        elem.style.transition = null;
                        resolve(true, e);
                    };

                    let transCancelledLis = function (e) {
                        elem.removeEventListener("transitionend", transEndLis);
                        elem.removeEventListener("transitioncancel", transCancelledLis);
                        elem.style.opacity = null;
                        elem.style.transition = null;
                        resolve(false, e);
                    };
                    elem.addEventListener("transitionend", transEndLis);
                    elem.addEventListener("transitioncancel", transCancelledLis);

                    if (getComputedStyle(elem).getPropertyValue("opacity") === "1") {
                        resolve(false);
                    }
                    //Fallback
                    setTimeout(() => {
                        resolve(false);
                    }, (time + delay) * 1000);

                    //Nach Seitenneuzeichnen, damit chrome das immer macht (und FF auch)
                    requestAnimationFrame(function () {
                        requestAnimationFrame(function () {
                            elem.style.opacity = 1;
                        });
                    });
                });
                return animPromise;
            });
        }

        if (Node) {
            Node.prototype["replaceWith"] = Helper.nonNull(Node.prototype["replaceWith"], function (elem) {
                this.parentElement.replaceChild(elem, this);
            });
            Node.prototype["remove"] = Helper.nonNull(Node.prototype["remove"], function () {
                this.parentElement.removeChild(this);
            });
        }

        if (Element) {
            Element.prototype.matches = Helper.nonNull(Element.prototype.matches, Helper.nonNull(Element.prototype["matchesSelector"], Element.prototype["webkitMatchesSelector"]));

            window["Element"]["prototype"]["closest"] = Helper.nonNull(window["Element"]["prototype"]["getAll"], function (s) {
                // if (!Element.prototype.matches)
                //     Element.prototype.matches = Element.prototype.msMatchesSelector ||
                //         Element.prototype.webkitMatchesSelector;
                //
                // if (!Element.prototype.closest)
                //     Element.prototype.closest = function(s) {
                let el = this;
                if (!document.documentElement.contains(el)) return null;
                do {
                    if (el.matches(s)) return el;
                    el = el.parentElement;
                } while (el !== null);
                return null;
                // };
            });
        }

        window["IDBObjectStore"]["prototype"]["getAll"] = Helper.nonNull(window["IDBObjectStore"]["prototype"]["getAll"], function () {
            let res = {};
            let items = [];
            this.openCursor().onsuccess = function (e) {
                let cursor = e.target.result;
                if (Helper.isNotNull(cursor)) {
                    items.push(cursor.value);
                    cursor.continue();
                }
                else if (Helper.isNotNull(res.onsuccess)) {
                    res.onsuccess({currentTarget: {result: items}});
                }
            };
            return res;
        });
    }

    String.prototype.startsWith = Helper.nonNull(String.prototype.startsWith, function (searchString, position) {
        position = position || 0;
        return this.indexOf(searchString, position) === position;
    });

    String.prototype.includes = Helper.nonNull(String.prototype.includes, function (searchString) {
        return this.indexOf(searchString) >= 0;
    });

    String.prototype.endsWith = Helper.nonNull(String.prototype.endsWith, function (searchString, position) {
        var subjectString = this.toString();
        if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
            position = subjectString.length;
        }
        position -= searchString.length;
        var lastIndex = subjectString.indexOf(searchString, position);
        return lastIndex !== -1 && lastIndex === position;
    });


    window["fetch"] = Helper.nonNull(window["fetch"], function (url, params) {
        console.log("customFetch", url);
        let request = null;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // IE
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                    request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                catch (e) {
                }
            }
        }

        let onloadPromise = new Promise((r) => {
            // this.result
            request.onload = r;
            request.onerror = function (err) {
                r(Promise.reject(err));
            };
        });

        let resultPromise = new Promise(function (resolve) {

            let response = {
                "json": function () {
                    request.send();

                    return onloadPromise.then(() => {
                        return JSON.parse(request.responseText)
                    });
                },
                "text": function () {
                    request.send();
                    return onloadPromise.then(() => request.responseText);
                },
                "arrayBuffer": () => {
                    request.responseType = "arraybuffer";
                    request.send();
                    return onloadPromise.then(() => request.response);
                }
            };
            resolve(response);
        });


        request.open('get', url, true);
        // request.send();
        return resultPromise;
    });
}

export { AndroidBridge, App, ChainAble, CookieCompliance, ConfirmDialog, Dialog, FlashMessenger, AbstractService, AbstractGapiResponse, Achievement, AchievementList, GameService, GapiPlayer, Leaderboard, Score, GapiHandler, Helper, InitPromise, ActionBarMenu, Menu, MenuAction, OpenSubmenuAction, Submenu, MyDb, Prioritised, ScriptLoader, CopyShareButton, MultipleShareButton, ShareButton, ShareManager, SmsShareButton, TelegramShareButton, WhatsappShareButton, SimpleWS, AbstractSite, Context, Fragment, PauseSite, SiteContainer, SiteManager, SystemSettings, Theme, ThemeManager, DBTranslator, Translator, TranslatorDB, ViewInflater, applyPolyfills };
