#include "HexFile.h"
#include <QTextStream>
#include <QMessageBox>
#include "CRC32.h"

#define USE_BIG_RECORDS
#define MAX_RECORDS_IN_BIG_RECORDS 512	//16 bytes by record, 512 bytes by row --> 16 rows / big record



CHexFile::CHexFile(void)
{
	mFileOpened = false;
	mFileParsed = false;
	mRecordsListValid = false;
	mHighAddress = 0;
	mDiscardedRecords = 0;
    mTotalParsedRecords = 0;
    mFirmwareCRC = 0;

  }

CHexFile::~CHexFile(void)
{
    for(int i = 0; i < mRecordsList.size(); i++)
        delete mRecordsList.at(i);

}

int CHexFile::CloseOpenedHexFile()
{
    for(int i = 0; i < mRecordsList.size(); i++)
        delete mRecordsList.at(i);

    mRecordsList.clear();

    mFileOpened = false;
    mFileParsed = false;
    mHexfileHandle = 0;
    mRecordsListValid = false;
    mHighAddress = 0;
    mDiscardedRecords = 0;
    mTotalParsedRecords = 0;

    return 1;
}

int CHexFile::OpenDataFile(QString FilePath, bool CloseIfAlreadyParsed)
{

    //Check if file exists
    if(QFile::exists(FilePath) == false)
        return 0;

    if(mFileParsed == true)
    {
        if(CloseIfAlreadyParsed == false)
        {
            return 0;
        }
        else
        {
            CloseOpenedHexFile();
        }
    }

    mHexfileHandle = new QFile(FilePath);

    if(mHexfileHandle->open(QIODevice::ReadOnly|QIODevice::Text|QIODevice::Unbuffered) == false)
    {
        delete mHexfileHandle;
        mHexfileHandle = 0;
        qDebug("Cannot open specified HEX file...");
        return 0;
    }

    mHexFileSize = mHexfileHandle->size();
	
	mFileOpened = true;

	mRecordsTableSize = 0;
	mFirmwareDataSize = 0;

    return ParseData();

    mHexfileHandle->close();

}


int CHexFile::ParseData(void)
{
	if(!mFileOpened)
		return 0;

	int Ret;
	bool Finished = false;
	unsigned int CurAddress;
	
//	CArray<CHexRecord*,CHexRecord*> TempArray;
    QList<CHexRecord*> TempList;
    QString RecordString;
    QTextStream RecordFileStream(mHexfileHandle);
//	CArchive ar(&mHexfileHandle,CArchive::load);

	while(!Finished)
	{
		CHexRecord *NewRecord = new CHexRecord;
        RecordString.clear();
//		ar.ReadString(mRecordString);
        RecordString = RecordFileStream.readLine();
	//	Ret = NewRecord.DecodeRawRecord(&mRecordString,mHighAddress);
        Ret = NewRecord->DecodeRawRecord(&RecordString,mHighAddress);
        mTotalParsedRecords++;
		 
		switch(Ret)
		{
			case CHexRecord::RET_DATA_RECORD:
			{
#ifdef USE_BIG_RECORDS
                TempList.append(NewRecord);
//				mRecordsTable[mRecordsTableSize] = NewRecord;
	//			mRecordsTableSize++;
#else
                mRecordsList.append(NewRecord);
				mRecordsTable[mRecordsTableSize] = NewRecord;
				mRecordsTableSize++;
#endif
				break;
			}
			case CHexRecord::RET_EOF_RECORD:
			{	
                mHexfileHandle->close();
				mFileOpened = false;
                delete mHexfileHandle;
				mRecordsListValid = true;
				Finished = true;
				delete NewRecord;
				
				break;
			}				
		
			case CHexRecord::RET_EXTENDED_LINEAR_ADDRESS:
			{
//				mHighAddress = NewRecord.GetExtenedAddress();
				mHighAddress = NewRecord->GetExtenedAddress();
				CurAddress = mHighAddress;
				delete NewRecord;
				break;
			}
			case CHexRecord::RET_IGNORED_ALL_FF_RECORD:
			{
				delete NewRecord;
				mDiscardedRecords++;
				break;
			}
			case CHexRecord::RET_EXTENDED_ADDRESS:	
			case CHexRecord::RET_START_SEGMENT_ADDRESS:
			case CHexRecord::RET_START_LINEAR_ADDRESS:		
			case CHexRecord::RET_UNKNOWN_RECORD_TYPE:
			case CHexRecord::RET_INVALID_RECORD: 
			case CHexRecord::RET_BAD_RECORD_CHECKSUM:
			case CHexRecord::RET_UNMANAGED_RECORD_TYPE:
			{
                mHexfileHandle->close();
				mFileOpened = false;
                delete mHexfileHandle;
				Finished = true;
                //MessageBox(NULL,"Parsing Error", "Cannot Parse Hex File",MB_OK);
                QMessageBox::warning(0,"Parsing Error","Cannot parse HEX file",QMessageBox::Ok);
				delete NewRecord;
				return 0;
				break;
			}		

		}
	}

#ifdef USE_BIG_RECORDS
	int i;
	int RecordCount;
	CHexRecord *TempRecordPtr;
	CHexRecord *BigRecord = new CHexRecord;
    CCRC32 Crc32Engine;

	
    TempRecordPtr = TempList.at(0);
	CurAddress = TempRecordPtr->GetStartAddress();
	*BigRecord = *TempRecordPtr;
    RecordCount = 1;

    for(i = 1; i < TempList.size(); i++)
	{
//		if(CurAddress == 0x1d0097F0)
//		{
//			CurAddress = CurAddress;
//		}
        CurAddress += (TempRecordPtr->GetRecordSizeInBytes());
        TempRecordPtr = TempList.at(i);
        if(CurAddress == TempRecordPtr->GetStartAddress() && RecordCount<MAX_RECORDS_IN_BIG_RECORDS  && (i != (TempList.size()-1)))
		{
			BigRecord->AppendRecord(TempRecordPtr);
            RecordCount++;
		}
		else
		{
            RecordCount = 1;        //Init to 1 because we load the record right below...
            mRecordsList.append(BigRecord);
			BigRecord = new CHexRecord;
			
            if(i == TempList.size()-1)  //Manage the last record.
			{
				*BigRecord = *TempRecordPtr;
                mRecordsList.append(BigRecord);
                BigRecord->mRecordSize += TempRecordPtr->mRecordSize;

			}
			else
			{
				*BigRecord = *TempRecordPtr;
				CurAddress = TempRecordPtr->GetStartAddress();
			}
		}
	}
#endif
    for(i = 0; i < TempList.size(); i++)
    {
        delete TempList[i];
    }

//    for(i = 0; i < mRecordsList.size(); i++)
//    {
//        mFirmwareDataSize += mRecordsList.at(i)->GetRecordSizeInBytes();
//    }

    FilterRecords(0x1D004000,0x1D07FFFF);

    QByteArray RawShit = GetRawData(false);

    mFirmwareDataSize = RawShit.size();

    unsigned int testcrc = CRC_START_32;
    for(unsigned int i = 0; i < mFirmwareDataSize; i++)
    {
        testcrc = Crc32Engine.UpdateCRC32(testcrc,RawShit[i]);
    }
    testcrc ^= 0xffffffffL;

    mFirmwareCRC = Crc32Engine.ComputeCRC32((const unsigned char *)RawShit.data(),RawShit.size());

	mFileParsed = true;
	return 1;
}

int CHexFile::FilterRecords(unsigned int StartAddress, unsigned int EndAddress)
{
    if(mRecordsList.isEmpty() == true)
    {
        return 0;
    }

    bool done = false;
    int i = 0;
    while(done == false)
    {
        if(mRecordsList.at(i)->GetStartAddress() < StartAddress ||
           mRecordsList.at(i)->GetStartAddress() > EndAddress)
        {
            //Record is outside filter area, delete it...
            delete mRecordsList.at(i);
            mRecordsList.removeAt(i);
        }
        else
        {
            i++;
        }

        if(i == mRecordsList.size())
        {
            done = true;
            break;
        }
    }

    return 1;
}

unsigned int CHexFile::GetNBRecords(void)
{
//	return mRecordsTableSize;
	
    return mRecordsList.size();

}

unsigned long CHexFile::GetFileSize(void)
{
	return mHexFileSize;
}

unsigned int CHexFile::GetDataCRC32()
{
    return mFirmwareCRC;
}

CHexRecord * CHexFile::GetRecord(int RecordIndex, int &Status)
{
	
    if(RecordIndex > mRecordsList.size())
//	if(RecordIndex > mRecordsTableSize)
	{
		Status = ERR_INDEX_OUT_OF_BOUND;
		return 0;
	}
	//CHexRecord *mRecordPtr = (CHexRecord*)&mRecordsList.GetAt(RecordIndex);
    CHexRecord *mRecordPtr = mRecordsList.at(RecordIndex);
//	CHexRecord *mRecordPtr = mRecordsTable[RecordIndex];
	return mRecordPtr;
}

QByteArray CHexFile::GetRawData(bool IncludeHeader)
{
    QByteArray Data;
    Data.clear();


    if(IncludeHeader)
    {
        QDataStream Strm(&Data,QIODevice::WriteOnly);

        //Header
                Strm << HEX_FILE_HEADER_CODE;
        //Flags
                Strm << (int)0x0000;    //TODO: Manage that
        //Nb Records
                Strm << GetNBRecords();
        //Firmware size
                Strm << GetFirmwareSize();
        //Version
                Strm << (int)0x00;      //TODO: Manage that
        //CRC32
                Strm << GetDataCRC32();

    }



    for(int i = 0; i < mRecordsList.size(); i++)
    {
        Data.append(mRecordsList.at(i)->GetRecord());
    }

    return Data;
}


QString CHexFile::GetHexFileInfoString()
{

    QString string;
    string.clear();

    if(HexDataValid() == false)
        return QString("No file loaded\n");

    for(int i = 0; i < mRecordsList.size(); i++)
    {
        CHexRecord *rec = mRecordsList.at(i);
        unsigned int StartAddress = rec->GetStartAddress();
        unsigned int Size = rec->GetRecordSizeInBytes();
        unsigned int EndAddress = StartAddress + Size;
        string.append(QString("Record %1: Start = [0x%2], Length = [%3], End = 0x%4\n").arg(i).arg(StartAddress,0,16).arg(Size).arg(EndAddress,0,16));
    }
    string.prepend("-------------------------------------------------------------\n");
    string.append("-------------------------------------------------------------\n");

    return string;
}


