/*
 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 setLanguage(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 setMode = function(mode) {
        this.options.mode = mode;
        this.currentMinute = -1;
        update.bind(this)();
        setCookie.bind(this)('uhr-mode', mode);
    };
    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;
        var hash, params;
        if (this.options.time === undefined) {
            this.options.time = new Date();
        }
        // parse the URL params
        hash = window.location.hash;
        if (hash !== undefined && typeof hash === 'string' && hash.charAt(0) === '#') {
            hash = hash.substring(1);
            hash = decodeURIComponent(hash);
            params = hash.split('&');
            params.forEach(function (element) {
                var pair = element.split('=');
                var key = pair[0];
                var value = pair[1];
                switch (key) {
                    case 'l':
                    case 'language':
                        this.options.language = value;
                        this.options.force = true;
                        break;
                    case 't':
                    case 'theme':
                        this.options.theme = value;
                        this.options.force = true;
                        break;
                    case 'm':
                    case 'mode':
                        this.options.mode = value;
                        this.options.force = true;
                        break;
                    case 's':
                    case 'status':
                        this.options.status = value;
                        this.options.force = true;
                        break;
                }
            }.bind(this));
        }
        // end parse the URL params
        setupHTML.bind(this)();
        wireFunctionality.bind(this)();
        if (userTime !== undefined) {
            this.time(userTime);
        }
    };
    // private helper methods (not exported)
    var toggleConfigScreen = function toggleConfigScreen() {
        $('#uhr-controlpanel' + this.id).toggle('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 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);

            // time mode switch
            var modeSwitch = $('<div class="onoffswitch" id="uhr-modeswitch' + this.id + '"></div>');
            modeSwitch.append('<input type="checkbox" class="onoffswitch-checkbox" id="uhr-modeswitch-checkbox' + this.id +
                                '" checked="checked" />');
            modeSwitch.append('<label class="onoffswitch-label" for="uhr-modeswitch-checkbox' + this.id + '">' +
                                '<div class="modeswitch-inner"></div>' + '<div class="onoffswitch-switch"></div>' +
                                '</label>');
            content.append(modeSwitch);
            // 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).hide('fast');
            }.bind(this));
            content.append(closebutton);
            e.after(controlpanel);
            controlpanel.hide();
            var configlink = $('<a class="uhr-configlink" id="uhr-configlink' + this.id + '"></a>');
            configlink.on('click', function() {
                toggleConfigScreen.bind(this)();
            }.bind(this));
            e.after(configlink);
        }
    };
    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();
        }

        // time mode switch
        var modeSwitch = $('#uhr-modeswitch-checkbox' + this.id);
        modeSwitch.on('click', function() {
            if (this.options.mode === 'seconds') {
                setMode.bind(this)('normal');
            } else {
                setMode.bind(this)('seconds');
            }
        }.bind(this));

        var mode = $.cookie('uhr-mode' + this.id);
        if (mode === undefined || this.options.force) {
            mode = this.options.mode;
        }
        modeSwitch.prop('checked', mode !== 'seconds');
        if (mode === 'seconds') {
            setMode.bind(this)('seconds');
        } else {
            setMode.bind(this)('normal');
        }

        // 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 destroy = function destroy() {
        this.timer = null;
        $(this.element)
            .removeAttr('style')
            .removeAttr('class')
            .empty();
        $('#uhr-configlink' + this.id).remove();
        $('#uhr-controlpanel' + this.id).remove();

    };
    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') && this.options.mode !== '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)();
        if (this.options.mode === 'seconds') {
            highlight.bind(this)('second' + second);
        } else {
            highlight.bind(this)('on');
            for (var i = 1; i <= dotMinute; i++) {
                highlight.bind(this)('dot' + i);
            }
            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,
            mode: 'normal'
        },
        "start": start,
        "stop": stop,
        "toggle": toggle,
        "language": setLanguage,
        "theme": setTheme,
        "time": setTime,
        "mode": setMode,
        "width": setWidth,
        // constructor method
        "_create": create,
        // destructor method
        "_destroy": destroy
    });
    $.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) {
        var vorne0 = {
            3: [2, 3, 4],
            4: [1, 5],
            5: [1, 4, 5],
            6: [1, 3, 5],
            7: [1, 2, 5],
            8: [1, 5],
            9: [2, 3, 4]
        };
        var hinten0 = {
            3: [8, 9, 10],
            4: [7, 11],
            5: [7, 10, 11],
            6: [7, 9, 11],
            7: [7, 8, 11],
            8: [7, 11],
            9: [8, 9, 10]
        };
        var vorne1 = {
            3: [3],
            4: [2, 3],
            5: [3],
            6: [3],
            7: [3],
            8: [3],
            9: [2, 3, 4]
        };
        var hinten1 = {
            3: [9],
            4: [8, 9],
            5: [9],
            6: [9],
            7: [9],
            8: [9],
            9: [8, 9, 10]
        };
        var vorne2 = {
            3: [2, 3, 4],
            4: [1, 5],
            5: [5],
            6: [4],
            7: [3],
            8: [2],
            9: [1, 2, 3, 4, 5]
        };
        var hinten2 = {
            3: [8, 9, 10],
            4: [7, 11],
            5: [11],
            6: [10],
            7: [9],
            8: [8],
            9: [7, 8, 9, 10, 11]
        };
        var vorne3 = {
            3: [1, 2, 3, 4, 5],
            4: [4],
            5: [3],
            6: [4],
            7: [5],
            8: [1, 5],
            9: [2, 3, 4]
        };
        var hinten3 = {
            3: [7, 8, 9, 10, 11],
            4: [10],
            5: [9],
            6: [10],
            7: [11],
            8: [7, 11],
            9: [8, 9, 10]
        };
        var vorne4 = {
            3: [4],
            4: [3, 4],
            5: [2, 4],
            6: [1, 4],
            7: [1, 2, 3, 4, 5],
            8: [4],
            9: [4]
        };
        var hinten4 = {
            3: [10],
            4: [9, 10],
            5: [8, 10],
            6: [7, 10],
            7: [7, 8, 9, 10, 11],
            8: [10],
            9: [10]
        };
        var vorne5 = {
            3: [1, 2, 3, 4, 5],
            4: [1],
            5: [1, 2, 3, 4],
            6: [5],
            7: [5],
            8: [1, 5],
            9: [2, 3, 4]
        };
        var hinten5 = {
            3: [7, 8, 9, 10, 11],
            4: [7],
            5: [7, 8, 9, 10],
            6: [11],
            7: [11],
            8: [7, 11],
            9: [8, 9, 10]
        };
        var hinten6 = {
            3: [9, 10],
            4: [8],
            5: [7],
            6: [7, 8, 9, 10],
            7: [7, 11],
            8: [7, 11],
            9: [8, 9, 10]
        };
        var hinten7 = {
            3: [7, 8, 9, 10, 11],
            4: [11],
            5: [10],
            6: [9],
            7: [8],
            8: [8],
            9: [8]
        };
        var hinten8 = {
            3: [8, 9, 10],
            4: [7, 11],
            5: [7, 11],
            6: [8, 9, 10],
            7: [7, 11],
            8: [7, 11],
            9: [8, 9, 10]
        };
        var hinten9 = {
            3: [8, 9, 10],
            4: [7, 11],
            5: [7, 11],
            6: [8, 9, 10, 11],
            7: [11],
            8: [10],
            9: [8, 9]
        };
        var seconds= {
            "0": [vorne0, hinten0],
            "1": [vorne0, hinten1],
            "2": [vorne0, hinten2],
            "3": [vorne0, hinten3],
            "4": [vorne0, hinten4],
            "5": [vorne0, hinten5],
            "6": [vorne0, hinten6],
            "7": [vorne0, hinten7],
            "8": [vorne0, hinten8],
            "9": [vorne0, hinten9],
            "10": [vorne1, hinten0],
            "11": [vorne1, hinten1],
            "12": [vorne1, hinten2],
            "13": [vorne1, hinten3],
            "14": [vorne1, hinten4],
            "15": [vorne1, hinten5],
            "16": [vorne1, hinten6],
            "17": [vorne1, hinten7],
            "18": [vorne1, hinten8],
            "19": [vorne1, hinten9],
            "20": [vorne2, hinten0],
            "21": [vorne2, hinten1],
            "22": [vorne2, hinten2],
            "23": [vorne2, hinten3],
            "24": [vorne2, hinten4],
            "25": [vorne2, hinten5],
            "26": [vorne2, hinten6],
            "27": [vorne2, hinten7],
            "28": [vorne2, hinten8],
            "29": [vorne2, hinten9],
            "30": [vorne3, hinten0],
            "31": [vorne3, hinten1],
            "32": [vorne3, hinten2],
            "33": [vorne3, hinten3],
            "34": [vorne3, hinten4],
            "35": [vorne3, hinten5],
            "36": [vorne3, hinten6],
            "37": [vorne3, hinten7],
            "38": [vorne3, hinten8],
            "39": [vorne3, hinten9],
            "40": [vorne4, hinten0],
            "41": [vorne4, hinten1],
            "42": [vorne4, hinten2],
            "43": [vorne4, hinten3],
            "44": [vorne4, hinten4],
            "45": [vorne4, hinten5],
            "46": [vorne4, hinten6],
            "47": [vorne4, hinten7],
            "48": [vorne4, hinten8],
            "49": [vorne4, hinten9],
            "50": [vorne5, hinten0],
            "51": [vorne5, hinten1],
            "52": [vorne5, hinten2],
            "53": [vorne5, hinten3],
            "54": [vorne5, hinten4],
            "55": [vorne5, hinten5],
            "56": [vorne5, hinten6],
            "57": [vorne5, hinten7],
            "58": [vorne5, hinten8],
            "59": [vorne5, hinten9]
        };

        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);
            if (typeof layout.seconds !== 'undefined' && layout.seconds !== null) {
                parseTimeDefinition(letters, 'second', layout.seconds);
            } else {
                parseTimeDefinition(letters, 'second', 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);