//Define variables
var stringReceived = ""; //note that the string is added to not necessarily one character at a time...
var AvailablePorts;
var eraseIndicator = false;
var erasing = false;
var tempString = "";
var TWindow = $("#Terminal");
var TerminalOverlay = $("#TerminalOverlay");
var TerminalWindow = document.getElementById("Terminal");

var TerminalCommandLine = document.getElementById("TerminalCommand");
var AdviceWindow = document.getElementById("AdviceWindow");
var Connection = function() {
    this.connectionId = null;
}

const serial = chrome.serial;
//Get available ports
var onGetDevices = function(ports) {
    if (ports[0] == undefined) {
        if (Platform_OS == "linux" || "chrome") {
            ErrorMessager(
                "Sorry, communications attempt failed." + "<br>" + "Problem M2: No valid ports were found on your computer", +"<br>" +
                "Try a TROUBLESHOOTING tip below, and then click GO! again:" +
                "<br>" + "[1] Disconnect and then reconnect USB-to-serial adapter to your computer." +
                "<br>" + "[2] Close then reopen TRAFx Communicator." +
                "<br>" + "[3] Reboot your computer." +
                "<br>" + "[4] Try a different computer."
            )
        } else {
            ErrorMessager(
                "Sorry, communications attempt failed." + "<br>" + "Problem M2: No valid ports were found on your computer", +"<br>" +
                "Try a TROUBLESHOOTING tip below, and then click GO! again:" +
                "<br>" + "[1] Disconnect and reconnect everything (e.g. PC---cable---dock---counter)." +
                "<br>" + "[2] Ensure that the USB-to-serial adapter driver software has been installed." +
                "<br>" + "[3] Close then reopen TRAFx Communicator." +
                "<br>" + "[4] Reboot your computer." +
                "<br>" + "[5] Still not working? Use the Troubleshooting Wizard at: <span id='supportMac' class='fakeLink'>www.trafx.net/support</span>");
            $("#supportMac").click(openSupport);
        }
    } else { //Mac
        FirstPort = ports[0].path;
        LastPort = ports[ports.length - 1].path;
    }
};

serial.getDevices(onGetDevices);

//for detecting OS
chrome.runtime.getPlatformInfo(DetectPlatform);

Connection.open = false;
Connection.downloading = false;
Connection.erasing = false;
var logFile = "";
var CurrentTime = new Date().getTime();
var version = "200310sdk";
var FileName = "Shuttlefile ";
var Platform_OS = "";


//Button handlers
document.querySelector("#download").onclick = InitializeDownload;
document.querySelector("#zzzz").onclick = doZZZZ;
document.querySelector("#about").onclick = AboutApplication;
document.getElementById("go").onclick = CheckForPorts;
document.getElementById("clear").onclick = clear;
document.getElementById("advanced").onclick = AdvancedOptions;
TerminalCommandLine.onblur = RegainFocus;
$("#yesPCTime").click(InsertPCTime);
$("#noPCTime").click(DontInsertPCTime);

serial.onReceive.addListener(onReceiveCallback);

//capturing backspace
TerminalCommandLine.onkeydown = function(e) { //this works, but there are some bonus characters that the dock outputs. these are likely bs outputs that we should detect and interpret correctly.
    var key = event.keyCode || event.charCode;
    if (key == 8 || key == 46) { //backspace
        //console.log("backspace pressed: " + key);
        var UserChar = String.fromCharCode(key);
        WriteSerial(UserChar);
    }
}
//capturing other keypresses, e.g. d for download,
TerminalCommandLine.onkeypress = function(e) {
    var keyPress;
    if (typeof event !== 'undefined') {
        keyPress = event.keyCode;
    } else if (e) {
        keyPress = e.which;
    }
    console.log("KeyPress: " + keyPress); //log keypress since the detected keyPress does not match the code tables.
    if (Connection.open == true) {
        if (keyPress == 67 || keyPress == 99) { //detect 67="c" or 99=num3 for c for configure  
            if (stringReceived.substring(stringReceived.length - 4).indexOf("+>>") !== -1) { //check if we're at "enter command"
                showDatePop();
                //Todo: disable command input so folks can't type in the background while they're deciding whether to do it manually or not
                WriteSerial("c");
            } else { //if not next to command line (e.g. naming counter), just write c to serial
                WriteSerial("c");
            }
        }
        if (keyPress == 89 || keyPress == 121) { //detect 89="y" or 121=f10 for y for yes   
            if (stringReceived.substring(stringReceived.length - 30).indexOf("stored in dock? (Yes/No):") !== -1) { //check if we're at "confirm erase dock"
                InitializeErase();
				console.log("erase initialized");
            } else { //if not next to erase confirm (e.g. naming counter), just write y to serial
                WriteSerial("y");
            }
        }       
        if (keyPress == 100 || keyPress == 68) { //detect a user-entered d 100=num4 68=d
            if (stringReceived.substring(stringReceived.length - 4).indexOf("+>>") !== -1) { //initialize download if we're at "enter command"
                InitializeDownload();
            } else { //if not currently at "enter command" (e.g. naming counter), just write d to serial since they could be typing a counter name etc.
                WriteSerial("d");
            }
        }
        else if (Connection.downloading == false) {
            if (stringReceived.length > 2000) { //If the dock/counter output is longer than 2000 characters (except during downloads), cut off the first characters so there are just 2000 left
                stringReceived = stringReceived.substring(stringReceived.length - 2000, stringReceived.length);
                //console.log("LogTruncated");
            };
            //If the above special cases don't apply, just send the character to the dock
            var UserChar = String.fromCharCode(keyPress);
            WriteSerial(UserChar);
        }
    } else {
        NotConnected("onkeypress");
    };
    return false; // Prevents the default action
};

//ending the download based on timeout
serial.onReceiveError.addListener(function(info) {
    if (info.error == "timeout" && Connection.downloading == true) {
        DetectEndString();
    } else if (info.error == "timeout" && Connection.erasing == true){
        DetectEraseEndString();
    } else if (info.error == "timeout") {
        TestForDock();
    }
});



//error handling section
function NotConnected() {
    console.error("Not connected, error raised by:", caller);
    ErrorMessager("Not connected", "You cannot perform that operation prior to being connected to a port");
};

function ErrorMessager(error, advice) {
    AdviceWindow.innerHTML = (error + "<br>" + "---------------------------------------------------------------------------" + "<br>" + advice);
}

//These two functions violate DRY with just a slight difference between them.  Ideally consolidate.
function DetectEraseEndString() {
    if (stringReceived.indexOf("++ ENTER COMMAND ++>>") !== -1) {
        AdviceWindow.innerHTML = "--Use Download+ button to download and save." +
            "<br>" + "<span class='tabbed'>--If you get stuck or lost: click ZZZZ or close and re-open TRAFx Communicator.</span>" +
            "<br>" + "<span class='tabbed'>--If there are many garbled characters or text you may need to change the counter's batteries or reboot your computer.</span>";
        finalizeErase();
    } else {
        ErrorMessager("Possible Download Problem", "Try the advice below." //Then, if necessary, method B."
            +
            "<br>" +
            "[1] Disconnect and reconnect everything (e.g., computer---cable---dock---counter)." +
            "<br>" + "[2] Close then reopen TRAFx Communicator." +
            "<br>" + "[3] Reattempt download.");
        Connection.downloading = false;
        DisplayOutput(stringReceived);
    }
}
function DetectEndString() {
    if (stringReceived.indexOf("++ ENTER COMMAND ++>>") !== -1) {
        AdviceWindow.innerHTML = "--Use Download+ button to download and save." +
            "<br>" + "<span class='tabbed'>--If you get stuck or lost: click ZZZZ or close and re-open TRAFx Communicator.</span>" +
            "<br>" + "<span class='tabbed'>--If there are many garbled characters or text you may need to change the counter's batteries or reboot your computer.</span>";
        finalizeDownload();
    } else {
        ErrorMessager("Possible Download Problem", "Try the advice below." //Then, if necessary, method B."
            +
            "<br>" +
            "[1] Disconnect and reconnect everything (e.g., computer---cable---dock---counter)." +
            "<br>" + "[2] Close then reopen TRAFx Communicator." +
            "<br>" + "[3] Reattempt download.");
        Connection.downloading = false;
        DisplayOutput(stringReceived);
    }
}

//ArrayBuffer conversion functions:
function ArrayBuffertoString(buf) {
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

function StringtoArrayBuffer(str) {
    var buf = new ArrayBuffer(str.length);
    var bufView = new Uint8Array(buf);
    for (var i = 0; i < str.length; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

function saveFile(Data, DockTime) {
    ourName = FileName + DockTime + '.txt';
    //console.log("suggestedname: " + ourName);
    chrome.fileSystem.chooseEntry({
            type: 'saveFile',
            suggestedName: ourName
        },
        function(writableFileEntry) {
            writableFileEntry.createWriter(function(writer) {
                writer.write(new Blob([Data], {
                    type: 'text/plain'
                }));
            });
        });
}

function DetermineDockTime() { //Determines dock or PC mode counter time
    TimeString = stringReceived.substring(stringReceived.lastIndexOf("TIME (yy-mm-dd hh:mm):") + 22); // this works
    var DockTime = TimeString.substring(0, [17]); //This could be dock time (shuttle mode) or counter time (PC mode) and will grab either, so the variable name is a bit misleading
    DockTime = DockTime.replace(/:/g, "");
    DockTime = DockTime.replace(/-/g, "");
    DockTime = DockTime.replace(/,/g, " "); //for PC mode time
    if (stringReceived.indexOf("DOCK TIME") == -1) { //do this if dock time isn't found in the terminal output, aka if we're in PC Mode
        FileName = stringReceived.substring(stringReceived.lastIndexOf("Counter name   :") + 16, stringReceived.lastIndexOf("Counter name   :") + 36); //grab counter name up to 20 chars long
        FileName = FileName.replace(/ /g, '') + " "; //trim off trailing whitespace, replace with just one
    } else { //do this if we're in shuttle mode
        FileName = "Shuttlefile ";
        DockTime = DockTime.substring(1,12); //Weird bug where the first character for docktime is something that .trim() won't get rid of.
    };
    if (stringReceived == "") {
        Connection.downloading = false;
    } else {
        saveFile(stringReceived, DockTime);
    }
}

function clear() {
    if (Connection.open == true) {
        //TerminalWindow.textContent
        
        var ClearedLog = "TRAFx Communicator Mac Version " + version + "\n" +
            "\n" + "====================================" +
            "\n" + "C_onfigure, V_iew, D_ownload, E_rase" +
            "\n" + "====================================" +
            "\n" + "++ ENTER COMMAND ++>>";
            
        if (Connection.downloading == false) {
            stringReceived = ClearedLog; //overwrite the huge stringReceived var
            DisplayOutput(stringReceived);
        } else {
            TerminalWindow.textContent = ClearedLog;
            console.log("failed to clear");
        }
        
        
        AdviceWindow.innerHTML = "Use Download+ button to download and save." +
            "<br>" + "If you get stuck or lost: click ZZZZ or close and re-open TRAFx Communicator." +
            "<br>" + "If there are many garbled characters or text you may need to change the counter's batteries or reboot your computer.";
    }
}

function TestForDock() {
    if (TerminalWindow.textContent.indexOf("TRAFx") == -1 && Connection.connectionId !== undefined) {
        ErrorMessager("Sorry, communications attempt failed " + "[Problem M1: No reply from counter or dock]", "Try a TROUBLESHOOTING tip below, and then click GO! again:" +
            "<br>" + "[1] Disconnect and reconnect everything (e.g., computer---cable---dock---counter)." +
            "<br>" + "[2] Is Dock in correct mode?" +
            "<br>" + "<span class='tabbed'> [a] PC MODE: computer---cable---Dock---COUNTER</span>" +
            "<br>" + "<span class='tabbed'> [b] Shuttle MODE: computer---cable---Dock   (NO COUNTER)</span>" +
            "<br>" + "[3] Still not working? Use the Troubleshooting Wizard at: <span id='support' class='fakeLink'>www.trafx.net/support</span>");
        $("#support").click(openSupport);
    } else {
        buttons = document.getElementsByClassName("button");
        for (var i = 0; i < buttons.length; i++) {
            buttons[i].style.color = "black";
            buttons[i].disabled = false;
        }
    };
}

function AdvancedOptions() {
    checkBox = document.getElementById("advanced");
    var AdvancedMenuElements = document.getElementsByClassName("advanced_menu");
    if (checkBox.checked == true) {
        for (var i = 0; i < AdvancedMenuElements.length; i++) {
            AdvancedMenuElements[i].style.visibility = "visible"
        }
    } else {
        for (var i = 0; i < AdvancedMenuElements.length; i++) {
            AdvancedMenuElements[i].style.visibility = "hidden"
        }
    };
}

function AboutApplication() {
    AdviceWindow.innerHTML = ("TRAFx Communicator Mac enables TRAFx customers to communicate with their TRAFx counting equipment using a MAC computer.")
}

function loadingIndicator() {
    if (TerminalWindow.textContent.indexOf(">") !== -1 && Connection.downloading == true) {
        TerminalWindow.textContent = TerminalWindow.textContent.replace(/>/g, '<');
    } else {
        TerminalWindow.textContent = TerminalWindow.textContent.replace(/</g, '>')
    }
}

function RegainFocus() {
    TerminalCommandLine.focus();
}

function DetectPlatform(PlatformInfo) {
    Platform_OS = PlatformInfo.os;
}

function CheckForPorts() {
    if (TerminalWindow.textContent.indexOf("TRAFx") == -1) {
        serial.getDevices(onGetDevices);
        //autoconnect to /dev.tty.usbserial if available because we assume the counter will always be on this mac port
        if (Platform_OS == "mac") {
            autoConnect("/dev/tty.usbserial");
        } else if (Platform_OS == "linux") {
            autoConnect(LastPort)
        } else {
            autoConnect(FirstPort) //for windows and chromebook
        };
    } else {
        WriteSerial("zzzz");
        AdviceWindow.innerHTML = (
            "Communication established" + "<br>" + "--Use Download+ button to download and save." +
            "<br>" + "--If you get stuck or lost: click ZZZZ or close and re-open TRAFx Communicator." +
            "<br>" + "--If there are many garbled characters or text you may need to change the counter's batteries or reboot your computer."
        );
    }
}

//The great and mighty auto connect function
function autoConnect(port) {
    serial.connect(port, {
        bitrate: 115200,
        receiveTimeout: 200
    }, function(Cinfo) {
        if (Cinfo === undefined) {
            console.error("Failed to connect to:", port);
            ErrorMessager(
                "Sorry, communications attempt failed." + "<br>" + "Problem M3: Port-related issue", "Try a TROUBLESHOOTING tip below, and then click GO! again:" +
                "<br>" + "[1] Disconnect and reconnect everything (e.g., computer---cable---dock---counter)." +
                "<br>" + "[2] Close then reopen TRAFx Communicator." +
                "<br>" + "[3] Close other programs that may be using serial or USB ports." +
                "<br>" + "[4] If using a laptop docking station, try without."
            );          
        } else {
            Connection.connectionId = Cinfo.connectionId;
            Connection.open = true;
            AdviceWindow.innerHTML = (
                "Communication established" + "<br>" + "---------------------------------------------------------------------------" + "<br>" + "--Use Download+ button to download and save." +
                "<br>" + "--If you get stuck or lost: click ZZZZ or close and re-open TRAFx Communicator." +
                "<br>" + "--If there are many garbled characters or text you may need to change the counter's batteries or reboot your computer."
            );
            console.log("Autoconnected to dock on port:", port, Cinfo);
            WriteSerial("zzzz");
        }
    })
}

//Write to port
function WriteSerial(Sendstring) {
    serial.send(Connection.connectionId, StringtoArrayBuffer(Sendstring), function() {});
}

//for receiving data from connection.  This is a bit inefficient because e.g. for one press of z, it's rewriting the window 365 times...  
function DisplayOutput(string) {
    //Capture backspace presses, which looks like: " "
    var lastThreeChars = string.substr(string.length -3); //grab the last three characters output by the dock
    var bsSbs = String.fromCharCode(8) + " " + String.fromCharCode(8); //create " " for comparison
    if (lastThreeChars == bsSbs) { //check if last three characters are " "
        stringReceived = string.substr(0, string.length - 4); //remove the last 4 characters from the main underlying var. If we don't do this, it shows up the next time the window regens
        string = string.substr(0, string.length - 4); //and remove from the string wer'e about to output also
    }
    TerminalWindow.textContent = string;
    TerminalWindow.scrollTop = TerminalWindow.scrollHeight; //set scrollposition to bottom of terminal window
}

function onReceiveCallback(info) {
    var DataRecieved = true
    var str = ArrayBuffertoString(info.data);
    stringReceived += str;
    if (Connection.downloading == false) { //if we're not downloading, then display it
        DisplayOutput(stringReceived)
    } else { //If we're downloading, save the stringreceived
        logFile = stringReceived;
    }
}

//to send d to counter
function InitializeDownload() {
    if (Connection.open == true) {
        WriteSerial("d");
        window.setTimeout(function() {
            Connection.downloading = true;
            WriteSerial("n");
            $("#loadingIndicator").removeClass("hidden");//show loading gif
            $("#TerminalCommand").attr("disabled","disabled"); //disable input while downloading
            TerminalWindow.style.fontSize = "20px";
            TerminalWindow.textContent = "Download in progress, please wait >>>>";
        }, 100);
        stringReceived = ""; //start with a blank stringReceived so we don't download the whole command history of that session
        Timer = window.setInterval(loadingIndicator, 1000); //make the chevrons dance back and forth
    } else {
        NotConnected("Download");
    }
}
function InitializeErase() {
    if (Connection.open == true) {
        WriteSerial("y"); //need to actually type y for yes
        window.setTimeout(function() {
            Connection.erasing = true;
            $("#loadingIndicator").removeClass("hidden");//show loading gif
            $("#TerminalCommand").attr("disabled","disabled"); //disable input while downloading
            TerminalOverlay.text("Erasing dock's memory, please wait...");
            TerminalOverlay.removeClass("hidden");
        }, 100);
		stringReceived = ""; //set it empty so we don't start with a whole lotta junk
    } else {
        NotConnected("Erase");
    }
}
function finalizeErase() {
    Connection.erasing = false;
    $("#loadingIndicator").addClass("hidden");//hide the loading gif again
    TerminalOverlay.addClass("hidden"); //hide the overlay
	//should probably combine the gif and overlay into one overlay, but that would mean bringing the downloading screen in line and using the same overlay for that
    $("#TerminalCommand").removeAttr("disabled"); //re-enable input when done downloading
    RegainFocus();//focus on commandline again
	
}

function doZZZZ() {
    if (Connection.open == true) {
        WriteSerial("zzzz")
    } else {
        NotConnected("doZZZZ")
    };
}

function finalizeDownload() {
    Connection.downloading = false;
    DetermineDockTime(); //This is really a save function
    $("#loadingIndicator").addClass("hidden");//hide the loading gif again
    $("#TerminalCommand").removeAttr("disabled"); //re-enable input when done downloading
    RegainFocus();//focus on commandline again
    DisplayOutput(stringReceived);
    window.clearInterval(Timer);
    TerminalWindow.style.fontSize = "13px";
}
function showDatePop() {
	$("#datePop").removeClass("hidden");
	$("#TerminalCommand").attr("disabled","disabled"); //disable input while prompt is up
}
function InsertPCTime(){
    $("#datePop").addClass("hidden");
	$("#TerminalCommand").removeAttr("disabled"); //re-enable input when user clicks a button
    var TimeCurrent = new Date($.now());
	console.log(TimeCurrent);
    var YearFormatted = ("" + TimeCurrent.getFullYear()).slice(-2); //Only need last two digits
    var MonthFormatted = ("0" + (TimeCurrent.getMonth()+1)).slice(-2); //needs to have leading 0 when applicable and always be 2 digits. Month numbers range from 0-11 so need to +1 to match up with normal human conventions.
    var DateFormatted = ("0" + TimeCurrent.getDate()).slice(-2); //needs to have leading 0 when applicable and always be 2 digits
    var HoursFormatted = ("0" + TimeCurrent.getHours()).slice(-2); //needs to have leading 0 when applicable and always be 2 digits
    var MinutesFormatted = ("0" + TimeCurrent.getMinutes()).slice(-2); //needs to have leading 0 when applicable and always be 2 digits
    var TimeFormatted = YearFormatted + "-" + MonthFormatted + "-" + DateFormatted + " " + HoursFormatted + ":" + MinutesFormatted; //put it all together
    var EnterPress = String.fromCharCode(13); //Enter key press 
    WriteSerial(TimeFormatted + EnterPress);
    RegainFocus();
    TerminalWindow.scrollTop = TerminalWindow.scrollHeight; //set scrollposition to bottom of terminal window
    //Todo: re-enable command input
}
function DontInsertPCTime(){
    $("#datePop").addClass("hidden");
	$("#TerminalCommand").removeAttr("disabled"); //re-enable input when user clicks a button
	if (stringReceived.substring(stringReceived.length - 4).indexOf(">>") !== -1) { //only do for shuttle mode, behaves properly for PC mode already
		stringReceived = stringReceived.substr(0, stringReceived.length -1); //cut off last character so things line up nicely when typing in the new date/time
	}
    RegainFocus();
    TerminalWindow.scrollTop = TerminalWindow.scrollHeight; //set scrollposition to bottom of terminal window
}
function openSupport(){ //opens a link in an external browser instead of within nwjs
    nw.Shell.openExternal('https://www.trafx.net/support');
    return false;
}


//Coded by Stefan Martin and subsequently contributed to and converted for use by NW.js by Tobias Haager