/*
 * Copyright (c) 2013-14, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "blfwk/stdafx.h"
#include "blfwk/StSRecordFile.h"
#ifdef LINUX
#include <string.h>
#endif

StSRecordFile::StSRecordFile(std::istream &inStream)
    : m_stream(inStream)
{
}

//! Frees any data allocated as part of an S-record.
StSRecordFile::~StSRecordFile()
{
    const_iterator it;
    for (it = m_records.begin(); it != m_records.end(); it++)
    {
        SRecord &theRecord = (SRecord &)*it;
        if (theRecord.m_data)
        {
            delete[] theRecord.m_data;
            theRecord.m_data = NULL;
        }
    }
}

//! Just looks for "S[0-9]" as the first two characters of the file.
bool StSRecordFile::isSRecordFile()
{
    int savePosition = (int)m_stream.tellg();
    m_stream.seekg(0, std::ios_base::beg);

    char buffer[2];
    m_stream.read(buffer, 2);
    bool isSRecord = (buffer[0] == 'S' && isdigit(buffer[1]));

    m_stream.seekg(savePosition, std::ios_base::beg);

    return isSRecord;
}

//! Extract records one line at a time and hand them to the parseLine()
//! method. Either CR, LF, or CRLF line endings are supported. The input
//! stream is read until EOF.
//! The parse() method must be called after the object has been constructed
//! before any of the records will become accessible.
//! \exception StSRecordParseException will be thrown if any error occurs while
//!     parsing the input.
void StSRecordFile::parse()
{
    // back to start of stream
    m_stream.seekg(0, std::ios_base::beg);

    std::string thisLine;

    do
    {
        char thisChar;
        m_stream.get(thisChar);

        if (thisChar == '\r' || thisChar == '\n')
        {
            // skip the LF in a CRLF
            if (thisChar == '\r' && m_stream.peek() == '\n')
                m_stream.ignore();

            // parse line if it's not empty
            if (!thisLine.empty())
            {
                parseLine(thisLine);

                // reset line
                thisLine.clear();
            }
        }
        else
        {
            thisLine += thisChar;
        }
    } while (!m_stream.eof());
}

bool StSRecordFile::isHexDigit(char c)
{
    return (isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}

int StSRecordFile::hexDigitToInt(char digit)
{
    if (isdigit(digit))
        return digit - '0';
    else if (digit >= 'a' && digit <= 'f')
        return 10 + digit - 'a';
    else if (digit >= 'A' && digit <= 'F')
        return 10 + digit - 'A';

    // unknow char
    return 0;
}

//! \exception StSRecordParseException is thrown if either of the nibble characters
//!     is not a valid hex digit.
int StSRecordFile::readHexByte(std::string &inString, int inIndex)
{
    char nibbleCharHi = inString[inIndex];
    char nibbleCharLo = inString[inIndex + 1];

    // must be hex digits
    if (!(isHexDigit(nibbleCharHi) && isHexDigit(nibbleCharLo)))
    {
        throw StSRecordParseException("invalid hex digit");
    }

    return (hexDigitToInt(nibbleCharHi) << 4) | hexDigitToInt(nibbleCharLo);
}

//! \brief Parses individual S-records.
//!
//! Takes a single S-record line as input and appends a new SRecord struct
//! to the m_records vector.
//! \exception StSRecordParseException will be thrown if any error occurs while
//!     parsing \a inLine.
void StSRecordFile::parseLine(std::string &inLine)
{
    int checksum = 0;
    SRecord newRecord;
    memset(&newRecord, 0, sizeof(newRecord));

    // must be at least a certain length
    if (inLine.length() < SRECORD_MIN_LENGTH)
    {
        throw StSRecordParseException("invalid record length");
    }

    // start char must be 'S'
    if (inLine[0] != SRECORD_START_CHAR)
    {
        throw StSRecordParseException("invalid record start char");
    }

    // parse type field
    if (!isdigit(inLine[1]))
    {
        throw StSRecordParseException("invalid S-record type");
    }
    newRecord.m_type = inLine[1] - '0';

    // parse count field
    newRecord.m_count = readHexByte(inLine, 2);
    checksum += newRecord.m_count;

    // verify the record length now that we know the count
    if (inLine.length() != 4 + newRecord.m_count * 2)
    {
        throw StSRecordParseException("invalid record length");
    }

    // get address length
    int addressLength = 0; // len in bytes
    bool hasData = false;
    switch (newRecord.m_type)
    {
        case 0: // contains header information
            addressLength = 2;
            hasData = true;
            break;
        case 1: // data record with 2-byte address
            addressLength = 2;
            hasData = true;
            break;
        case 2: // data record with 3-byte address
            addressLength = 3;
            hasData = true;
            break;
        case 3: // data record with 4-byte address
            addressLength = 4;
            hasData = true;
            break;
        case 5: // the 2-byte address field contains a count of all prior S1, S2, and S3 records
            addressLength = 2;
            break;
        case 7: // entry point record with 4-byte address
            addressLength = 4;
            break;
        case 8: // entry point record with 3-byte address
            addressLength = 3;
            break;
        case 9: // entry point record with 2-byte address
            addressLength = 2;
            break;
        default:
            // unrecognized type
            throw StSRecordParseException("unknown S-record type");
            break;
    }

    // read address
    int address = 0;
    int i;
    for (i = 0; i < addressLength; ++i)
    {
        int addressByte = readHexByte(inLine, SRECORD_ADDRESS_START_CHAR_INDEX + i * 2);
        address = (address << 8) | addressByte;
        checksum += addressByte;
    }
    newRecord.m_address = address;

    // read data
    if (hasData)
    {
        int dataStartCharIndex = 4 + addressLength * 2;
        int dataLength = newRecord.m_count - addressLength - 1; // total rem - addr - cksum (in bytes)
        uint8_t *data = new uint8_t[dataLength];

        for (i = 0; i < dataLength; ++i)
        {
            int dataByte = readHexByte(inLine, dataStartCharIndex + i * 2);
            data[i] = dataByte;
            checksum += dataByte;
        }

        newRecord.m_data = data;
        newRecord.m_dataCount = dataLength;
    }

    // read and compare checksum byte
    checksum = (~checksum) & 0xff; // low byte of one's complement of sum of other bytes
    newRecord.m_checksum = readHexByte(inLine, (int)inLine.length() - 2);
    if (checksum != newRecord.m_checksum)
    {
        throw StSRecordParseException("invalid checksum");
    }

    // now save the new S-record
    m_records.push_back(newRecord);
}
