/*
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import {WidgetPrototype} from "./widget/widget-prototype";
import {Globals} from "./domain/globals";
import {UhrRenderer} from "./renderer";
import {EMPTY_LAYOUT, Layout} from "./domain/layout";
import * as Cookies from "js-cookie";

export class Uhr {

    private timer: number = null;
    private currentMinute: number = null;

    constructor(private widgetInstance: WidgetPrototype) {
        const userTime = this.widgetInstance.options.time;
        if (this.widgetInstance.options.time === undefined) {
            this.widgetInstance.options.time = new Date();
        }
        this.parseHash();
        this.setupHTML();
        this.wireFunctionality();
        if (userTime !== undefined) {
            this.setTime(userTime);
        }
    }

    destroy(): void {
        if (!!this.timer) {
            window.clearInterval(this.timer);
            this.timer = null;
        }
        this.widgetInstance.element
            .removeAttr('style')
            .removeAttr('class')
            .empty();
        $(`#uhr-configlink${this.widgetInstance.uuid}`).remove();
        $(`#uhr-controlpanel${this.widgetInstance.uuid}`).remove();
    }

    start(): void {
        if (!this.isOn()) {
            this.timer = window.setInterval(() => {
                this.widgetInstance.options.time = new Date();
                this.update();
            }, 1000);
            this.update();
            this.setCookie('uhr-status', 'on');
        }
    }

    stop(): void {
        if (this.isOn()) {
            window.clearInterval(this.timer);
            this.timer = null;
            this.update();
            this.setCookie('uhr-status', 'off');
        }
    }

    toggle(): void {
        if (this.isOn()) {
            this.stop();
        } else {
            this.start();
        }
    }

    setLanguage(key: string): void {
        if (key !== this.widgetInstance.options.language) {
            this.widgetInstance.options.language = key;
            const renderer = new UhrRenderer(this.getCurrentLayout(), this.widgetInstance.element.find('.letterarea'));
            renderer.render(() => {
                this.currentMinute = -1;
                this.update();
            });
            this.setCookie('uhr-language', key);
            this.update();
        }
    }

    setTheme(styleClass: string): void {
        if (styleClass !== this.widgetInstance.options.theme) {
            this.widgetInstance.element.removeClass(this.widgetInstance.options.theme).addClass(styleClass);
            $(`#uhr-onoffswitch${this.widgetInstance.uuid}`).removeClass(this.widgetInstance.options.theme).addClass(styleClass);
            this.widgetInstance.options.theme = styleClass;
            this.setCookie('uhr-theme', styleClass);
        }
    }

    setTime(time: Date): void {
        this.currentMinute = null;
        if (time === null) {
            this.widgetInstance.options.time = new Date();
        } else {
            if (this.timer !== null) {
                window.clearInterval(this.timer);
            }
            this.widgetInstance.options.time = time;
        }
        this.update();
    }

    setMode(mode: string): void {
        this.widgetInstance.options.mode = mode;
        this.currentMinute = null;
        this.update();
        this.setCookie('uhr-mode', mode);
    }

    setWidth(width: string): void {
        const e = this.widgetInstance.element;
        e.css('width', width);
        const realWidth = e.width();
        e.width(realWidth);
        e.height(realWidth);
        e.css('font-size', (realWidth / 40) + 'px');
    }

    private setupHTML(): void {
        const e: JQuery<HTMLElement> = this.widgetInstance.element;
        // Base clock area
        e.addClass('uhr')
            .empty()
            .append('<span class="item dot dot1"></span>')
            .append('<span class="item dot dot2"></span>')
            .append('<span class="item dot dot3"></span>')
            .append('<span class="item dot dot4"></span>')
            .append('<div class="letterarea"></div>')
            .append('<div class="reflection"></div>');

        this.setWidth(this.widgetInstance.options.width);

        if (this.widgetInstance.options.controls) {
            const controlpanel = $(`<div class="uhr-controlpanel" id="uhr-controlpanel${this.widgetInstance.uuid}"></div>`);
            const content = $('<div class="content"></div>');
            controlpanel.append(content);
            // on/off switch
            const toggleSwitch = $(`<div class="onoffswitch" id="uhr-onoffswitch${this.widgetInstance.uuid}"></div>`);
            toggleSwitch.append(`<input type="checkbox" class="onoffswitch-checkbox" id="uhr-onoffswitch-checkbox${this.widgetInstance.uuid}" checked="checked" />`);
            toggleSwitch.append(`<label class="onoffswitch-label" for="uhr-onoffswitch-checkbox${this.widgetInstance.uuid}"><div class="onoffswitch-inner"></div><div class="onoffswitch-switch"></div></label>`);
            content.append(toggleSwitch);

            // time mode switch
            const modeSwitch = $(`<div class="onoffswitch" id="uhr-modeswitch${this.widgetInstance.uuid}"></div>`);
            modeSwitch.append(`<input type="checkbox" class="onoffswitch-checkbox" id="uhr-modeswitch-checkbox${this.widgetInstance.uuid}" checked="checked" />`);
            modeSwitch.append(`<label class="onoffswitch-label" for="uhr-modeswitch-checkbox${this.widgetInstance.uuid}"><div class="modeswitch-inner"></div><div class="onoffswitch-switch"></div></label>`);
            content.append(modeSwitch);
            // language chooser
            if (Globals.hasMultipleLayouts()) {
                const languageChooser = $(`<select id="uhr-languagechooser${this.widgetInstance.uuid}"></select>`);
                Globals.getLayouts().forEach(layout => {
                    languageChooser.append(`<option value="${layout.code}">${layout.prettyName}</option>`);
                });
                content.append(languageChooser);
            }

            // theme chooser
            if (Globals.hasMultipleThemes()) {
                const themeChooser = $(`<select id="uhr-themechooser${this.widgetInstance.uuid}"></select>`);
                Globals.getThemes().forEach(theme => {
                    themeChooser.append(`<option value="${theme.styleClass}">${theme.name}</option>`);
                });
                content.append(themeChooser);
            }
            const closebutton = $(`<a class="uhr-closecontrolpanel" id="uhr-closecontrolpanel${this.widgetInstance.uuid}"></a>`);
            //FIXME deprecated?!
            closebutton.on('click', () => $(`#uhr-controlpanel${this.widgetInstance.uuid}`).hide('fast'));
            content.append(closebutton);
            e.after(controlpanel);
            controlpanel.hide();
            const configlink = $(`<a class="uhr-configlink" id="uhr-configlink${this.widgetInstance.uuid}"></a>`);
            // FIXME deprecated!?
            configlink.on('click', () => this.toggleConfigScreen());
            e.after(configlink);
        }
    };

    private wireFunctionality(): void {
        // on/off switch
        const toggleSwitch = $(`#uhr-onoffswitch-checkbox${this.widgetInstance.uuid}`);
        // FIXME deprecated!?
        toggleSwitch.on('click', () => this.toggle());
        let status = this.getCookie('uhr-status');
        if (status === undefined || this.widgetInstance.options.force) {
            status = this.widgetInstance.options.status;
        }
        toggleSwitch.prop('checked', status === 'on');
        if (status === 'on') {
            this.start();
        } else {
            this.stop();
        }

        // time mode switch
        const modeSwitch = $(`#uhr-modeswitch-checkbox${this.widgetInstance.uuid}`);
        // FIXME deprecated!?
        modeSwitch.on('click', () => {
            if (this.widgetInstance.options.mode === 'seconds') {
                this.setMode('normal');
            } else {
                this.setMode('seconds');
            }
        });

        let mode = this.getCookie('uhr-mode');
        if (mode === undefined || this.widgetInstance.options.force) {
            mode = this.widgetInstance.options.mode;
        }
        modeSwitch.prop('checked', mode !== 'seconds');
        if (mode === 'seconds') {
            this.setMode('seconds');
        } else {
            this.setMode('normal');
        }

        // language chooser
        const languageChooser = $(`#uhr-languagechooser${this.widgetInstance.uuid}`);
        // FIXME deprecated!?
        languageChooser.on('change', () => {
            const languageKey = $(`#uhr-languagechooser${this.widgetInstance.uuid}`).val() as string;
            this.setLanguage(languageKey);
        });
        let selectedLayout = this.getCookie('uhr-language');
        if (selectedLayout === undefined || this.widgetInstance.options.force) {
            selectedLayout = this.widgetInstance.options.language;
        }
        let found = Globals.getLayouts().some(item => selectedLayout === item.code);
        if (!found) {
            let fallbackLanguage;
            if (Globals.hasLayouts()) {
                fallbackLanguage = Globals.getFirstLayout().code;
            } else {
                fallbackLanguage = '';
            }
            console.warn(`Language '${selectedLayout}' not found! Using fallback '${fallbackLanguage}'.`);
            selectedLayout = fallbackLanguage;
        }
        languageChooser.val(selectedLayout);
        this.widgetInstance.options.language = "";
        this.setLanguage(selectedLayout);

        // theme chooser
        const themeChooser = $(`#uhr-themechooser${this.widgetInstance.uuid}`);
        // FIXME deprecated!?
        themeChooser.on('change', () => {
            const themeKey = $(`#uhr-themechooser${this.widgetInstance.uuid}`).val() as string;
            this.setTheme(themeKey);
        });
        let selectedTheme = this.getCookie('uhr-theme');
        if (selectedTheme === undefined || this.widgetInstance.options.force) {
            selectedTheme = this.widgetInstance.options.theme;
        }
        found = Globals.getThemes().some(item => selectedTheme === item.styleClass);
        if (!found) {
            const fallbackTheme = Globals.getFirstTheme().styleClass;
            console.warn(`Theme '${selectedTheme}' not found! Using fallback '${fallbackTheme}'.`);
            selectedTheme = fallbackTheme;
        }
        themeChooser.val(selectedTheme);
        this.widgetInstance.options.theme = "";
        this.setTheme(selectedTheme);
        if (this.widgetInstance.options.autoresize) {
            // FIXME deprecated!?
            $(window).on('resize', () => {
                const $e = this.widgetInstance.element;
                const $parent = $e.parent();
                const $window = $(window);
                const parentWidth = $parent.width();
                const parentHeight = $parent.height();
                const windowWidth = $window.width();
                const windowHeight = $window.height();
                const size = `${Math.min(parentWidth, parentHeight, windowWidth, windowHeight)}px`;
                this.setWidth(size);
            });
        }
    }

    private isOn(): boolean {
        return this.timer !== null;
    }

    private getCookie(cookieName: string): string {
        return Cookies.get(cookieName + this.widgetInstance.uuid);
    }

    private setCookie(cookieName: string, cookieValue: string): void {
        let options;
        if (this.widgetInstance.options.cookiePath !== undefined) {
            options = {expires: 365, path: this.widgetInstance.options.cookiePath};
        } else {
            options = {expires: 365};
        }
        Cookies.set(cookieName + this.widgetInstance.uuid, cookieValue, options);
    }

    private update(): void {
        if (this.isOn()) {
            const time = this.widgetInstance.options.time;
            if (!this.getCurrentLayout().hasOwnProperty('seconds') && this.widgetInstance.options.mode !== 'seconds') {
                if (time.getMinutes() === this.currentMinute) {
                    return;
                }
                this.currentMinute = time.getMinutes();
            }
            this.show(time);
        } else {
            this.clear();
            this.currentMinute = -1;
        }
    }

    private show(time: Date): void {
        const second = this.getSecond(time);
        const dotMinute = this.getDotMinute(time);
        const hour = this.getHour(time);
        const coarseMinute = this.getCoarseMinute(time);
        this.clear();
        if (this.widgetInstance.options.mode === 'seconds') {
            this.highlight(`second${second}`);
        } else {
            this.highlight('on');
            for (let i = 1; i <= dotMinute; i++) {
                this.highlight(`dot${i}`);
            }
            this.highlight(`minute${coarseMinute}`);
            this.highlight(`hour${hour}`);
        }
    }

    private clear(): void {
        this.widgetInstance.element.find('.item').removeClass('active');
    }

    private highlight(itemClass: string): void {
        this.widgetInstance.element.find(`.item.${itemClass}`).addClass('active');
    }

    private getSecond(time: Date): number {
        if (typeof this.getCurrentLayout().getSeconds === 'function') {
            return this.getCurrentLayout().getSeconds(time);
        }
        return time.getSeconds();
    };

    private getDotMinute(date: Date): number {
        if (typeof this.getCurrentLayout().getDotMinute === 'function') {
            return this.getCurrentLayout().getDotMinute(date);
        }
        return date.getMinutes() % 5;
    };

    private getCoarseMinute(date: Date): number {
        if (typeof this.getCurrentLayout().getCoarseMinute === 'function') {
            return this.getCurrentLayout().getCoarseMinute(date);
        }
        return date.getMinutes();
    };

    private getHour(date: Date): number {
        if (typeof this.getCurrentLayout().getHour === 'function') {
            return this.getCurrentLayout().getHour(date);
        }
        const hour = date.getHours();
        if (date.getMinutes() >= 25) {
            return (hour + 1) % 24;
        }
        return hour;
    };

    private toggleConfigScreen() {
        $(`#uhr-controlpanel${this.widgetInstance.uuid}`).toggle('fast');
    };

    private parseHash(): void {
        let hash: string = window.location.hash;
        if (hash !== undefined && hash.charAt(0) === '#') {
            hash = hash.substring(1);
            hash = decodeURIComponent(hash);
            const params: string[] = hash.split('&');
            params.forEach(element => {
                const pair: string[] = element.split('=');
                const key = pair[0];
                const value = pair[1];
                switch (key) {
                    case 'l':
                    case 'language':
                        this.widgetInstance.options.language = value;
                        this.widgetInstance.options.force = true;
                        break;
                    case 't':
                    case 'theme':
                        this.widgetInstance.options.theme = value;
                        this.widgetInstance.options.force = true;
                        break;
                    case 'm':
                    case 'mode':
                        this.widgetInstance.options.mode = value;
                        this.widgetInstance.options.force = true;
                        break;
                    case 's':
                    case 'status':
                        this.widgetInstance.options.status = value;
                        this.widgetInstance.options.force = true;
                        break;
                }
            });
        }
    }

    private getCurrentLayout(): Layout {
        const matchingLanguages: Layout[] = Globals.getLayouts().filter(element => element.code === this.widgetInstance.options.language, this);
        if (matchingLanguages.length > 0) {
            return matchingLanguages[0];
        }
        // fallback: return empty object
        return EMPTY_LAYOUT;
    };
}