var ver = "1.0.8";
var xmlPids = transport.loadXML("pids.xml");
var groups = xmlPids.getElementsByTagName("group");
var pidDefaults;
var host = "http://localhost:8886";

transformXML(xmlPids, transport.loadXML("tabs.xsl"), "tabs");
transformXML(xmlPids, transport.loadXML("knobs.xsl"), "panel");

function onLoad() {
    var xmlDefaults = transport.loadXML("defaults.xml");
    if (xmlDefaults) pidDefaults = xmlDefaults.getElementsByTagName("param");
    updateTitle();
    self.setTimeout(autoConnect, 1000);
}

var emulator = {
    connected: false,
    port: null,
    dtcCount: new Array,
    dtcCodes: new Array,
    pidDisabled: new Array(256),
    ignition: true,
    connect: function () {
        if (this.connected) return false;
        this.port = null;
        var ports = serial.list();
        for (var n = ports.length - 1; n >= 0; n--) {
            serial.timeout = 500;
            if (serial.open(ports[n], 38400)) {
                if (this.sendCommand("ATINF0") || this.sendCommand("ATINF0")) {
                    this.port = ports[n];
                    this.connected = true;
                    this.init();
                    updateTitle();
                    return true;
                } else {
                    this.disconnect();
                }
            }
            serial.close();
        }
        alert("Freematics OBD-II Emulator is not connected.\nPlease connect it to the PC via USB cable.\n\nFor product information, please visit http://freematics.com/products");
        return false;
    },
    init: function () {
        document.getElementById("status").className = "top_connected";
        document.getElementById("button_conn").innerHTML = "Disconnect";
        document.getElementById("button_ignite").disabled = false;
        if (!this.sendCommand("ATE0") || !this.sendCommand(this.ignition ? "ATACC1" : "ATACC0") || !this.pushReadiness()) {
            alert("Error occurred during initialization");
            this.disconnect();
            return false;
        }
        this.showReadiness();
        self.setTimeout("pushDefaultPidValue(0)", 200);
        return true;
    },
    disconnect: function() {
        serial.close();
        this.connected = false;
        this.port = null;
        document.getElementById("status").className = "top";
        document.getElementById("button_conn").innerHTML = "Connect";
        document.getElementById("button_ignite").disabled = true;
        updateTitle();
    },
    sendCommand: function (cmd, purge) {
        if (purge) {
            while (serial.read()) {
                console.log(serial.response);
            }
        }
        console.log("Send command " + cmd);
        if (serial.write(cmd + "\r", "OK%0D")) {
            console.log("Expected response: " + serial.response);
            return true;
        } else {
            console.log("Invalid response: " + serial.response);
            return false;
        }
    },
    setDTC: function (mode, code) {
        if (this.dtcCount[mode] >= 6) {
            alert("Up to 6 DTC can bet set for a mode. To set additional DTC, please clear previous ones first.");
            return;
        }
        if (this.dtcCodes[mode] && this.dtcCodes[mode].indexOf(code) >= 0) {
            alert("DTC " + code + " was already set for Mode 0" + mode.toString(16));
            return;
        }
	
        var codes = this.dtcCodes[mode];
        if (codes)
            codes += "," + code;
        else
            codes = code;
        if (!this.sendCommand("ATSET DTC=" + codes, true)) {
            alert("Unable to set DTC " + code);
        } else {
            if (!this.dtcCount[mode]) this.dtcCount[mode] = 0;
            this.dtcCount[mode]++;
            this.dtcCodes[mode] = codes;
            alert("DTC " + code + " for Mode 0" + mode.toString(16) + " has been set successfully.\n\nTotally " + this.dtcCount[mode] + " DTCs has been set");
        }
    },
    clearDTC: function (quiet)
    {
        if (!this.sendCommand("ATCLR DTC", true)) {
            if (!quiet) alert("Error clearing DTC");
        } else {
            if (!quiet) alert("DTC cleared successfully");
            this.dtcCount[0x3] = 0;
            this.dtcCount[0x7] = 0;
            this.dtcCount[0xa] = 0;
            this.dtcCodes[0x3] = "";
            this.dtcCodes[0x7] = "";
            this.dtcCodes[0xa] = "";
        }
    },
    setVIN: function (vin)
    {
        if (!this.sendCommand("ATSET VIN=" + vin, true)) {
            alert("Unable to set VIN");
        } else {
            alert("VIN set to " + vin);
        }
    },
    toggleIgnition: function ()
    {
        if (!this.sendCommand(this.ignition ? "ATACC0" : "ATACC1")) {
            alert("Unable to set ignition status");
            return false;
        }
        this.ignition = !this.ignition;
        var e = document.getElementById("button_ignite");
        e.innerHTML = "Ignition is " + (this.ignition ? "ON" : "OFF");
        e.className = this.ignition ? "ignite_button" : "ignite_button_off";
        return true;
    },
    setProtocol: function (protocol)
    {
        if (!this.sendCommand("ATBUS " + protocol, true)) {
            alert("Unable to set the protocol to " + protocol + "\nMake sure the protocol is supported by the emulator.");
        } else {
            var isJ1850 = protocol.substr(0, 5) == "J1850";
            document.getElementById("set_dtc_07").disabled = !isJ1850;
            document.getElementById("set_dtc_0a").disabled = !isJ1850;
            alert("Protocol set successfully");
        }
    },
    setPidValue: function (pid, value)
    {
        if (!serial.write("ATSET " + pid + "=" + value + "\r", "OK%0D")) {
            //alert("Error occurred");
            return false;
        }
        console.log("PID " + pid + " set to " + value);
        return true;
    },
    togglePID: function (pid, enabled)
    {
        var i = parseInt(pid, 16) & 0xff;
        this.pidDisabled[i] = !enabled;
        var specialPid = i & 0xE0;
        var val = 0;
        var vals = "";
        var pidTmp = specialPid + 1;
        for (m = 0; m < 4; m++) {
            val = 0;
            for (n = 1; n <= 8; n++) {
                val <<= 1;
                if (!this.pidDisabled[pidTmp++]) val |= 1;
            }
            vals += val.toString(16) + ",";
        }
        specialPid |= 0x100;
        var cmd = "ATSET 0" + specialPid.toString(16) + "=" + vals.substr(0, vals.length - 1);
        if (!this.sendCommand(cmd)) {
            alert("Unable to set PID " + specialPid.toString(16));
            return false;
        } else {
            alert("PID " + pid + " is " + (enabled ? "enabled" : "disabled"));
        }
    },
    toggleVIN: function (enabled)
    {
        if (this.sendCommand(enabled ? "ATVIN1" : "ATVIN0")) {
            alert("VIN access is " + (enabled ? "enabled" : "disabled"));
        } else {
            alert("Error toggling VIN access");
        }
    },
    // readiness monitors (PID 0101)
    readiness: new Array(0, 0x7, 0xff, 0),
    pushReadiness: function () {
        var cmd = "ATSET 0101=0x" + byte2hex(this.readiness[0]) + ",0x" + byte2hex(this.readiness[1]) + ",0x" + byte2hex(this.readiness[2]) + ",0x" + byte2hex(this.readiness[3]);
        return this.sendCommand(cmd);
    },
    getReadinessBit: function (code)
    {
        var byte = 0;
        switch (code.substr(0, 1)) {
            case 'A':
                byte = this.readiness[0];
                break;
            case 'B':
                byte = this.readiness[1];
                break;
            case 'C':
                byte = this.readiness[2];
                break;
            case 'D':
                byte = this.readiness[3];
                break;
        }
        return (byte & (1 << parseInt(code.substr(1, 1)))) != 0;
    },
    setReadinessBit:function (code, set)
    {
        var bit = 1 << parseInt(code.substr(1, 1));
        switch (code.substr(0, 1)) {
            case 'A':
                if (set)
                    this.readiness[0] |= bit;
                else 
                    this.readiness[0] &= ~bit;
                break;
            case 'B':
                if (set)
                    this.readiness[1] |= bit;
                else 
                    this.readiness[1] &= ~bit;
                break;
            case 'C':
                if (set)
                    this.readiness[2] |= bit;
                else 
                    this.readiness[2] &= ~bit;
                break;
            case 'D':
                if (set)
                    this.readiness[3] |= bit;
                else 
                    this.readiness[3] &= ~bit;
                break;
        }
    },
    showReadiness:function ()
    {
        var list;
        for (m = 0; m < groups.length; m++) {
            if (groups[m].getAttribute("name") == "Readiness") {
                list = groups[m].getElementsByTagName("readiness");
            }
        }
        if (!list) return;
        for (var i = 0; i < list.length; i++) {
            var id = list[i].getAttribute("id");
            document.getElementById("Availability-" + id).setAttribute("checked", this.getReadinessBit(list[i].getAttribute("available")) ? "checked" : "");
            document.getElementById("Readiness-" + id).innerHTML = this.getReadinessBit(list[i].getAttribute("complete")) ? "Incomplete" : "Complete";
        }
        document.getElementById("DTC_COUNT").value = this.readiness[0] & 0x7f;
    },
    setDTCCount:function (value)
    {
        if (value >= 0 && value <= 127) {
            var MIL = value > 0 ? 0x80 : 0;
            this.readiness[0] = MIL | value;
            if (pushReadiness()) 
                document.getElementById("DTC_COUNT").value = value;
        }
    },
    setAvailability:function (code, set)
    {
        this.setReadinessBit(code, set);
        return this.pushReadiness();
    },
    setCompleteness:function (code, set)
    {
        this.setReadinessBit(code, set);
        if (this.pushReadiness()) {
            return set ? "Incomplete" : "Complete";
        } else {
            return "";
        }	
    }
};

function updateTitle()
{
    document.title = "Freematics Emulator " + ver + " - " + (emulator.connected ? "Connected" : "Disconnected");
}

function autoConnect()
{
    if (emulator.connected) {
        emulator.disconnect();
        return;
    }
    document.getElementById("button_conn").innerHTML = "Connecting...";
    self.setTimeout("emulator.connect()", 0);
}

function byte2hex(n)
{
	return n >= 0x10 ? n.toString(16) : ("0" + n.toString(16));
}

function updateFirmware()
{
	alert("Please be aware there are two types of Freematics OBD-II Emulator (with and without J1850 support). Flashing a wrong firmware file will likely brick your device.")
	openItem("FirmwareUpdater.exe");
    emulator.disconnect();
}

function replayLogFile()
{
	alert("This feature is not implemented yet");
}

function pushDefaultPidValue(index) {
    if (index < pidDefaults.length && emulator.connected) {
        var pid = pidDefaults[index].getAttribute("id");
        var value = pidDefaults[index].getAttribute("value");
        serial.write("ATSET " + pid + "=" + value + "\r");
        for (m = 0; m < groups.length; m++) {
            var e = document.getElementById(groups[m].getAttribute("name") + "-" + pid);
            if (e) {
                e.value = value;
            }
        }
        self.setTimeout("pushDefaultPidValue(" + (index + 1) + ")", 0);
    }
}

var knob = {
    pidElement: null,
    pidDiff: 0,
    pidInterval: 0,
    pidValueMin: 0,
    pidValueMax: 0,
    pidActive: 0,
    StartChangeValue: function (element, pid, ratio, minval, maxval) {
        this.pidActive = pid;
        this.pidElement = element;
        this.pidDiff = (maxval > 1000 ? 10 : 1) * ratio;
        this.pidInterval = 200;
        this.pidValueMin = minval;
        this.pidValueMax = maxval;
        this.timerChangeValue();
    },
    timerChangeValue: function () {
        if (this.pidElement) {
            var val = parseInt(this.pidElement.value);
            val += this.pidDiff;
            if (val < this.pidValueMin)
                val = this.pidValueMin;
            else if (val > this.pidValueMax)
                val = this.pidValueMax;

            this.setValue(this.pidActive, val);
            if (this.pidDiff != 0) {
                this.pidElement.value = val;
                if (this.pidInterval > 50)
                    this.pidInterval -= 10;
                if (this.pidInterval) self.setTimeout("knob.timerChangeValue()", this.pidInterval);
            }
        }
    },
    setValue: function (pid, value) {
        serial.write("ATSET " + pid + "=" + value + "\r");
    },
    StopChangeValue: function () {
        this.pidDiff = 0;
        this.pidInterval = 0;
        this.timerChangeValue();
        this.pidElement = null;
    }
};
