

// [BOOTLOADB] or [BOOTLOADPIOB]
function bootload_obj() {
    this.update = function (input) {
        var MsgName = "[BOOTLOADB]"
        var start = input.indexOf(MsgName);
        if (start < 0) {
            MsgName = "[BOOTLOADPIOB]"
            start = input.indexOf(MsgName);
            if (start < 0) {
                return false;
            }
        }
        this.name = MsgName;

        //init the parser
        var p = new parser(input, start + MsgName.length);

        //get the bootloadb or bootloadpiob info
        this.length = p.fetchU16();
        this.subId = p.fetchU08();
        this.passFail = p.fetchU08();
        this.validCount = p.fetchU16();
        this.address = p.fetchU32();
        return true;
    }
}

function zeroPad(num, size) {
    var s = "000000000" + num;
    return s.substr(s.length - size);
}

function writeTextArea(doc, areaId, text) {
    var textArea = doc.getElementById(areaId);

    var d = new Date();
    var time = zeroPad(d.getHours(), 2) + ":" + zeroPad(d.getMinutes(), 2) + ":" + zeroPad(d.getSeconds(), 2) + "." + zeroPad(d.getMilliseconds(), 3);
    var out = time + ": " + text + "\r\n";
    textArea.value += out;
    textArea.scrollTop = textArea.scrollHeight;
}

function initTextArea(doc, areaId) {
    var textArea = doc.getElementById(areaId);
    textArea.value = "";
}



function startBootLoad(srecFile, loadType, doc) {
    srecFile.setLoadType(loadType);
    srecFile.setStartPos();
    srecFile.startProgress();
    if (srecFile.isLoadTypePIO() == true) {
        srecFile.setState(loadState.WaitPingBoot);
        writeTextArea(doc, "statusArea", "Pinging the Receiver");
        sendBootLoadPingCmd("[BOOTLOADPIOB]");
        startWarning();
    } else if (srecFile.isLoadTypeGNSS() == true) {
        srecFile.setState(loadState.WaitPingNav);
        writeTextArea(doc, "statusArea", "Starting GNSS BOOT2");
        SendNovaCmd("[BOOTLOADA]PING,BOOT2");
        startWarning();
    } else {
        alert("startBootLoad(): Invalid loadType " + loadType);
    }
}

var pingInterval;
var writeInterval;
var resetPingInterval;
var updateStatus;
function bootloadStateMachine(msgObj, srecFile, srecBuffer, doc) {
    var loadType = srecFile.getLoadType();
    

    srecFile.updateProgress(0);
    switch (srecFile.getState()) {

        case loadState.WaitPingNav:
            if ((msgObj.name == "[BOOTLOADA]") || (msgObj.name == "[??]")){
                srecFile.setState(loadState.WaitPingBoot);
                pingInterval = setInterval(function () {
                    writeTextArea(doc, "statusArea", "Pinging GNSS BOOT2");
                    sendBootLoadPingCmd("[BOOTLOADB]");
                }, 3700);
            } 
            break;

        case loadState.WaitPingBoot:
            if (msgObj.subId == bootLoadSubId.NB_PingRep) {
                doc.getElementById("updateStatus").innerHTML = "Updating . . .";
                if (msgObj.name == "[BOOTLOADB]") {
                    writeTextArea(doc, "statusArea", "Running GNSS BOOT2");
                    clearInterval(pingInterval);
                } else if (msgObj.name == "[BOOTLOADPIOB]") {
                    writeTextArea(doc, "statusArea", "Received Ping Response");
                }
                if (msgObj.passFail == 1) {
                    writeTextArea(doc, "statusArea", "Setup, Start: " + toHexString(srecFile.getStartAddr(), 8) + " Last: " + toHexString(srecFile.getLastAddr(), 8));
                    srecFile.setState(loadState.WaitSetup);
                    sendBootLoadSetupCmd(msgObj.name, srecFile.getStartAddr(), srecFile.getLastAddr());
                } else {
                    alert("Ping: failed");
                }
            }
            break;

        case loadState.WaitSetup:
            if (msgObj.subId == bootLoadSubId.NB_SetupRep) {
                if (msgObj.passFail == 1) {
                    srecFile.setState(loadState.WaitLoadData);
                    if (srecFile.getNextChunk(srecBuffer) != eSrecResult.RS_OK) {
                        updateStatus = "Firmware Update Failed";
                        doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
                        writeTextArea(doc, "statusArea", "Failed Reading an S19 Record");
                        srecFile.setState(loadState.WaitReset);
                        sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                        break;
                    }
                    writeTextArea(doc, "statusArea", "Setup Complete, Loading Firmware");
                    doc.getElementById("updateStatus").innerHTML = "Loading Firmware . . .";
                    sendBootLoadDataCmd(msgObj.name, srecFile.getNextDataFrame(), srecBuffer.address, srecBuffer.data, srecBuffer.len);
                } else {
                    writeTextArea(doc, "statusArea", "Setup Failed");
                    updateStatus = "Firmware Update Failed";
                    doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
                    srecFile.setState(loadState.WaitReset);
                    sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                }
            }
            break;

        case loadState.WaitLoadData:
            if ((msgObj.subId == bootLoadSubId.NB_LoadDataRep) && (msgObj.passFail == 1)) {
                    srecFile.updateProgress(srecBuffer.len);
                srecFile.retryCount = 0;
                    doc.getElementById("loadBar").style.width = srecFile.getPercentLoaded() + "%";
                    doc.getElementById("bytesLoaded").innerHTML = srecFile.getBytesLoaded() + "/" + srecFile.getTotalBytes();
                    if (srecFile.getNextChunk(srecBuffer) != eSrecResult.RS_OK) {
                    // Failed reading data from the S19 file, probably an S19 CRC failure.
                        updateStatus = "Firmware Update Failed";
                        doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
                        writeTextArea(doc, "statusArea", "Failed Reading an S19 Record");
                        srecFile.setState(loadState.WaitReset);
                        sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                        break;
                    }
                    if (srecBuffer.len > 0) {
                    // More data available, send the next chunk.
                        doc.getElementById("updateStatus").innerHTML = "Loading Firmware . . .";
                        sendBootLoadDataCmd(msgObj.name, srecFile.getNextDataFrame(), srecBuffer.address, srecBuffer.data, srecBuffer.len);
                        break;
                    } else {
                    // Reached EOF and sent all of the data, Do CRC Check.
                        srecFile.setState(loadState.WaitChkCrc);
                        writeTextArea(doc, "statusArea", "Loading Complete, Starting CRC Check");
                        sendBootLoadCmd(msgObj.name, bootLoadSubId.NB_ChkCrcCmd);
                    }
            } else if ((msgObj.name == "<NODATA>") || (msgObj.name == "[??]") || 
                      ((msgObj.subId == bootLoadSubId.NB_LoadDataRep) && (msgObj.passFail == 0))) {
                writeTextArea(doc, "statusArea", "Loading Firmware Failed, Frame: " + srecFile.getDataFrame() + " Address: " + toHexString(srecBuffer.address, 8));
                if (srecFile.retryCount < 5) {
                    srecFile.retryCount++;
                    writeTextArea(doc, "statusArea", "Resending Frame: " + srecFile.getDataFrame() + " Address: " + toHexString(srecBuffer.address, 8));
                    if (srecFile.isLoadTypeGNSS() == true) {
                        sendBootLoadDataCmd("[BOOTLOADB]", srecFile.getDataFrame(), srecBuffer.address, srecBuffer.data, srecBuffer.len);
                } else {
                        sendBootLoadDataCmd("[BOOTLOADPIOB]", srecFile.getDataFrame(), srecBuffer.address, srecBuffer.data, srecBuffer.len);
                    }
                } else {
                    srecFile.retryCount = 0;
                    updateStatus = "Firmware Update Failed";
                    doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
                    writeTextArea(doc, "statusArea", "Exceeded Resend Count, Resetting the Receiver");
                    srecFile.setState(loadState.WaitReset);
                    sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                }
            }
            break;

        case loadState.WaitChkCrc:
            if (msgObj.subId == bootLoadSubId.NB_ChkCrcRep) {
                if (msgObj.passFail == 1) {
                    //alert("CRC Suceeded");
                    writeTextArea(doc, "statusArea", "CRC Check Successful, Writing Flash");
                    srecFile.setState(loadState.WaitWrite);
                    sendBootLoadCmd(msgObj.name, bootLoadSubId.NB_ProgCmd);
                    if (srecFile.isLoadTypeGNSS() == true) {
                        writeInterval = setInterval(function () {
                            // Sending NB_WriteFCmd to eventually receive the NB_WriteFRep
                            // response.  The NB_WriteFCmd has no effect.
                            sendBootLoadCmd("[BOOTLOADB]", bootLoadSubId.NB_WriteFCmd);
                        }, 3700);
                    }
                }
                else {
                    writeTextArea(doc, "statusArea", "CRC Check Failed");
                    updateStatus = "Firmware Update Failed";
                    doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
                    srecFile.setState(loadState.WaitReset);
                    sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                }
            }
            break;

        case loadState.WaitWrite:
            if (msgObj.subId == bootLoadSubId.NB_WriteFRep) {
                if (srecFile.isLoadTypeGNSS() == true) {
                    clearInterval(writeInterval);
                }
                if (msgObj.passFail == 1) {
                    //alert("Write Flash Suceeded");
                    writeTextArea(doc, "statusArea", "Write Flash Successful");
                    writeTextArea(doc, "statusArea", "Resetting the Receiver");
                    updateStatus = "Firmware Update Successful";
                    if (msgObj.name == "[BOOTLOADB]") {
                        srecFile.setState(loadState.WaitNavReset);
                        sendBootLoadCmd(msgObj.name, bootLoadSubId.NB_ResetCmd);
                    } else {
                        // The PIOB resets itself after sending the NB_WriteFRep so 
                        // just send [PING] commands until it's ready.
                        srecFile.setState(loadState.WaitBootloadPIOReset);
                    }
                    resetPingInterval = setInterval(function () {
                        SendNovaCmd("[PING]");
                        writeTextArea(doc, "statusArea", "Pinging the Receiver");
                    }, 3700);
                } else {
                    writeTextArea(doc, "statusArea", "Write Flash Failed");
                    updateStatus = "Firmware Update Failed";
                    srecFile.setState(loadState.WaitReset);
                    sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
                }
                doc.getElementById("updateStatus").innerHTML = updateStatus + ", Resetting the Receiver";
            }
            break;

        case loadState.WaitNavReset:
            if (msgObj.name == "[PING]") {
                clearInterval(resetPingInterval);
                writeTextArea(doc, "statusArea", "Nav Reset Complete");
                srecFile.setState(loadState.WaitReset);
                sendBootLoadCmd("[BOOTLOADPIOB]", bootLoadSubId.NB_ResetCmd);
            }
            break;

        case loadState.WaitReset:
            if (msgObj.subId == bootLoadSubId.NB_ResetRep) {
                if (msgObj.passFail == 1) {
                    srecFile.setState(loadState.WaitBootloadPIOReset);
                    //writeTextArea(doc, "statusArea", "Resetting the Receiver");
                    resetPingInterval = setInterval(function () {
                        SendNovaCmd("[PING]");
                        writeTextArea(doc, "statusArea", "Pinging the Receiver");
                        srecFile.updateProgress(0);
                    }, 3700);
                }
                else {
                    writeTextArea(doc, "statusArea", "Reset Failed");
                }
            }
            break;

        case loadState.WaitBootloadPIOReset:
            if (msgObj.name == "[PING]") {
                clearInterval(resetPingInterval);
                writeTextArea(doc, "statusArea", "Received Ping Response");
                srecFile.setState(loadState.StateNone);
                writeTextArea(doc, "statusArea", "Receiver Ready");
                doc.getElementById("updateStatus").innerHTML = updateStatus + ", Receiver Ready";
                stopWarning();
            }
            break;
        default:
            break;

    } // end switch

}


// From nova_bin_msg.h
var bootLoadSubId = {
   NB_PingCmd:        1, 
   NB_PingRep:        2,
   NB_BaudCmd:        3,
   NB_BaudRep:        4,
   NB_SetupCmd:       5,
   NB_SetupRep:       6,
   NB_LoadDataCmd:    7,
   NB_LoadDataRep:    8,
   NB_ChkCrcCmd:      9,
   NB_ChkCrcRep:      10,
   NB_ProgCmd:        11,
   NB_ProgRep:        12,
   NB_EraseCmd:       13,
   NB_EraseRep:       14,
   NB_WriteFCmd:      15,
   NB_WriteFRep:      16,
   NB_ResetCmd:       17,
   NB_ResetRep:       18,
   NB_Working:        19
};

// From nova_cmd_piobootb.c
var bootLoadErrorCode = {
   Err_PingTarget:    0,
   Err_InvalidBaud:   1,
   Err_SetupRange:    2,
   Err_LoadData:      3,
   Err_MaxAddr:       4,
   Err_BadCrc:        5,
   Err_EraseFlash:    6,
   Err_WriteFlash:    7,
   Err_Reset:         8,
   Err_SCN:           9,
   Err_PT:            10,  
   Err_SubID:         100   
};

// From software_info.h
var softwareType = {
   ST_Bootblock1:     0,
   ST_Bootblock2:     1,
   ST_NavProg:        2,
   ST_MTest:          3,
   ST_PioBoot:        4,
   ST_PioApp:         5,
   ST_PioBluetooth:   6,
   ST_All:            7

};

var loadState = {
   StateNone:               0,
   WaitVersion:             1,
   WaitPingNav:             2,
   WaitPingBoot:            3,
   WaitBaud:                4,
   WaitPingBaud:            5,
   WaitSetup:               6,
   WaitLoadData:            7,
   WaitChkCrc:              8,
   WaitErase:               9,
   WaitWrite:               10,
   WaitReset:               11,
   WaitPingPort:            12,
   WaitUSBPortClose:        13,
   WaitUSBPortOpen:         14,
   WaitPingUSBPort:         15,
   WaitBootloadPIOReset:    16,
   WaitBootloadNavReset:    17,
   WaitPIORESET:            18
};

// Calculate and append the crc bytes to the end of the command
// cmdArray is a Uint8Array Typed Array
function completeBinaryCmd(cmdArray, cmdLen, startIdx) {
    var len, idx, crc, crcStr;
    
    len = (cmdArray[startIdx + 1] << 8) | (cmdArray[startIdx]);
    idx = startIdx + len;
    crc = crc_CCITT(cmdArray.subarray(startIdx, idx), len);
    crcStr = "*";
    crcStr += toHexString(crc, 4);
    crcStr += "\r\n"; // CRLF
    //alert("crc: " + crcStr);
    for (var i = 0; i < 7; i++) {
        cmdArray[idx++] = crcStr.charCodeAt(i);
    }
    return idx;
}

// dataArray is a Uint8Array Typed Array
function bootLoadCmdSize(mnemonic, dataArraySize) {
    var len = mnemonic.length + 10 + dataArraySize + 7;
    return len;
}

// buildBootLoadCmd()
// dataArray and cmdArray are Uint8Array Typed Arrays
function buildBootLoadCmd(mnemonic, subID, passFail, address, dataArray, dataArrayLen, cmdArray) {
    var crc = 0, idx = 0, i, len, start;
    
    // mnemonic should be either "[BOOTLOADB]" or "[BOOTLOADPIOB]"
    for (idx = 0; idx < mnemonic.length; idx++) {
        cmdArray[idx] = mnemonic.charCodeAt(idx);
    }
    start = idx; // 1st byte of header (1st byte after mnemonic)
    len = 10 + dataArrayLen;
    cmdArray[idx++] = (len & 0xff);
    cmdArray[idx++] = ((len >> 8) & 0xff);
    cmdArray[idx++] = subID;
    cmdArray[idx++] = passFail;
    cmdArray[idx++] = (dataArrayLen & 0xff);
    cmdArray[idx++] = ((dataArrayLen >> 8) & 0xff);
    for (i = 0; i < 4; i++) {
        cmdArray[idx++] = ((address >> (i*8)) & 0xff);
    }
    for (i = 0; i < dataArrayLen; i++) {
        cmdArray[idx++] = dataArray[i];
    }
    len = completeBinaryCmd(cmdArray, idx, start);
    //alert ("crc: " + cmdArray[idx] + " " + cmdArray[idx+1] + " " + cmdArray[idx+2] + " " + cmdArray[idx+3] + " " + cmdArray[idx+4]);
    return len;
}

// sendBootLoadPingCmd()
function sendBootLoadPingCmd(mnemonic, targetType) {
    var dataArray = new Uint8Array(0);
    var cmdLen = bootLoadCmdSize(mnemonic, 0);
    var cmdArray = new Uint8Array(cmdLen);

    targetType = typeof targetType !== 'undefined' ? targetType : 0; // Not used by PIO
    buildBootLoadCmd(mnemonic, bootLoadSubId.NB_PingCmd, 0, targetType, dataArray, 0, cmdArray);
    SendBinaryCmd("/", cmdArray, cmdArray.length);
}

// sendBootLoadSetupCmd()
function sendBootLoadSetupCmd(mnemonic, minAddress, maxAddress) {
    var dataArray = new Uint8Array(4);
    var cmdLen    = bootLoadCmdSize(mnemonic, dataArray.length);
    var cmdArray  = new Uint8Array(cmdLen);

    //alert("sendBootLoadSetupCmd: start " + minAddress + " last " + maxAddress);
    for (var i = 0; i < 4; i++) {
        dataArray[i] = ((maxAddress >> (i * 8)) & 0xff);
    }
    buildBootLoadCmd(mnemonic, bootLoadSubId.NB_SetupCmd, 0, minAddress, dataArray, dataArray.length, cmdArray);
    SendBinaryCmd("/", cmdArray, cmdArray.length);
}

// sendBootLoadDataCmd()
function sendBootLoadDataCmd(mnemonic, dataFrame, startAddress, dataArray, dataArrayLen) {
    var cmdLen = bootLoadCmdSize(mnemonic, dataArrayLen);
    var cmdArray = new Uint8Array(cmdLen);

    buildBootLoadCmd(mnemonic, bootLoadSubId.NB_LoadDataCmd, dataFrame, startAddress, dataArray, dataArrayLen, cmdArray);
    SendBinaryCmd("/", cmdArray, cmdArray.length);
}

// sendBootLoadCmd()
// Use for: NB_ChkCrcCmd, NB_ProgCmd, NB_WriteFCmd, or NB_ResetCmd
function sendBootLoadCmd(mnemonic, subid, holdForResp) {
    var dataArray = new Uint8Array(0);
    var cmdLen = bootLoadCmdSize(mnemonic, 0);
    var cmdArray = new Uint8Array(cmdLen);

    buildBootLoadCmd(mnemonic, subid, 0, 0, dataArray, 0, cmdArray);
    SendBinaryCmd("/", cmdArray, cmdArray.length, holdForResp);
}
