
/// <reference path="../lib1/innovaphone.lib1.js" />
/// <reference path="../ui1.lib/innovaphone.ui1.lib.js" />
/// <reference path="../ui1.svg/innovaphone.ui1.svg.js" />

var innovaphone = innovaphone || {};
innovaphone.ui1 = innovaphone.ui1 || {};
innovaphone.ui1.Clock = innovaphone.ui1.Clock || function (style, config, lang, cl) {

    const DEFAULT_LANG_ID = "en";
    const DEFAULT_CONFIG = {
        langId: "en",
        dialType: "digital",
        colorDial: "rgba(10, 10, 10, 0)",
        colorDialMarking: "rgba(127, 127, 127, 1)",
        colorCenter: "rgba(255, 255, 255, 1)",
        colorHour: "rgba(200, 200, 200, 1)",
        colorMinute: "rgba(255, 255, 255, 1)",
        colorSecond: "#f59b10",
        colorDate: "rgba(127, 127, 127, 1)"
    };
    config = config || DEFAULT_CONFIG;
    AnalogDial.prototype = innovaphone.ui1.nodePrototype;
    DigitalDial.prototype = innovaphone.ui1.nodePrototype;
    LedDial.prototype = innovaphone.ui1.nodePrototype;

    // Clock interface

    this.createNode("div", "position: relative;" + style, null, cl);
    let that = this,
        currentType = null,
        timeout = null,
        lastHours = 0,
        dial = null;

    if (lang) lang.onlangchanged.attach(updateLang);
    function updateLang() {
        dial.setLang(lang?.current || config.langId || DEFAULT_LANG_ID);
        update(true);
    }

    this.remove = function () {
        that.container.remove();
        lang.onlangchanged.detach(updateLang);
        return that;
    };

    this.start = function () {
        update(true);
        return that;
    };

    this.stop = function () {
        if (timeout) window.clearTimeout(timeout);
        timeout = null;
        return that;
    };

    this.setDialType = function (type) {
        if (type != "analog" && type != "digital" && type != "led") type = "analog";
        if (currentType != type) {
            if (dial) dial.remove();
            if (type == "analog") dial = this.add(new AnalogDial(config, lang?.current || config.langId || DEFAULT_LANG_ID));
            else if (type == "digital") dial = this.add(new DigitalDial(config, lang?.current || config.langId || DEFAULT_LANG_ID));
            else if (type == "led") dial = this.add(new LedDial(config, lang?.current || config.langId || DEFAULT_LANG_ID));
            update(true);
        }
        currentType = type;
        return that;
    };

    function update(starting) {
        let date = new Date(),
            hours = date.getHours(),
            milliseconds = date.getMilliseconds(),
            full = starting || (lastHours == 1 && hours == 0);

        lastHours = hours;
        dial.update(date, full);
        timeout = setTimeout(update, 1000 - milliseconds);
    }

    that.setDialType(config.dialType);
    that.start(true);

    // Analog clock dial

    function AnalogDial(config, langId) {
        this.createNode("div", "position: absolute; width: 100%; height: 100%");
        let that = this,
            lastLine = 0,
            dial = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 0; stroke: none; fill: " + config.colorDial + ";", "-100 -100 200 200", "<circle cx='0' cy='0' r='95'/>")),
            dialLines = [],
            dialHours = [],
            dialWeekday = null,
            dialDate = null,
            handHour = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 5; transform: rotate(0deg); stroke:" + config.colorHour + ";", "-100 -100 200 200", "<line style='stroke-width: 3; stroke-linecap: round;' x1='0' y1='7' x2='0' y2='-50'/>")),
            handMinute = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 6; transform: rotate(0deg); stroke:" + config.colorMinute + ";", "-100 -100 200 200", "<line style='stroke-width: 2.5; stroke-linecap: round;' x1='0' y1='7' x2='0' y2='-80'/>")),
            handSecond = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 6; transform: rotate(0deg); stroke:" + config.colorSecond + ";", "-100 -100 200 200", "<line style='stroke-width: 1.5; stroke-linecap: round;' x1='0' y1='7' x2='0' y2='-75'/>")),
            center = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 7; stroke: none; fill: " + config.colorCenter + ";", "-100 -100 200 200", "<circle cx='0' cy='0' r='3'/>"));

        for (let i = 0; i < 60; i++) {
            let long = i % 5 == 0;
            let line = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; stroke: " + config.colorDialMarking + ";", "-100 -100 200 200", "<line style='stroke-width: " + (long ? "3" : "0.5") + ";stroke-linecap: round;' x1='0' x2='0'" + (long ? "y1='-89' y2='-91'" : "y1='-89' y2='-91'") + "/>"));
            line.container.style.transform = "rotate(" + (i * 6) + "deg)";
            dialLines.push(line);
        }
        for (let i = 0; i < 12; i++) {
            let hour = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 20px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='0' y='-75' transform='translate(0, -75) rotate(" + (i * -30) + ") translate(0, 80)'>" + (i == 0 ? "12" : i) + "</text>"));
            hour.container.style.transform = "rotate(" + (i * 30) + "deg)";
            dialHours.push(hour);
        }

        function updateTime(date, full) {
            let hours = date.getHours() + date.getMinutes() / 60.0;
            let minutes = date.getMinutes() + date.getSeconds() / 60.0;
            let seconds = Math.floor(date.getSeconds());

            handHour.container.style.transition = hours == 0 ? "transform 0s" : "transform 0.5s";
            handMinute.container.style.transition = minutes == 0 ? "transform 0s" : "transform 0.5s";
            handSecond.container.style.transition = seconds == 0 ? "transform 0s" : "transform 0.5s";

            handHour.container.style.transform = "rotate(" + (hours * 30) + "deg)";
            handMinute.container.style.transform = "rotate(" + (minutes * 6) + "deg)";
            handSecond.container.style.transform = "rotate(" + (seconds * 6) + "deg)";

            let currentLine = Math.floor(seconds) % 60;
            dialLines[lastLine].container.style.stroke = config.colorDialMarking;
            dialLines[currentLine].container.style.stroke = config.colorSecond;
            lastLine = currentLine;
        }

        function updateDate(date) {
            let weekdayText = date.toLocaleDateString(langId, { weekday: "long" });
            let dateText = date.toLocaleDateString(langId, { day: "numeric", month: "long", year: "numeric" });
            if (dialWeekday) dialWeekday.container.remove();
            if (dialDate) dialDate.container.remove();
            dialWeekday = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 10px; stroke: none; fill:" + config.colorDate + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='0' y='35'>" + weekdayText + "</text>"));
            dialDate = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 7px; stroke: none; fill:" + config.colorDate + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='0' y='45'>" + dateText + "</text>"));
        }

        this.setLang = function (lang) {
            langId = lang;
            updateDate(new Date());
        }

        this.update = function (date, full) {
            if (full) updateDate(date);
            updateTime(date, full);
        };

        this.remove = function () {
            that.container.remove();
        };
    }
    AnalogDial.prototype = innovaphone.ui1.nodePrototype;


    // Digital clock dial

    function DigitalDial(config, langId) {
        this.createNode("div", "position: absolute; width: 100%; height: 100%");
        let that = this,
            lastLine = 0,
            lastHours = null,
            lastDayPeriod = null,
            lastMinutes = null,
            lastSeconds = null,
            dial = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 0; stroke: none; fill: " + config.colorDial + ";", "-100 -100 200 200", "<rect x='-95' y='-95' width='190' height='190' rx='20' ry='20'/>")),
            dialSeparator1 = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 16px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='-30' y='2'>:</text>")),
            dialSeparator2 = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 16px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='30' y='2'>:</text>")),
            dialHour = null,
            dialDayPeriod = null,
            dialMinute = null,
            dialSecond = null,
            dialTime = null,
            dialFullDate = null;

        function updateTime(date, full) {
            let hours = date.getHours();
            let dayPeriod = null;
            if (langId == "en") {
                dayPeriod = date.toLocaleTimeString(langId, { dayPeriod: "long" });
                if (hours == 0) hours = 12;
                if (hours > 12) hours -= 12;
            }
            let minutes = date.getMinutes();
            let seconds = date.getSeconds();
            if (full || lastHours != hours || lastMinutes != minutes) {
                if (dialHour) dialHour.container.remove();
                dialHour = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 46px; stroke: none; fill:" + config.colorHour + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='-60' y='9'>" + (hours < 10 ? "0" : "")  + hours + "</text>"));
            }
            if (full || lastMinutes != minutes) {
                if (dialMinute) dialMinute.container.remove();
                dialMinute = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 46px; stroke: none; fill:" + config.colorMinute + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='0' y='9'>" + (minutes < 10 ? "0" : "") + minutes + "</text>"));
            }
            if (full || lastSeconds != seconds) {
                if (dialSecond) dialSecond.container.remove();
                dialSecond = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 46px; stroke: none; fill:" + config.colorSecond + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='60' y='9'>" + (seconds < 10 ? "0" : "") + seconds + "</text>"));
            }
            if (dayPeriod && (full || lastDayPeriod != dayPeriod)) {
                if (dialDayPeriod) dialDayPeriod.container.remove();
                dialDayPeriod = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 10px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='end' x='83' y='29'>" + dayPeriod + "</text>"));
            }
            lastHours = hours;
            lastMinutes = minutes;
            lastSeconds = seconds;
            lastDayPeriod = dayPeriod;
        }

        function updateDate(date) {
            let weekdayText = date.toLocaleDateString(langId, { weekday: "long" });
            let dateText = date.toLocaleDateString(langId, { day: "numeric", month: "long", year: "numeric" });
            let fullDateText = date.toLocaleDateString(langId, { weekday: "long", day: "numeric", month: "long", year: "numeric" });
            if (dialFullDate) dialFullDate.container.remove();
            dialFullDate = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 10px; stroke: none; fill:" + config.colorDate + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='end' x='83'" + (langId == "en" ? "y='-28'" : "y='29'") + ">" + fullDateText + "</text>"));
        }

        this.setLang = function (lang) {
            langId = lang;
            updateTime(new Date());
            updateDate(new Date());
        }

        this.update = function (date, full) {
            if (full) updateDate(date);
            updateTime(date, full);
        };

        this.remove = function () {
            that.container.remove();
        };
    }
    DigitalDial.prototype = innovaphone.ui1.nodePrototype;


    // LED clock dial

    const LED_DIGITS = {
        //   a  b  c  d  e  f  g  
        " ": [ 0, 0, 0, 0, 0, 0, 0 ],
        "0": [ 1, 1, 1, 1, 1, 1, 0 ],
        "1": [ 0, 1, 1, 0, 0, 0, 0 ],
        "2": [ 1, 1, 0, 1, 1, 0, 1 ],
        "3": [ 1, 1, 1, 1, 0, 0, 1 ],
        "4": [ 0, 1, 1, 0, 0, 1, 1 ],
        "5": [ 1, 0, 1, 1, 0, 1, 1 ],
        "6": [ 1, 0, 1, 1, 1, 1, 1 ],
        "7": [ 1, 1, 1, 0, 0, 0, 0 ],
        "8": [ 1, 1, 1, 1, 1, 1, 1 ],
        "9": [ 1, 1, 1, 1, 0, 1, 1 ]
    }

    function createLedDigit(scale, translate, colorBackground, colorOff, colorOn) {
        let digitSource = "<g stroke='" + colorBackground + "' stroke-width='0.5' transform='scale(" + scale + "), translate(" + translate + ")'>";
        digitSource += "<polygon fill='var(--a)' points=' -4,-8 -3,-9 3,-9 4,-8 3,-7 -3,-7'/>";
        digitSource += "<polygon fill='var(--b)' points=' 4,-8 5,-7 5,-1 4,0 3,-1 3,-7'/>";
        digitSource += "<polygon fill='var(--c)' points=' 4,0 5,1 5,7 4,8 3,7 3,1'/>";
        digitSource += "<polygon fill='var(--d)' points=' 4,8 3,9 -3,9 -4,8 -3,7 3,7'/>";
        digitSource += "<polygon fill='var(--e)' points=' -4,8 -5,7 -5,1 -4,0 -3,1 -3,7'/>";
        digitSource += "<polygon fill='var(--f)' points=' -4,0 -5,-1 -5,-7 -4,-8 -3,-7 -3,-1'/>";
        digitSource += "<polygon fill='var(--g)' points=' -4,0 -3,-1 3,-1 4,0 3,1 -3,1'/>";
        digitSource += "</g>";
        let digit = new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1;", "-200 -200 400 400", digitSource);

        digit.set = function (value) {
            let arr = LED_DIGITS[value];
            arr = arr || LED_DIGITS[" "];
            this.container.style.setProperty("--a", arr[0] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--b", arr[1] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--c", arr[2] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--d", arr[3] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--e", arr[4] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--f", arr[5] == 1 ? colorOn : colorOff);
            this.container.style.setProperty("--g", arr[6] == 1 ? colorOn : colorOff);
        }

        return digit;
    }

    function LedDial(config, langId) {
        this.createNode("div", "position: absolute; width: 100%; height: 100%");
        let that = this,
            lastHours = null,
            lastMinutes = null,
            dial = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 0; stroke: none; fill: " + config.colorDial + ";", "-100 -100 200 200", "<rect x='-95' y='-95' width='190' height='190' rx='20' ry='20'/>")),
            dialSeparator = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 16px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='-18' y='2'>:</text>")),
            dialAM = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 7px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='61' y='-14'>AM</text>")),
            dialPM = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 7px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='73' y='-14'>PM</text>")),
            dialHourHi = this.add(createLedDigit("5", "-27 0", config.colorDial, config.colorDial, config.colorHour)),
            dialHourLo = this.add(createLedDigit("5", "-15 0", config.colorDial, config.colorDial, config.colorHour)),
            dialMinuteHi = this.add(createLedDigit("5", "1 0", config.colorDial, config.colorDial, config.colorMinute)),
            dialMinuteLo = this.add(createLedDigit("5", "13 0", config.colorDial, config.colorDial, config.colorMinute)),
            dialSecondHi = this.add(createLedDigit("2", "61 0", config.colorDial, config.colorDial, config.colorSecond)),
            dialSecondLo = this.add(createLedDigit("2", "73 0", config.colorDial, config.colorDial, config.colorSecond)),
            dialWeekday = [],
            dialDayHi = this.add(createLedDigit("1", "50 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialDayLo = this.add(createLedDigit("1", "62 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialMonthHi = this.add(createLedDigit("1", "82 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialMonthLo = this.add(createLedDigit("1", "94 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialYear0 = this.add(createLedDigit("1", "114 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialYear1 = this.add(createLedDigit("1", "126 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialYear2 = this.add(createLedDigit("1", "138 110", config.colorDial, config.colorDial, config.colorMinute)),
            dialYear3 = this.add(createLedDigit("1", "150 110", config.colorDial, config.colorDial, config.colorMinute)),
            dateSeparator1 = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 9px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='36' y='58.5'>.</text>")),
            dateSeparator2 = this.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 9px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='51.5' y='58.5'>.</text>"));

        createDialWeekday();

        function updateTime(date, full) {
            let hours = date.getHours();
            if (lastHours != hours) {
                lastHours = hours;
                if (langId == "en") {
                    if (hours >= 12) {
                        dialAM.container.style.fill = config.colorDialMarking;
                        dialPM.container.style.fill = config.colorMinute;
                    }
                    else {
                        dialAM.container.style.fill = config.colorMinute;
                        dialPM.container.style.fill = config.colorDialMarking;
                    }
                    if (hours == 0) hours = 12;
                    else if (hours > 12) hours -= 12;
                }
                let hoursString = (hours < 10 ? "0" : "") + hours;
                dialHourHi.set(hoursString.charAt(0));
                dialHourLo.set(hoursString.charAt(1));
            }

            let minutes = date.getMinutes();
            if (lastMinutes != minutes) {
                lastMinutes = minutes;
                let minutesString = (minutes < 10 ? "0" : "") + minutes;
                dialMinuteHi.set(minutesString.charAt(0));
                dialMinuteLo.set(minutesString.charAt(1));
            }

            let seconds = date.getSeconds();
            let secondsString = (seconds < 10 ? "0" : "") + seconds;
            dialSecondHi.set(secondsString.charAt(0));
            dialSecondLo.set(secondsString.charAt(1));
        }

        function updateDate(date) {
            let weekDay = (date.getDay() - 1)  % 7;
            for (let i = 0; i < 7; i++) {
                dialWeekday[i].container.style.fill = (weekDay == i ? config.colorMinute : config.colorDialMarking);
            }
            let day = date.getDate();
            let dayString = (day < 10 ? "0" : "") + day;
            dialDayHi.set(dayString.charAt(0));
            dialDayLo.set(dayString.charAt(1));
            let month = date.getMonth() + 1;
            let monthString = (month < 10 ? "0" : "") + month;
            dialMonthHi.set(monthString.charAt(0));
            dialMonthLo.set(monthString.charAt(1));
            let year = date.getFullYear();
            let yearString = "" + year;
            dialYear0.set(yearString.charAt(0));
            dialYear1.set(yearString.charAt(1));
            dialYear2.set(yearString.charAt(2));
            dialYear3.set(yearString.charAt(3));
        }

        function createDialWeekday() {
            for (let i = 0; i < 7; i++) {
                let text = (new Date("2025-02-" + (24 + i))).toLocaleDateString(langId, { weekday: "short" });
                if (dialWeekday[i]) dialWeekday[i].container.remove();
                dialWeekday[i] = that.add(new innovaphone.ui1.SvgInline("position:absolute; width: 100%; height: 100%; z-index: 1; font-size: 7px; stroke: none; fill:" + config.colorDialMarking + ";", "-100 -100 200 200", "<text dominant-baseline='middle' text-anchor='middle' x='" + (-66 + (i*22)) + "' y='39'>" + text + "</text>"));
            }
        }

        this.setLang = function (lang) {
            langId = lang;
            createDialWeekday();
            updateTime(new Date());
            updateDate(new Date());
        }

        this.update = function (date, full) {
            if (full) updateDate(date);
            updateTime(date, full);
        };

        this.remove = function () {
            that.container.remove();
        };
    }
    LedDial.prototype = innovaphone.ui1.nodePrototype;
};
innovaphone.ui1.Clock.prototype = innovaphone.ui1.nodePrototype;
