

var eSrecResult = {
    RS_OK:     0,
    RS_ERR:    1,
    RS_EOF:    2,
    RS_NONE:   3
};

function SoftwareInfo() {
    this.crcPtr = 0;
    this.version = null;
    this.swType = 0;
    this.date = null;
    this.productType = 0;
    this.swSpinNumber = 0;
    this.swInfoVersion = 0;
    this.product = null;
}

function SrecComponent() {
    this.startPos           = 0;
    this.startAddr          = 0;
    this.lastAddr           = 0;
    this.totalBytes         = 0;
    this.totalLoaded        = 0;
    this.percentLoaded = 0;
    this.info        = null;
}

function SrecFile() 
{
    this.len           = 0;
    this.pos           = 0;
    this.loadState     = 0;
    this.loadType      = -1;
    this.dataFrame     = 0;
    this.retryCount    = 0;
    this.fileName      = null;
    this.data          = null;
    this.components    = new Array(6);
    for(var i=0; i<6; i++){
        this.components[i] = new SrecComponent();
    } 
}

function getNextDataFrame() {
    this.dataFrame = (this.dataFrame + 1) % 256;
    if (this.dataFrame == 0) {
        this.dataFrame = 1;
    }
    return this.dataFrame;
}
SrecFile.prototype.getNextDataFrame = getNextDataFrame;

function getDataFrame() {
    return this.dataFrame;
}
SrecFile.prototype.getDataFrame = getDataFrame;


function getSrecLoadState() {
    return this.loadState;
}
SrecFile.prototype.getState = getSrecLoadState;

function setSrecLoadState(state) {
    this.loadState = state;
}
SrecFile.prototype.setState = setSrecLoadState;

function getSrecLoadType() {
    return this.loadType;
}
SrecFile.prototype.getLoadType = getSrecLoadType;

function setSrecLoadType(loadType) {
    this.loadType = loadType;
}
SrecFile.prototype.setLoadType = setSrecLoadType;
function isLoadTypeGNSS() {
    if ((this.loadType == softwareType.ST_NavProg) ||
        (this.loadType == softwareType.ST_Bootblock1) ||
        (this.loadType == softwareType.ST_Bootblock2)) {
        return true;
    } else {
        return false;
    }
}
SrecFile.prototype.isLoadTypeGNSS = isLoadTypeGNSS;
function isLoadTypePIO() {
    if ((this.loadType == softwareType.ST_PioApp) ||
        (this.loadType == softwareType.ST_PioBoot)) {
        return true;
    } else {
        return false;
    }
}
SrecFile.prototype.isLoadTypePIO = isLoadTypePIO;

function getSrecStartAddr() {
    if ((this.loadType >= 0) && (this.loadType <= softwareType.ST_PioApp)) {
        return this.components[this.loadType].startAddr;
    } else {
        alert("getStartAddr(): Bad loadType " + this.loadType);
        return 0;
    }
}
SrecFile.prototype.getStartAddr = getSrecStartAddr;

function getSrecLastAddr() {
    if ((this.loadType >= 0) && (this.loadType <= softwareType.ST_PioApp)) {
        return this.components[this.loadType].lastAddr;
    } else {
        alert("getLastAddr(): Bad loadType " + this.loadType);
        return 0;
    }
}
SrecFile.prototype.getLastAddr = getSrecLastAddr;

function setSrecStartPos() {
    if ((this.loadType >= 0) && (this.loadType <= softwareType.ST_PioApp)) {
        this.pos = this.components[this.loadType].startPos;
    } else {
        alert("setStartPos(): Bad loadType " + this.loadType);
    }
}
SrecFile.prototype.setStartPos = setSrecStartPos;

//
//  getBytesLoaded()
//
function getBytesLoaded() {
    var swType = this.loadType;
    if ((swType >= 0) && (swType <= softwareType.ST_PioApp)) {
        return this.components[swType].bytesLoaded;
    } else {
        alert("getBytesLoaded(): Bad swType " + swType);
        return 0;
    }
}
SrecFile.prototype.getBytesLoaded = getBytesLoaded;

//
//  getTotalBytes()
//
function getTotalBytes() {
    var swType = this.loadType;
    if ((swType >= 0) && (swType <= softwareType.ST_PioApp)) {
        return this.components[swType].totalBytes;
    } else {
        alert("getTotalBytes(): Bad swType " + swType);
        return 0;
    }
}
SrecFile.prototype.getTotalBytes = getTotalBytes;

//
//  getPercentLoaded()
//
function getPercentLoaded() {
    var swType = this.loadType;
    if ((swType >= 0) && (swType <= softwareType.ST_PioApp)) {
        return this.components[swType].percentLoaded;
    } else {
        alert("getPercentLoaded(): Bad swType " + swType);
        return 0;
    }
}
SrecFile.prototype.getPercentLoaded = getPercentLoaded;

//
//  startProgress()
//
function startProgress() {
    var swType = this.loadType;
    if ((swType >= 0) && (swType <= softwareType.ST_PioApp)) {
        this.components[swType].bytesLoaded   = 0;
        this.components[swType].percentLoaded = 0;
    } else {
        alert("startProgress(): Bad swType " + swType);
    }
}
SrecFile.prototype.startProgress = startProgress;


//
//  updateProgress()
//
function updateProgress(cnt) {
    var swType = this.loadType;
    if ((swType >= 0) && (swType <= softwareType.ST_PioApp)) {
        
        if (cnt > 0) {
            this.components[swType].bytesLoaded += cnt;
            this.components[swType].percentLoaded = (this.components[swType].bytesLoaded / this.components[swType].totalBytes) * 100;
        }
    } else {
        alert("updateProgress(): Bad swType " + swType);
    }
}
SrecFile.prototype.updateProgress = updateProgress;



function getNextChunk(srecChunk) {
    var srecStr = new SrecStr();
    var status;
    var retStatus = eSrecResult.RS_OK;
    var binStatus;
    var prevPos = this.pos;

    srecChunk.reset();
    status = this.getSrecStr(srecStr);
    while (status == eSrecResult.RS_OK) {
        if (srecStr.str.charCodeAt(1) == 0x33) { // S3 record
            srec = new Srec();
            binStatus = GetSrecBinary(srecStr, srec);
            if (binStatus == eSrecResult.RS_OK) {
                if (srecChunk.concat(srec) == true) {
                    prevPos = this.pos;
                    status = this.getSrecStr(srecStr);
                    continue;
                } else {
                    // srecChunk was full, reset pos so we get this
                    // record next time. 
                    this.pos = prevPos;
                    break;
                }
            } else {
                //alert("GetSrecBinary() failed");
                this.pos = prevPos;
                retStatus = eSrecResult.RS_ERR;
                break;
            }
        }
        else if (srecStr.str.charCodeAt(1) == 0x37) { // S7 record
            this.pos = prevPos;
            //alert("getNextChunk() Read S7 Record prev: " + prevPos);
            break;
        }
        else { // Must be an S0 record - shouldn't happen
            //alert("getNextChunk() Read S0 Record");
            prevPos = this.pos;
            status = this.getSrecStr(srecStr);
        }
    } // while
    if (status == eSrecResult.RS_ERR) {
        retStatus = eSrecResult.RS_ERR;
    }
    return retStatus;
}

SrecFile.prototype.getNextChunk = getNextChunk;

function getSrecStr(srecStr) {
    var CRLF = "\r\n";
    var pos = this.pos;

    if (pos >= (this.len - 1)) {
        return eSrecResult.RS_EOF;
    }
    if (this.data[pos] != 'S') {
        return eSrecResult.RS_ERR;
    }
    idx = this.data.indexOf(CRLF, pos);
    if (idx < 0) {
        return eSrecResult.RS_ERR;
    }
    this.pos = idx + 2;
    //alert("idx: " + pos + " pos: " + srecFile.pos);
    //srecStr = String(srecFile.data.slice(pos, srecFile.pos));
    srecStr.str = this.data.slice(pos, this.pos);
    //alert("str len: " + srecStr.str.length);
    return eSrecResult.RS_OK;  
}
SrecFile.prototype.getSrecStr = getSrecStr;

function SrecStr() {
    this.str = null;
}


function parseS0(srec, startPos, startAddr) {
    var rec0_len = 19;
    var crcPtr = 0;
    var date, time, version;
    var major, minor, build;
    var swType;
    var swSpinNumber;
    var swInfoVersion;
    var product = null;
    
    
    //alert("parse srec size: " + srec.len);

    // Get the address of the CRC32 of the SW image. 
    for (var i = 0; i < 4; i++) {
        crcPtr = (crcPtr << 8) | srec.data[i + rec0_len];
    }
    
    major  = srec.data[4 + rec0_len];
    minor  = srec.data[5 + rec0_len];
    build  = srec.data[6 + rec0_len];
    swType = srec.data[7 + rec0_len];
    if (swType == 10) {
        swType = 4; // ST_PioBoot
    } else if (swType == 11) {
        swType = 5; // ST_PioApp
    }

    
    date            = String.fromCharCode.apply(null, srec.data.subarray((8+rec0_len), (20+rec0_len)));
    time            = String.fromCharCode.apply(null, srec.data.subarray((20 + rec0_len), (29 + rec0_len)));
    product         = String.fromCharCode.apply(null, srec.data.subarray((36 + rec0_len), (64 + rec0_len)));
    swSpinNumber    = srec.data[33 + rec0_len];
    swInfoVersion   = srec.data[35 + rec0_len];

    if (((swType == 4) || (swType == 5)) && (product.indexOf("3040") != -1)) {
        //alert("Octagon: " + swType + " " + product);
        return;
    }
    this.components[swType].info = new SoftwareInfo();
    this.components[swType].info.crcPtr = crcPtr;
    this.components[swType].info.version = major + "." + minor + "." + build;
    this.components[swType].info.swType = swType;
    this.components[swType].info.date = date + " " + time;
    this.components[swType].info.swSpinNumber = swSpinNumber;
    this.components[swType].info.swInfoVersion = swInfoVersion;
    this.components[swType].info.product = product;
    this.components[swType].startPos = startPos;
    this.components[swType].startAddr = startAddr;
    this.components[swType].lastAddr = crcPtr + 3;
    this.components[swType].totalBytes = this.components[swType].lastAddr - startAddr + 1;
    //alert("Start Pos: " + this.components[swType].startPos);
    //alert("Start Addr: " + toHexString(this.components[swType].startAddr, 8));
    //alert("Last Addr: " + toHexString(this.components[swType].lastAddr, 8));
    //alert("CRC Addr: " + toHexString(this.components[swType].info.crcPtr, 8));
    //alert("Version: " + this.components[swType].info.version);
    //alert("Type: " + this.components[swType].info.swType);
    //alert("Date/Time: " + this.components[swType].info.date);
    //alert("Product: " + this.components[swType].info.product);
}
SrecFile.prototype.parseS0 = parseS0;


function Srec(size) {
    this.type     = -1;
    this.len      = 0;
    this.address  = 0;
    this.data     = null;
    size = typeof size !== 'undefined' ? size : 0;
    //alert("Srec size: " + size);
    if (size > 0) {
        this.data = new Uint8Array(size);
        //alert("Srec allocated array");
    }
}

function srecReset() {
    this.type    = -1;
    this.len     = 0;
    this.address = 0;
}
Srec.prototype.reset = srecReset;


function srecConcat(srec) {
    if (srec.len <= (this.data.length - this.len)) {
        var j = 0;
        for (var i = this.len; i < (this.len + srec.len); i++) {
            this.data[i] = srec.data[j++];
        }
        this.len += srec.len;
        if (this.address == 0){
            this.address = srec.address;
        }
        return true;
    } else {
        return false;
    }
}
Srec.prototype.concat = srecConcat;


function GetSrecAddrLen(type) {
    var addrLen = 0;
    switch (type) {
        case 0: // S0
        case 1: // S1
        case 9: // S9
            addrLen = 2;
            break;
        case 2: // S2
        case 8: // S8
            addrLen = 3;
            break;
        case 3: // S3
        case 7: // S7
            addrLen = 4;
            break;
        default: // Shouldn't happen
            addrLen = 0;
    }
    return addrLen;
}

function toHexString(n, d) {
    if (n < 0) {
        n = 0xFFFFFFFF + n + 1;
    }
    //return "0x" + ("00000000" + n.toString(16).toUpperCase()).substr(-d);
    return ("00000000" + n.toString(16).toUpperCase()).substr(-d);
}

function Xdigit(achar) {
    var a = achar.charCodeAt();
    var n = 16;

    if (a >= 48 && a <= 57) {
        n = a - 48;
        //alert("a " + a + " " + n);
    } else if (a >= 65 && a <= 70) {
        n = (a - 65) + 10;
        //alert("a " + a + " " + n);
    }
    if (isNaN(n) || n > 15) {
        alert("achar " + achar + " a " + a + " n " + n);
    }
    return n;
}

// converts the s19 record in srecStr to its binary format 
// in srec.  
function GetSrecBinary(srecStr, srec) {
    var CRLF = "\r\n";
   
    var idx = srecStr.str.indexOf(CRLF, 0);
    srec.type = Xdigit(srecStr.str[1]);
    var srecLen = ((Xdigit(srecStr.str[2]) << 4) + Xdigit(srecStr.str[3]));
    var srecAddrLen = GetSrecAddrLen(srec.type); // 2, 3, or 4
    srec.len = srecLen - srecAddrLen - 1;
    srec.data = new Uint8Array(srec.len);

    //alert("srec.type: " + srec.type);
    //alert("srecLen: " + srecLen);
    //alert("srecAddrLen: " + srecAddrLen);
    //alert("srec.len: " + srec.len);

    var chk = 0;
    var j = 0;
    srec.address = 0;
    // Calculate the checksum starting with len bytes up to and including 
    // the checksum bytes.  Save the data bytes in srec.data excluding
    // the length, address bytes and the checksum. 
    for (var i = 2; i < idx; i += 2) {
        var x = ((Xdigit(srecStr.str[i]) << 4) + Xdigit(srecStr.str[i + 1]));
        chk += x;
        if (i == 2)
            srecLen = x;
        else if ((i == 4) || (i == 6) || ((i == 8) && (srecAddrLen > 2)) || ((i == 10) && (srecAddrLen > 3)))
            srec.address = ((srec.address << 8) | x);
        else {
            if (j < srec.len) {
                srec.data[j] = x;
                j++;
            }
        }
    }
    if ((chk & 0xFF) != 0xFF) {
        srec.type = -1;
        //alert("Bad Checksum: " + chk);
        return eSrecResult.RS_ERR;
    }

    if (idx != ((srecLen * 2) + 4)) {
        srec.type = -1;
        alert("Bad recLen: " + srecLen + " in " + idx);
        return eSrecResult.RS_ERR;
    }

    //alert("srec.address: " + toHexString(srec.address, 8));
    return eSrecResult.RS_OK;
}

var parseState = {
    WAITING_FIRST_S0: 0,
    WAITING_LAST_S0:  1,
    WAITING_FIRST_S3: 2,
    WAITING_LAST_S3:  3
};

function parseFile() {
    var srecStr = new SrecStr();
    var status = eSrecResult.RS_OK;
    var state = parseState.WAITING_FIRST_S0;
    var s0recs;
    var s0cnt = 0;
    var binaryStatus;
    var srec;
    var startPos, startAddr;

    this.pos = 0; // start at the beginning of the file
    status = this.getSrecStr(srecStr);
    while (status == eSrecResult.RS_OK) {
        switch (state) {
            case parseState.WAITING_FIRST_S0:
                if (srecStr.str.charCodeAt(1) == 0x30) { // S0 record
                    state = parseState.WAITING_LAST_S0;
                    s0cnt = 1;
                    s0recs = new Srec(128);
                    srec = new Srec();
                    binaryStatus = GetSrecBinary(srecStr, srec);
                    if (binaryStatus != eSrecResult.RS_OK) {
                        alert("parseFile(): GetSrecBinary() Failed: " + binaryStatus);
                        return eSrecResult.RS_ERR;
                    } else {
                        s0recs.concat(srec);
                    }
                } else {
                    alert("parseFile(): Expected First S0 Record");
                    return eSrecResult.RS_ERR;
                }
                break;
            case parseState.WAITING_LAST_S0:
                if (srecStr.str.charCodeAt(1) == 0x30) { // S0 record
                    s0cnt++;
                    srec = new Srec();
                    binaryStatus = GetSrecBinary(srecStr, srec);
                    if (binaryStatus != eSrecResult.RS_OK) {
                        alert("parseFile(): GetSrecBinary() failed: " + binaryStatus);
                        return eSrecResult.RS_ERR;
                    } else {
                        s0recs.concat(srec);
                    }
                    if (s0cnt == 4) { // should be 4 S0 records for each component
                        state = parseState.WAITING_FIRST_S3;
                        startPos = this.pos;
                        //alert("parseFile(): Last S0 Record startPos " + startPos);
                    }
                } else {
                    alert("parseFile(): Expected S0 Record " + srecStr.str.charCodeAt(1) + " State: " + state);
                    return eSrecResult.RS_ERR;
                }
                break;
            case parseState.WAITING_FIRST_S3:
                if (srecStr.str.charCodeAt(1) == 0x33) { // S3 record
                    state = parseState.WAITING_LAST_S3;
                    srec = new Srec();
                    GetSrecBinary(srecStr, srec);
                    startAddr = srec.address;
                    this.parseS0(s0recs, startPos, startAddr);
                } else {
                    alert("parseFile(): Expected S3 Record");
                    return eSrecResult.RS_ERR;
                }
                break;
            case parseState.WAITING_LAST_S3:
                if (srecStr.str.charCodeAt(1) == 0x37) { // S7 record
                    state = parseState.WAITING_FIRST_S0;
                }
                break;
            default:
                alert("parseFile(): Invalid Parse State: " + state);
                return eSrecResult.RS_ERR;
                break;
        } // switch
        status = this.getSrecStr(srecStr);
    } // while
    return eSrecResult.RS_OK;
}

SrecFile.prototype.parseFile = parseFile;



