/*
 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/>.
 */
(function ($) {
    'use strict';
    var uhrGlobals = {
        "id": 0,
        "languages": [],
        "themes": [],
        registerLanguage: function registerLanguage(code, language) {
            var alreadyExists = uhrGlobals.languages.some(function (element) {
                if (code === element.code) {
                    console.error("Error: Language code '" + code + "' cannot be registered for language '" + language.language + "' because it is already registered for language '" + element.language + "'!");
                    return true;
                }
                return false;
            });
            if (!alreadyExists) {
                language.code = code;
                uhrGlobals.languages.push(language);
            }
        }
    };

    // auto-detect themes
    $('link[rel=stylesheet]').each(function (index, item) {
        var styleSheet = $(item);
        var styleClass = styleSheet.attr('data-class');
        if (styleClass !== undefined) {
            var name = styleSheet.attr('data-name');
            if (name === undefined) {
                name = styleClass;
            }
            uhrGlobals.themes.push({'styleClass': styleClass, 'name': name});
        }
    });
    // fall-back if no theme was included
    if (uhrGlobals.themes.length === 0) {
        uhrGlobals.themes.push({});
    }

    // public interface methods (exported later)
    var start = function start() {
        if (!isOn.bind(this)()) {
            this.timer = window.setInterval(function () {
                this.options.time = new Date();
                update.bind(this)();
            }.bind(this), 1000);
            update.bind(this)();
            setCookie.bind(this)('uhr-status', 'on');
        }
    };
    var stop = function stop() {
        if (isOn.bind(this)()) {
            window.clearInterval(this.timer);
            this.timer = null;
            update.bind(this)();
            setCookie.bind(this)('uhr-status', 'off');
        }
    };
    var toggle = function toggle() {
        if (isOn.bind(this)()) {
            this.stop();
        } else {
            this.start();
        }
    };
    var setLanguage = function setLanugage(languageKey) {
        if (languageKey !== this.options.language) {
            this.options.language = languageKey;
            var renderer = new UhrRenderer(language.bind(this)(), this.element.find('.letterarea'));
            renderer.render.bind(this)(function () {
                this.currentMinute = -1;
                update.bind(this)();
            }.bind(this));
            setCookie.bind(this)('uhr-language', languageKey);
            update.bind(this)();
        }
    };
    var setTheme = function setTheme(theme) {
        if (theme !== this.options.theme) {
            this.element.removeClass(this.options.theme).addClass(theme);
            $('#uhr-onoffswitch' + this.id).removeClass(this.options.theme).addClass(theme);
            this.options.theme = theme;
            setCookie.bind(this)('uhr-theme', theme);
        }
    };
    var setTime = function setTime(time) {
        this.currentMinute = -1;
        if (time === null) {
            this.options.time = new Date();
        } else {
            if (this.timer !== null) {
                window.clearInterval(this.timer);
            }
            this.options.time = time;
        }
        update.bind(this)();
    };
    var setWidth = function setWidth(width) {
        var e = this.element;
        e.css('width', width);
        var realWidth = e.width();
        e.width(realWidth);
        e.height(realWidth);
        e.css('font-size', (realWidth / 40) + 'px');
    };

    // private interface methods
    var create = function create() {
        this.id = uhrGlobals.id++;
        this.timer = null;
        this.currentMinute = -1;
        var userTime = this.options.time;
        if (this.options.time === undefined) {
            this.options.time = new Date();
        }
        setupHTML.bind(this)();
        wireFunctionality.bind(this)();
        if (userTime !== undefined) {
            this.time(userTime);
        }
    };
    // private helper methods (not exported)
    var showConfigScreen = function showConfigScreen() {
        $('#uhr-controlpanel' + this.id).fadeIn('fast');
    };
    // set up
    var setupHTML = function setupHTML() {
        var e = this.element;
        // Base clock area
        e.addClass('uhr');
        e.empty();
        e.append('<span class="item dot dot1"></span>');
        e.append('<span class="item dot dot2"></span>');
        e.append('<span class="item dot dot3"></span>');
        e.append('<span class="item dot dot4"></span>');
        e.append('<div class="letterarea"></div>');
        e.append('<div class="reflection"></div>');
        setWidth.bind(this)(this.options.width);

        if (this.options.controls) {
            var configlink = $('<a class="uhr-configlink" id="uhr-configlink' + this.id + '"></a>');
            configlink.on('click', function () {
                showConfigScreen.bind(this)();
            }.bind(this));
            e.after(configlink);
            var controlpanel = $('<div class="uhr-controlpanel" id="uhr-controlpanel' + this.id + '"></div>');
            var content = $('<div class="content"></div>');
            controlpanel.append(content);
            // on/off switch
            var toggleSwitch = $('<div class="onoffswitch" id="uhr-onoffswitch' + this.id + '"></div>');
            toggleSwitch.append('<input type="checkbox" class="onoffswitch-checkbox" id="uhr-onoffswitch-checkbox' + this.id + '" checked="checked" />');
            toggleSwitch.append('<label class="onoffswitch-label" for="uhr-onoffswitch-checkbox' + this.id + '">'
                + '<div class="onoffswitch-inner"></div>'
                + '<div class="onoffswitch-switch"></div>'
                + '</label>');
            content.append(toggleSwitch);

            // language chooser
            if (uhrGlobals.languages.length > 1) {
                var languageChooser = $('<select id="uhr-languagechooser' + this.id + '"></select>');
                uhrGlobals.languages.forEach(function (item) {
                    languageChooser.append('<option value="' + item.code + '">' + item.language + '</option>');
                });
                content.append(languageChooser);
            }

            // theme chooser
            if (uhrGlobals.themes.length > 1) {
                var themeChooser = $('<select id="uhr-themechooser' + this.id + '"></select>');
                uhrGlobals.themes.forEach(function (item) {
                    themeChooser.append('<option value="' + item.styleClass + '">' + item.name + '</option>');
                });
                content.append(themeChooser);
            }
            var closebutton = $('<a class="uhr-closecontrolpanel" id="uhr-closecontrolpanel' + this.id + '"></a>');
            closebutton.on('click', function () {
                $('#uhr-controlpanel' + this.id).fadeOut('fast');
            }.bind(this));
            content.append(closebutton);
            e.after(controlpanel);
            controlpanel.hide();
        }
    };
    var wireFunctionality = function wireFunctionality() {
        // on/off switch
        var toggleSwitch = $('#uhr-onoffswitch-checkbox' + this.id);
        toggleSwitch.on('click', function () {
            this.toggle();
        }.bind(this));
        var status = $.cookie('uhr-status' + this.id);
        if (status === undefined || this.options.force) {
            status = this.options.status;
        }
        toggleSwitch.prop('checked', status === 'on');
        if (status === 'on') {
            this.start();
        } else {
            this.stop();
        }

        // language chooser
        var languageChooser = $('#uhr-languagechooser' + this.id);
        languageChooser.on('change', function () {
            var languageKey = $('#uhr-languagechooser' + this.id).val();
            this.language(languageKey);
        }.bind(this));
        var selectedLanguage = $.cookie('uhr-language' + this.id);
        if (selectedLanguage === undefined || this.options.force) {
            selectedLanguage = this.options.language;
        }
        var found = uhrGlobals.languages.some(function (item) {
            return selectedLanguage === item.code;
        });
        if (!found) {
            var fallbackLanguage;
            if (uhrGlobals.languages.length > 0) {
                fallbackLanguage = uhrGlobals.languages[0].code;
            } else {
                fallbackLanguage = '';
            }
            console.warn("Language '" + selectedLanguage + "' not found! Using fallback '" + fallbackLanguage + "'");
            selectedLanguage = fallbackLanguage;
        }
        languageChooser.val(selectedLanguage);
        this.options.language = "";
        this.language(selectedLanguage);

        // theme chooser
        var themeChooser = $('#uhr-themechooser' + this.id);
        themeChooser.on('change', function () {
            var themeKey = $('#uhr-themechooser' + this.id).val();
            this.theme(themeKey);
        }.bind(this));
        var selectedTheme = $.cookie('uhr-theme' + this.id);
        if (selectedTheme === undefined || this.options.force) {
            selectedTheme = this.options.theme;
        }
        found = uhrGlobals.themes.some(function (item) {
            return selectedTheme === item.styleClass;
        });
        if (!found) {
            var fallbackTheme = uhrGlobals.themes[0].styleClass;
            console.warn("Theme '" + selectedTheme + "' not found! Using fallback '" + fallbackTheme + "'");
            selectedTheme = fallbackTheme;
        }
        themeChooser.val(selectedTheme);
        this.options.theme = "";
        this.theme(selectedTheme);
        if (this.options.autoresize) {
            $(window).on('resize', function () {
                var $e = this.element;
                var $parent = $e.parent();
                var $window = $(window);
                var parentWidth = $parent.width();
                var parentHeight = $parent.height();
                var windowWidth = $window.width();
                var windowHeight = $window.height();
                var size = Math.min(parentWidth, parentHeight, windowWidth, windowHeight) + 'px';
                setWidth.bind(this)(size);
            }.bind(this));
        }
    };
    var setCookie = function setCookie(cookieName, cookieValue) {
        var options = {};
        if (this.options.cookiePath !== undefined) {
            options = {expires: 365, path: this.options.cookiePath};
        } else {
            options = {expires: 365};
        }
        $.cookie(cookieName + this.id, cookieValue, options);
    };

    // business logic
    var isOn = function isOn() {
        return this.timer !== null;
    };
    var update = function update() {
        if (isOn.bind(this)()) {
            var time = this.options.time;
            if (!language.bind(this)().hasOwnProperty('seconds')) {
                if (time.getMinutes() === this.currentMinute) {
                    return;
                }
                this.currentMinute = time.getMinutes();
            }
            show.bind(this)(time);
        } else {
            clear.bind(this)();
            this.currentMinute = -1;
        }
    };
    var show = function show(time) {
        var second = getSecond.bind(this)(time);
        var dotMinute = getDotMinute.bind(this)(time);
        var hour = getHour.bind(this)(time);
        var coarseMinute = getCoarseMinute.bind(this)(time);
        clear.bind(this)();
        highlight.bind(this)('on');
        for (var i = 1; i <= dotMinute; i++) {
            highlight.bind(this)('dot' + i);
        }
        highlight.bind(this)('second' + second);
        highlight.bind(this)('minute' + coarseMinute);
        highlight.bind(this)('hour' + hour);
    };
    var highlight = function highlight(itemClass) {
        this.element.find('.item.' + itemClass).addClass('active');
    };
    var clear = function clear() {
        this.element.find('.item').removeClass('active');
    };
    var getSecond = function getSecond(date) {
        if (typeof language.bind(this)().getSeconds === 'function') {
            return language.bind(this)().getSeconds(date);
        }
        return date.getSeconds();
    };
    var getDotMinute = function getDotMinute(date) {
        if (typeof language.bind(this)().getDotMinute === 'function') {
            return language.bind(this)().getDotMinute(date);
        }
        var minutes = date.getMinutes();
        return minutes % 5;
    };
    var getCoarseMinute = function getCoarseMinute(date) {
        if (typeof language.bind(this)().getCoarseMinute === 'function') {
            return language.bind(this)().getCoarseMinute(date);
        }
        return date.getMinutes();
    };
    var getHour = function getHour(date) {
        if (typeof language.bind(this)().getHour === 'function') {
            return language.bind(this)().getHour(date);
        }
        var hour = date.getHours();
        if (date.getMinutes() >= 25) {
            return (hour + 1) % 24;
        }
        return hour;
    };

    var language = function language() {
        var matchingLanguages = uhrGlobals.languages.filter(function (element) {
            return (element.code === this.options.language);
        }, this);
        if (matchingLanguages.length > 0) {
            return matchingLanguages[0];
        }
        // fallback: return empty object
        return {};
    };

    $.widget("fritteli.uhr", {
        "options": {
            width: '100%',
            status: 'on',
            language: 'de_CH',
            theme: uhrGlobals.themes[0].styleClass,
            force: false,
            controls: true,
            cookiePath: undefined,
            autoresize: true
        },
        "start": start,
        "stop": stop,
        "toggle": toggle,
        "language": setLanguage,
        "theme": setTheme,
        "time": setTime,
        "width": setWidth,
        // constructor method
        "_create": create
    });
    $.fritteli.uhr.register = uhrGlobals.registerLanguage;
    /**
     * Hilfsklasse zum Rendern der Uhr.
     * @param layout     Layout-Objekt, das gerendert werden soll.
     * @param renderarea Das jQuery-gewrappte HTML-Element, auf dem gerendert werden soll.
     */
    function UhrRenderer(layout, renderarea) {
        this.render = function render(beforeshow) {
            if (layout.parsed === undefined) {
                switch (layout.version) {
                    case 2:
                        var delegate = new UhrRendererV2Delegate(layout);
                        var parsedLayout = delegate.parse();
                        Object.defineProperty(layout, "parsed", {"value": parsedLayout, "writable": false, "configurable": false});
                        break;
                    default:
                        console.warn("Unknown layout version: '" + layout.version + "'");
                        return;
                }
            }
            var letters = layout.parsed;
            renderarea.fadeOut('fast', function () {
                renderarea.empty();
                letters.forEach(function (line, index, array) {
                    line.forEach(function (letter) {
                        renderarea.append(letter.toString());
                    });
                    if (index < array.length - 1) {
                        renderarea.append('<br/>');
                    }
                });
                if (typeof beforeshow === 'function') {
                    beforeshow();
                }
                renderarea.fadeIn('fast');
            });
        };
    }

    function UhrRendererV2Delegate(layout) {
        function parseArrayOrObject(letters, styleClass, input) {
            if (typeof input !== 'undefined' && input !== null) {
                if (Array.isArray(input)) {
                    input.forEach(function (item) {
                        parseObject(letters, styleClass, item);
                    });
                } else {
                    parseObject(letters, styleClass, input);
                }
            }
        }

        function parseObject(letters, styleClass, object) {
            if (typeof object !== 'undefined' && object !== null) {
                Object.keys(object).forEach(function (y) {
                    var highlightLetters = object[y];
                    highlightLetters.forEach(function (x) {
                        letters[y - 1][x - 1].addStyle(styleClass);
                    });
                });
            }
        }

        function parseTimeDefinition(letters, styleClass, definition) {
            if (typeof definition !== 'undefined' && definition !== null) {
                Object.keys(definition).forEach(function (listString) {
                    var array = listString.split(',');
                    var highlightLetters = definition[listString];
                    array.forEach(function (item) {
                        parseArrayOrObject(letters, styleClass + item, highlightLetters);
                    });
                });
            }
        }

        this.parse = function parse() {
            var letters = [];
            layout.letters.forEach(function (string) {
                var line = [];
                for (var c = 0; c < string.length; c++) {
                    var character = new Letter(string[c]);
                    line.push(character);
                }
                letters.push(line);
            });
            parseArrayOrObject(letters, 'on', layout.permanent);
            parseTimeDefinition(letters, 'second', layout.seconds);
            parseTimeDefinition(letters, 'minute', layout.minutes);
            parseTimeDefinition(letters, 'hour', layout.hours);
            return letters;
        };
    }

    /**
     * Ein Buchstabe. Hilfsklasse für den Renderer und Inhalt der Layout-Arrays.
     * @param value Der Buchstabe, der Dargestellt werden soll.
     * @param style Die CSS-Styleklassen des Buchstabens.
     */
    function Letter(value, style) {
        var myValue = value;
        var myStyle = style || '';
        this.addStyle = function (style) {
            if (myStyle === '') {
                myStyle = style;
            } else {
                myStyle += ' ' + style;
            }
        };
        this.toString = function () {
            return '<span class="item letter ' + myStyle + '">' + myValue + '</span>';
        };
    }
})(jQuery);