/*
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.reduce(function (exists, element) {
                if (exists) {
                    return true;
                }
                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;
            }, false);
            if (!alreadyExists) {
                language.code = code;
                uhrGlobals.languages.push(language);
            }
        }
    };

	// auto-detect themes
	var styleSheets = $('link[rel=stylesheet]');
	for (var i = 0; i < styleSheets.length; i++) {
		var styleSheet = $(styleSheets[i]);
		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(this)) {
            var uhr = this;
            this._timer = window.setInterval(function uhrTimer() {
                uhr.options.time = new Date();
                update(uhr);
            }, 1000);
            update(this);
            setCookie(this, 'uhr-status', 'on');
        }
    };
    var stop = function stop() {
        if (isOn(this)) {
            window.clearInterval(this._timer);
            this._timer = null;
            update(this);
            setCookie(this, 'uhr-status', 'off');
        }
    };
    var toggle = function toggle() {
        if (isOn(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(this), this.element.find('.letterarea'));
            var uhr = this;
            renderer.render(this, function () {
                uhr._currentMinute = -1;
                update(uhr);
            });
            setCookie(this, 'uhr-language', languageKey);
            update(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(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(this);
    };

    // private interface methods
    var create = function create() {
        this._id = uhrGlobals.id++;
        var userTime = this.options.time;
        if (this.options.time === undefined) {
            this.options.time = new Date();
        }
        setupHTML(this);
        wireFunctionality(this);
        if (userTime !== undefined) {
            this.time(userTime);
        }
    };
    // private helper methods (not exported)
    // set up
    var setupHTML = function setupHTML(uhr) {
        var e = uhr.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>');
        e.css('width', uhr.options.width);
        var realWidth = e.width();
        e.width(realWidth);
        e.height(realWidth);
        e.css('font-size', (realWidth / 40) + 'px');

        if (uhr.options.controls) {
            // on/off switch
            var toggleSwitch = $('<div class="onoffswitch" id="uhr-onoffswitch' + uhr._id + '"></div>');
            toggleSwitch.append('<input type="checkbox" class="onoffswitch-checkbox" id="uhr-onoffswitch-checkbox' + uhr._id + '" checked="checked" />');
            toggleSwitch.append('<label class="onoffswitch-label" for="uhr-onoffswitch-checkbox' + uhr._id + '">'
                + '<div class="onoffswitch-inner"></div>'
                + '<div class="onoffswitch-switch"></div>'
                + '</label>');
            e.after(toggleSwitch);

            // language chooser
            if (uhrGlobals.languages.length > 1) {
                var languageChooser = $('<select id="uhr-languagechooser' + uhr._id + '"></select>');
                for (var i = 0; i < uhrGlobals.languages.length; i++) {
                    var language = uhrGlobals.languages[i];
                    languageChooser.append('<option value="' + language.code + '">' + language.language + '</option>');
                }
                e.after(languageChooser);
            }

            // theme chooser
            if (uhrGlobals.themes.length > 1) {
                var themeChooser = $('<select id="uhr-themechooser' + uhr._id + '"></select>');
                for (var i = 0; i < uhrGlobals.themes.length; i++) {
                    var theme = uhrGlobals.themes[i];
                    themeChooser.append('<option value="' + theme.styleClass + '">' + theme.name + '</option>');
                }
                e.after(themeChooser);
            }
        }
    };
    var wireFunctionality = function wireFunctionality(uhr) {

        // on/off switch
        var toggleSwitch = $('#uhr-onoffswitch-checkbox' + uhr._id);
        toggleSwitch.on('click', function () {
            uhr.toggle();
        });
        var status = $.cookie('uhr-status' + uhr._id);
        if (status === undefined || uhr.options.force) {
            status = uhr.options.status;
        }
        toggleSwitch.prop('checked', status === 'on');
        if (status === 'on') {
            uhr.start();
        } else {
            uhr.stop();
        }

        // language chooser
        var languageChooser = $('#uhr-languagechooser' + uhr._id);
        languageChooser.on('change', function () {
            uhr.language(this.value);
        });
        var selectedLanguage = $.cookie('uhr-language' + uhr._id);
        if (selectedLanguage === undefined || uhr.options.force) {
            selectedLanguage = uhr.options.language;
        }
        var found = false;
        for (var i = 0; i < uhrGlobals.languages.length; i++) {
            var code = uhrGlobals.languages[i].code;
            if (selectedLanguage === code) {
                found = true;
                break;
            }
        }
        if (!found) {
            var fallback;
            if (uhrGlobals.languages.length > 0) {
                fallback = uhrGlobals.languages[0].code;
            } else {
                fallback = '';
            }
            console.warn("Language " + selectedLanguage + " not found! Using fallback: " + fallback);
            selectedLanguage = fallback;
        }
        languageChooser.val(selectedLanguage);
        uhr.options.language = "";
        uhr.language(selectedLanguage);

        // theme chooser
        var themeChooser = $('#uhr-themechooser' + uhr._id);
        themeChooser.on('change', function () {
            uhr.theme(this.value);
        });
        var selectedTheme = $.cookie('uhr-theme' + uhr._id);
        if (selectedTheme === undefined || uhr.options.force) {
            selectedTheme = uhr.options.theme;
        }
        found = false;
        for (var i = 0; i < uhrGlobals.themes.length; i++) {
            var styleClass = uhrGlobals.themes[i].styleClass;
            if (selectedTheme === styleClass) {
                found = true;
                break;
            }
        }
        if (!found) {
            var fallback = uhrGlobals.themes[0].styleClass;
            console.warn("Theme " + selectedTheme + " not found! Using fallback: " + fallback);
            selectedTheme = fallback;
        }
        themeChooser.val(selectedTheme);
        uhr.options.theme = "";
        uhr.theme(selectedTheme);
    };
    var setCookie = function setCookie(uhr, cookieName, cookieValue) {
        var options = {};
        if (uhr.options.cookiePath !== undefined) {
            options = {expires: 365, path: uhr.options.cookiePath};
        } else {
            options = {expires: 365};
        }
        $.cookie(cookieName + uhr._id, cookieValue, options);
    };

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

    var language = function language(uhr) {
        var matchingLanguages = uhrGlobals.languages.filter(function (element) {
            return (element.code === this.options.language)
        }, uhr);
        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
        },
        "start": start,
        "stop": stop,
        "toggle": toggle,
        "language": setLanguage,
        "theme": setTheme,
        "time": setTime,
        // constructor method
        "_create": create,
        // private variables
        "_id": -1,
        "_timer": null,
        "_currentMinute": -1
    });
    $.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.layout = layout;
		this.renderarea = renderarea;
	}
	UhrRenderer.prototype.render = function (uhr, beforeshow) {
		var renderer = this;
		if (this.layout._parsed === undefined) {
			switch (this.layout.version) {
				case 2:
					var delegate = new _UhrRendererV2Delegate(this.layout);
					this.layout._parsed = delegate.parse();
					break;
				default:
					console.warn("Unknown layout version: '" + this.layout.version + "'");
					return;
			}
		}
		var letters = this.layout._parsed;
		this.renderarea.fadeOut('fast', function () {
			renderer.renderarea.empty();
			for (var y = 0; y < letters.length; y++) {
				for (var x = 0; x < letters[y].length; x++) {
					var letter = letters[y][x];
					renderer.renderarea.append(letter.toString());
				}
				if (y < letters.length - 1) {
					renderer.renderarea.append('<br/>');
				}
			}
			if (typeof beforeshow === 'function') {
				beforeshow();
			}
			renderer.renderarea.fadeIn('fast');
		});
	};
	function _UhrRendererV2Delegate(layout) {
		this.layout = layout;
		this._parseArrayOrObject = function (letters, styleClass, input) {
			if (Array.isArray(input)) {
				for (var i = 0; i < input.length; i++) {
					this._parseObject(letters, styleClass, input[i]);
				}
			} else {
				this._parseObject(letters, styleClass, input);
			}
		}
		this._parseObject = function(letters, styleClass, object) {
			for (var line in object) {
				if (object.hasOwnProperty(line)) {
					var highlightLetters = object[line];
					for (var i = 0; i < highlightLetters.length; i++) {
						var x = highlightLetters[i] - 1;
						letters[line - 1][x].addStyle(styleClass);
					}
				}
			}
		}
		this._parseTimeDefinition = function(letters, styleClass, definition) {
			for (var listString in definition) {
				if (definition.hasOwnProperty(listString)) {
					var array = listString.split(',');
					var highlightLetters = definition[listString];
					for (var index = 0; index < array.length; index++) {
						this._parseArrayOrObject(letters, styleClass + array[index], highlightLetters);
					}
				}
			}
		}
	}
	_UhrRendererV2Delegate.prototype.parse = function() {
		var letters = [];
		for (var i = 0; i < this.layout.letters.length; i++) {
			var line = [];
			var string = this.layout.letters[i];
			for (var c = 0; c < string.length; c++) {
				var character = new Letter(string[c]);
				line.push(character);
			}
			letters.push(line);
		}
		this._parseArrayOrObject(letters, 'on', this.layout.permanent);
		this._parseTimeDefinition(letters, 'minute', this.layout.minutes);
		this._parseTimeDefinition(letters, 'hour', this.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);