"use strict";

const api = require("./api");
const cookie = require("./cookie")("bbctrl-");
const Sock = require("./sock");
const semverLt = require("semver/functions/lt");

if (document.getElementById("svelte-dialog-host") != undefined) {
  SvelteComponents.createComponent(
    "DialogHost",
    document.getElementById("svelte-dialog-host")
  );
}
if (document.getElementById("adminViewSvelte") != undefined) {
  SvelteComponents.createComponent("AdminNetworkView");
}

function parse_version(v) {
    const pattern = /^(\d+)\.(\d+)\.(\d+)(?:[-.]?(.*))?$/;
    const [ version, major, minor, patch, pre ] = v.trim().match(pattern) || [];

    return {
        version,
        major,
        minor,
        patch,
        pre
    };
}

function fixup_version_number(version) {
    const v = parse_version(version);

    version = `${v.major}.${v.minor}.${v.patch}`;
    if (v.pre) {
        const [ , prefix, num ] = v.pre.match(/([a-zA-Z])(\d+)/);

        const suffix = prefix === "b"
            ? `beta.${num}`
            : v.pre;

        version = `${version}-${suffix}`;
    }

    return version;
}

function is_object(o) {
    return o !== null && typeof o == "object";
}

function is_array(o) {
    return Array.isArray(o);
}

function update_array(dst, src) {
    while (dst.length) {
        dst.pop();
    }

    for (let i = 0; i < src.length; i++) {
        Vue.set(dst, i, src[i]);
    }
}

function hasOwnProperty(obj, key) {
    return Object.prototype.hasOwnProperty.call(obj, key);
}

function update_object(dst, src, remove) {
    let props, index, key, value;

    if (remove) {
        props = Object.getOwnPropertyNames(dst);

        for (index in props) {
            key = props[index];
            if (!hasOwnProperty(src, key)) {
                Vue.delete(dst, key);
            }
        }
    }

    props = Object.getOwnPropertyNames(src);
    for (index in props) {
        key = props[index];
        value = src[key];

        if (is_array(value) && hasOwnProperty(dst, key) && is_array(dst[key])) {
            update_array(dst[key], value);
        } else if (is_object(value) && hasOwnProperty(dst, key) && is_object(dst[key])) {
            update_object(dst[key], value, remove);
        } else {
            Vue.set(dst, key, value);
        }
    }
}

module.exports = new Vue({
    el: "body",

    data: function() {
        return {
          status: "connecting",
          currentView: "loading",
          display_units: localStorage.getItem("display_units") || "METRIC",
          index: -1,
          modified: false,
          template: require("../resources/config-template.json"),
          config: {
            settings: { units: "METRIC" },
            motors: [{}, {}, {}, {}],
            version: "<loading>",
            full_version: "<loading>",
            ip: "<>",
            wifiName: "not connected",
            macros:[{},{},{},{},{},{},{},{}],
            macrosList:[],
            GCodeList:[]
          },
          state: {
            messages: [],
          },
          video_size: cookie.get("video-size", "small"),
          crosshair: cookie.get("crosshair", "false") != "false",
          errorTimeout: 30,
          errorTimeoutStart: 0,
          errorShow: false,
          errorMessage: "",
          confirmUpgrade: false,
          confirmUpload: false,
          firmwareUpgrading: false,
          checkedUpgrade: false,
          firmwareName: "",
          latestVersion: "",
        };
    },

    components: {
        estop: { template: "#estop-template" },
        "loading-view": { template: "<h1>Loading...</h1>" },
        "control-view": require("./control-view"),
        "settings-view": require("./settings-view"),
        "motor-view": require("./motor-view"),
        "tool-view": require("./tool-view"),
        "io-view": require("./io-view"),
        "admin-general-view": require("./admin-general-view"),
        "admin-network-view": require("./admin-network-view"),
        "macros-view": require('./macros'),
        "help-view": require("./help-view"),
        "cheat-sheet-view": {
            template: "#cheat-sheet-view-template",
            data: function() {
                return {
                    showUnimplemented: false
                };
            },
        },
    },

    watch: {
        display_units: function(value) {
            localStorage.setItem("display_units", value);
            SvelteComponents.setDisplayUnits(value);
        },
    },

    events: {
        "config-changed": function() {
            this.modified = true;
        },

        send: function(msg) {
            if (this.status == "connected") {
                this.sock.send(msg);
            }
        },

        connected: function() {
            this.update();
        },

        update: function() {
            this.update();
        },

        check: async function() {
            try {
                const response = await fetch("https://raw.githubusercontent.com/OneFinityCNC/onefinity-release/master/latest.txt", {
                    cache: "no-cache"
                });

                this.latestVersion = (await response.text()).trim();
            } catch (err) {
                this.latestVersion = "";
            }
        },

        upgrade: function() {
            this.confirmUpgrade = true;
        },

        upload: function(firmware) {
            this.firmware = firmware;
            this.firmwareName = firmware.name;
            this.confirmUpload = true;
        },

        error: function(msg) {
            // Honor user error blocking
            if (Date.now() - this.errorTimeoutStart < this.errorTimeout * 1000) {
                return;
            }

            // Wait at least 1 sec to pop up repeated errors
            if (1 < msg.repeat && Date.now() - msg.ts < 1000) {
                return;
            }

            // Popup error dialog
            this.errorShow = true;
            this.errorMessage = msg.msg;
        },
    },

    computed: {
        popupMessages: function() {
            const msgs = [];

            for (let i = 0; i < this.state.messages.length; i++) {
                const text = this.state.messages[i].text;
                if (!/^#/.test(text)) {
                    msgs.push(text);
                }
            }

            return msgs;
        },
    },

    ready: function() {
        window.onhashchange = () => this.parse_hash();
        this.connect();

        SvelteComponents.registerControllerMethods({
            dispatch: (...args) => this.$dispatch(...args)
        });
    },

    methods: {
        block_error_dialog: function() {
            this.errorTimeoutStart = Date.now();
            this.errorShow = false;
        },

        toggle_video: function() {
            if (this.video_size == "small") {
                this.video_size = "large";
            } else if (this.video_size == "large") {
                this.video_size = "small";
            }
            cookie.set("video-size", this.video_size);
        },

        toggle_crosshair: function(e) {
            e.preventDefault();
            this.crosshair = !this.crosshair;
            cookie.set("crosshair", this.crosshair);
        },

        estop: function() {
            if (this.state.xx == "ESTOPPED") {
                api.put("clear");
            } else {
                api.put("estop");
            }
        },

        upgrade_confirmed: async function() {
            this.confirmUpgrade = false;

            try {
                await api.put("upgrade");
                this.firmwareUpgrading = true;
            } catch (error) {
                console.error("Error during upgrade:", error);
                alert("Error during upgrade");
            }
        },

        upload_confirmed: async function() {
            this.confirmUpload = false;

            const form = new FormData();
            form.append("firmware", this.firmware);

            try {
                await api.put("firmware/update", form);
                this.firmwareUpgrading = true;
            } catch (error) {
                console.error("Firmware update failed:", error);
                alert("Firmware update failed");
            }
        },

        show_upgrade: function() {
            if (!this.latestVersion) {
                return false;
            }

            return semverLt(this.config.full_version, this.latestVersion);
        },

        showShutdownDialog: function() {
            SvelteComponents.showDialog("Shutdown");
        },

        update: async function() {
            const config = await api.get("config/load");
            const wifi = await api.get("wifi");
            update_object(this.config, config, true);
            this.config.full_version = fixup_version_number(this.config.full_version);
            this.config.ip = wifi.ipAddresses;
            this.config.wifiName = wifi.wifi;
            this.parse_hash();

            if (!this.checkedUpgrade) {
                this.checkedUpgrade = true;

                const check = this.config.admin["auto-check-upgrade"];
                if (typeof check == "undefined" || check) {
                    this.$emit("check");
                }
            }

            SvelteComponents.handleConfigUpdate(this.config);
        },

        connect: function() {
            this.sock = new Sock(`//${location.host}/sockjs`);

            this.sock.onmessage = (e) => {
                if (typeof e.data != "object") {
                    return;
                }

                if (e.data.log && e.data.log.msg !== "Switch not found") {
                    this.$broadcast("log", e.data.log);

                    if (Object.keys(e.data).length === 1) {
                        // If there's only log data, we're done
                        return;
                    }
                }

                // Check for session ID change on controller
                if ("sid" in e.data) {
                    if (typeof this.sid == "undefined") {
                        this.sid = e.data.sid;
                    } else if (this.sid != e.data.sid) {
                        if (this.hostname && location.hostname !== "localhost") {
                            location.hostname = this.hostname;
                        }

                        location.reload();
                    }
                }

                update_object(this.state, e.data, false);

                SvelteComponents.handleControllerStateUpdate(this.state);

                delete this.state.log;

                this.$broadcast("update");
            };

            this.sock.onopen = () => {
                this.status = "connected";
                this.$emit(this.status);
                this.$broadcast(this.status);
            };

            this.sock.onclose = () => {
                this.status = "disconnected";
                this.$emit(this.status);
                this.$broadcast(this.status);
            };
        },

        parse_hash: function() {
            const hash = location.hash.substr(1);

            if (!hash.trim().length) {
                location.hash = "control";
                return;
            }

            const parts = hash.split(":");

            if (parts.length == 2) {
                this.index = parts[1];
            }

            this.currentView = parts[0];
        },

        save: async function() {
            const selected_tool = this.config.tool["selected-tool"];
            const saveModbus =
                selected_tool !== "pwm" &&
                selected_tool !== "laser" &&
                selected_tool !== "router";
            const settings = {
                ["tool"]: { ...this.config.tool },
                ["pwm-spindle"]: { ...this.config["pwm-spindle"] },
                ["modbus-spindle"]: saveModbus
                    ? { ...this.config["modbus-spindle"] }
                    : undefined,
            };
            delete settings.tool["tool-type"];

            this.config["selected-tool-settings"][selected_tool] = settings;
            this.display_units = this.config.settings["units"];
  
            try {
                await api.put("config/save", this.config);
                this.modified = false;
            } catch (error) {
                console.error("Save failed:", error);
                alert("Save failed");
            }
        },

        close_messages: function(action) {
            if (action == "stop") {
                api.put("stop");
            }

            if (action == "continue") {
                api.put("unpause");
            }

            // Acknowledge messages
            if (this.state.messages.length) {
                const id = this.state.messages.slice(-1)[0].id;
                api.put(`message/${id}/ack`);
            }
        },
    },
});
