/*<<<NL>>>*/
#include "stdafx.h"
#include "mpfs2lib.h"

/*********************************************************************
 *
 *    MPFS Generator Library
 *
 *********************************************************************
 * FileName:        MPFSlib.cs
 * Dependencies:    Microsoft .NET Framework 2.0
 * Processor:       x86
 * Complier:        Microsoft Visual C# 2008 Express Edition
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * This software is owned by Microchip Technology Inc. ("Microchip") 
 * and is supplied to you for use exclusively as described in the 
 * associated software agreement.  This software is protected by 
 * software and other intellectual property laws.  Any use in 
 * violation of the software license may subject the user to criminal 
 * sanctions as well as civil liability.  Copyright 2008 Microchip
 * Technology Inc.  All rights reserved.
 *
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT 
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL 
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR 
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF 
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS 
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE 
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER 
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT 
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 *
 * Author               Date   		Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Elliott Wood		4/17/2008	    Original
 * No microchip stuff:
 * Marc Mirti Mancinelli 28/12/2013 c++ version and ...
 ********************************************************************/
//Supported file types in http2.c
// File type extensions corresponding to HTTP_FILE_TYPE
static char *s_httpFileExtensions[] =
{
    "txt",          // HTTP_TXT
    "htm",          // HTTP_HTM
    "html",         // HTTP_HTML
    "cgi",          // HTTP_CGI
    "xml",          // HTTP_XML
    "css",          // HTTP_CSS
    "gif",          // HTTP_GIF
    "png",          // HTTP_PNG
    "jpg",          // HTTP_JPG
    "cla",          // HTTP_JAVA
    "wav",          // HTTP_WAV
    "js",           // HTTP_JS (mmm:added)
    "ico",          // HTTP_ICO (mmm:added)
	NULL		// HTTP_UNKNOWN
};

void MPFS2Builder::contentTypeCheck(MPFSFileRecord *pFile,bool bUtf8)
{
	xbString ext(::xbGetFileExt(pFile->FileName,false));
	if (ext.CompareNC("inc") == 0)
		return;

	char **pExtSupported = s_httpFileExtensions;
	while(*pExtSupported)
	{
		if(ext.CompareNC(*pExtSupported) == 0)
			return;
		pExtSupported++;
	}
	warningLog("File "+pFile->FileName+" has an unsupported extension",FileLoc(pFile->origFileName));
}

class EncodeBuffer:public xbObject, public IxbOutputStream
{
public:
	EncodeBuffer(xbData *pDataOut)
	{
		m_pDataOut = pDataOut;
		Create();
	}
	virtual ~EncodeBuffer()
	{
		eProtectTrace(Destroy());
	}

	xbImplObject();

	virtual bool GetInterface(REFIID riid, void ** ppInterf)
	{
		xbImplInterface(IxbOutputStream);
		return xbObject::GetInterface(riid,ppInterf);
	}
	virtual bool			CanWrite() const
	{
		return true;
	}
	virtual ulong			Write(const void *pData,ulong DataLg,bool DataLg_Required = true)
	{
		m_pDataOut->Add(pData,DataLg);
		return DataLg;
	}
protected:
	xbData	*m_pDataOut;
};

void MPFS2Builder::GetCompressPlug(xbRefPtr<IxbCompressPlug> *ppPlug)
{
	xbRefPtr<IxbCompressPlug> prPlug(m_prCompressPlug);
	LPCSTR pType = "GZIP";

	if (prPlug.IsValid() && prPlug->CanWorkWithType(pType,NULL))
	{
		*ppPlug = prPlug;
		return;
	}
	bool bPluginInUpdate;
	int cTry = 10;
	while((--cTry) >= 0)
	{
		bPluginInUpdate = g_xbCompressPlugType.IsInUpdate();
		xbRefPtr<IxbObject> prPlugObj;
		if (g_xbCompressPlugType.CreatePluggedObject(pType,&prPlugObj))
		{
			prPlug = xbRefQIPtr<IxbCompressPlug>(prPlugObj);
			break;
		}
		::xbSleep(1000);
	}
	if (cTry < 0)
	{
		if (!bPluginInUpdate && !g_xbCompressPlugType.IsInUpdate())
			eMpfs2::Throw("Can't read/write object : compression "+xbString(pType)+" not available");
		xbEhotPlug::Throw("Can't read/write object : compression "+xbString(pType)+" not ready to use or not available");
	}

	*ppPlug = prPlug;
	m_prCompressPlug = prPlug;
}

bool MPFS2Builder::gzipData(xbData *pdata,int *pgzipRatio)
{
//		return false;
	if (pdata->IsEmpty())
		return false;

	xbData						dtaOut;
	{
		EncodeBuffer				encodeBuf(&dtaOut);
		xbRefPtr<IxbCompressPush>	prEncode;
		xbRefPtr<IxbCompressPlug>	prPlug;
		GetCompressPlug(&prPlug);

//		encodeBuf.Create();
		prPlug->Compress(&encodeBuf,0,&prEncode);
		prEncode->SetCompressLevel(xbCompressHigh);

		prEncode->Write(pdata->Begin(),pdata->Length);
		prEncode->CompressFlush();
		prEncode->Close();
	}
	
	if (dtaOut.Length > pdata->Length)
		return false;

    *pgzipRatio = (int)(100 - (100 * dtaOut.Length / pdata->Length));
	pdata->Attach(&dtaOut);
	return true;
}

void MPFS2Builder::AddFile(const xbString &localName, const xbString &imageName)
{
	if (FileMatchesExt(imageName, excludeTypes))
		return;
	
	if (hexFormat == MPFShexFormat_disabled
		&& FileMatchesExt(imageName, hexTypes))
		return;

    // Skip if can't be opened
    if (!xbFileExists(localName))
        eMpfs2::Throw("Could not read " + localName);

    // Set up the new file record
    MPFSFileRecord *pnewFile = xbDnew MPFSFileRecord();
	MPFSFileRecord *idxFile = NULL;
	try
	{
		pnewFile->FileName = imageName;
		pnewFile->origFileName = localName;

		// Read the data in
		xbFileStream fs(localName, xbOpen, xbAccessR, xbWorkSharedR);
		fs.GetFileTime(NULL,NULL,&pnewFile->fileDate);
		bool bDyna = FileMatchesExt(imageName, dynamicTypes);
		bool bUtf8 = FileMatchesExt(imageName, utf8types);

		if (bDyna || bUtf8)
		{
			fs.ReadTextBOM();
			//fs.CRLFasLF = false;
			if (bUtf8)
				fs.SetAppTextCP(CP_UTF8);
			else
				fs.SetAppTextCP(CP_ACP);

			pnewFile->data.Empty();
			xbString ln;
			while(fs.ReadString(&ln,NULL)>0)
			{
				pnewFile->data.Add(ln.Begin(),ln.Length);
				pnewFile->data.Add("\r\n",2);
			}
		}
		else
		{
			ulong cFileSz = fs.GetLength();
			fs.Read(pnewFile->data.GetWritePtr((int)cFileSz),cFileSz);
		}

		// Parse the file if necessary
		if (bDyna)
		{
			idxFile = dynVarParser.MPFSparse(*pnewFile);
		}

		// GZip the file if possible
		int gzipRatio = 0;
		if (idxFile == NULL && FileMatchesExt(imageName, hexTypes))
		{
			hexCompress(pnewFile);
		}
		else
		{
			if (idxFile == NULL && !FileMatchesExt(imageName, nonGZipTypes))
			{
				if (gzipData(&pnewFile->data,&gzipRatio))
				{
					pnewFile->isZipped = true;
				}
			}
		}

		contentTypeCheck(pnewFile,bUtf8);

		// Add the file and return
		if (idxFile == NULL)
		{
			infoLog("    " + pnewFile->FileName + ": " + xbString(pnewFile->data.Length) + " bytes" + 
				(pnewFile->isZipped ? " (gzipped by " + xbString(gzipRatio) + "%)" : "")
				+(bUtf8?" (UTF8)":""));
			files.Add(pnewFile);
			pnewFile = NULL;
		}
		else
		{
			infoLog("    " + pnewFile->FileName + ": " + xbString(pnewFile->data.Length) + " bytes, " + xbString(idxFile->data.Length / 8) + " vars"
				+(bUtf8?" (UTF8)":""));
			pnewFile->hasIndex = true;
			files.Add(pnewFile);
			pnewFile = NULL;
			files.Add(idxFile);
			idxFile = NULL;
		}
	}
	catch(...)
	{
		eProtectBegin();
		if (pnewFile!=NULL)
			delete pnewFile;
		if (idxFile!=NULL)
			delete idxFile;
		eProtectEndTrace();
		throw;
	}
}

void MPFS2Builder::AddDirectory(const xbString &dataPath, const xbString &imagePath)
{
	if (FileMatchesExt(imagePath, excludeTypes))
		return;

	// Make sure directory exists
    if (!xbDirExists(dataPath))
        eMpfs2::Throw("Directory " + dataPath + " does not exist.");

    // Make sure directory is not the project directory
	if (projectDir.StartWithNC(dataPath))
        eMpfs2::Throw("The project directory is located in the source "
                "directory.\nThe generator cannot run if the image is to be placed "
                "in the source directory.\nPlease select the base MPLAB project "
                "directory before continuing.");
    
	bool bFirstFile = true;
    // Load directory members
	xbDirEnum denum(dataPath);
	xbString fname;

	// Add all sub files
	while(denum.Next(&fname))
	{
		if (denum.GetAttr().IsHidden())
			continue;

		if (bFirstFile)
		{
			infoLog(dataPath + " :");
			bFirstFile = false;
		}
		if (denum.GetAttr().IsDir())
			continue;

		AddFile(dataPath+"/"+fname, imagePath + fname);
	}

	// Add all subdirectories
	denum.Reset();
	while(denum.Next(&fname))
	{
		if (denum.GetAttr().IsHidden())
			continue;

		if (bFirstFile)
		{
			infoLog(dataPath + " :");
			bFirstFile = false;
		}
		if (!denum.GetAttr().IsDir())
			continue;
        AddDirectory(dataPath+"/"+fname, imagePath + fname + "/");
	}
}


xbRefPtr<MPFS2Builder::MPFS2Writer> MPFS2Builder::MPFS2WriterCreate(MPFSOutputFormat format)
{
    xbRefPtr<MPFS2Writer> w;
    switch (format)
    {
        case MPFSOutputFormat_C18:
        case MPFSOutputFormat_C32:
			MPFS2C18Writer::CreateObject(&w,projectDir + outputFile);
            break;
        case MPFSOutputFormat_ASM30:
			MPFS2ASM30Writer::CreateObject(&w,projectDir + outputFile);
			break;
/*		case MPFSOutputFormat_MDD:
            MDDWriter(localPath);
            return;	*/

		case MPFSOutputFormat_BIN:
			MPFS2BINWriter::CreateObject(&w,projectDir + outputFile);
            break;
	    default:
			eMpfs2::Throw("MPFS2 generation format not supported !");
    }
	return w;
}

void MPFS2Builder::Generate(MPFSOutputFormat format)
{
    // Start with nothing as the output name
    generatedImageName.Empty();

    // Make sure all paths exist
    if (!xbDirExists(projectDir))
		eMpfs2::Throw("Project directory \"" + projectDir + "\" does not exist!");

    // Make sure we have some files
    if (files.Length == 0)
        eMpfs2::Throw("No file to include to MPFS2!");

	if (files.Length > 0x8000) //To be sure there is no problem...
        eMpfs2::Throw("Too many files to include to MPFS2!");

    // Write any index files that have changed
    indexUpdated = dynVarParser.WriteIndices(GetTokenVersion());


    // Set up the writer
    xbRefPtr<MPFS2Writer> w(MPFS2WriterCreate(format));

	WriteImage(*w);	
}

void MPFS2Builder::ListUsedTokens(bool bFull)
{
	dynVarParser.ListUsedTokens(bFull);
}

uint32 dateTimeToPicTime(const xbDateTime &dt)
{
	SYSTEMTIME st;
	dt.ToSystemTime(&st);
	return TIME_COUNT(st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond);
}

void MPFS2Builder::WriteImage(MPFS2Writer &w)
{
    // Determine address of each file and string
    uint32 numFiles = (uint32)files.Length;
    uint32 lenHeader = 8;
    uint32 lenHashes = 2 * numFiles;
    uint32 lenFAT = 22 * numFiles;
    uint32 baseAddr = lenHeader + lenHashes + lenFAT;
	uint32 counter=0;
	uint32 loopCntr=0;
	uint32 numFileRecrds = 0;
	
	MPFSFileRecord **ptr = files.Begin();
	MPFSFileRecord **pend = files.End();
	while(ptr < pend)
	{
        (*ptr)->locStr = baseAddr;
        baseAddr += (uint32)(*ptr)->FileName.Length + 1;
		ptr++;
	}
	ptr = files.Begin();
	while(ptr < pend)
	{
        (*ptr)->locData = baseAddr;
        baseAddr += (uint32)(*ptr)->data.Length;
		ptr++;
	}

	// Write the image
    w.Write("MPFS");
    w.Write((byte)0x02);
    w.Write((byte)0x01);
    w.Write((uint16)files.Length);

	ptr = files.Begin();
	while(ptr < pend)
	{
        w.Write((uint16)(*ptr)->nameHash);
		ptr++;
	}

	ptr = files.Begin();
	while(ptr < pend)
	{
        w.Write((*ptr)->locStr);
        w.Write((*ptr)->locData);
        w.Write((uint32)(*ptr)->data.Length);
        w.Write(dateTimeToPicTime((*ptr)->fileDate));
        w.Write((uint32)0);
		
		uint16 flags = 0;
        if ((*ptr)->hasIndex)
            flags |= MPFS2_FLAG_HASINDEX;
        if ((*ptr)->isZipped)
            flags |= MPFS2_FLAG_ISZIPPED;
        w.Write(flags);
		ptr++;
    }

	ptr = files.Begin();
	while(ptr < pend)
	{
        w.Write((*ptr)->FileName);
        w.Write((byte)0x00);
		ptr++;
    }

	ptr = files.Begin();
	while(ptr < pend)
	{
		w.Write((*ptr)->data.Begin(),(*ptr)->data.Length);
		ptr++;
    }

    w.Close();

	generatedImageName = w.imageName;

	infoLog("\r\nGENERATED MPFS2 IMAGE: " + xbString(w.ImageLength) + " bytes (index updated:"+xbString(IndexUpdated?"yes":"no")+")\n -> "+generatedImageName+"\n");
}
#ifdef hhh


public class MPFSClassicBuilder : MPFSBuilder
{
    #region Fields
    public UInt32 ReserveBlock;
    #endregion

    #region Constructor
    /// <summary>
    /// Creates a new MPFS Classic image builder
    /// </summary>
    /// <param name="localPath">The directory to save the image in</param>
    /// <param name="localFile">The output file name for the image</param>
    public MPFSClassicBuilder(String localPath, String localFile)
    {
        if (!localPath.EndsWith("\\"))
            localPath += "\\";
        this.LocalPath = localPath;
        this.LocalFile = localFile;
        this.ReserveBlock = 0;
        this.log = new List<string>();
        this.files = new List<MPFSFileRecord>();
        this.indexUpdated = false;
    }
    #endregion

    #region Constants
    const byte MPFS_DATA = 0x00;
    const byte MPFS_DELETED = 0x01;
    const byte MPFS_DLE = 0x03;
    const byte MPFS_ETX = 0x04;

    String MPFS_C_HEADER =
        "/***************************************************************\r\n" +
        " * MPFSImg.c\r\n" +
        " * Defines an MPFS2 image to be stored in program memory.\r\n" +
        " *\r\n" +
        " * NOT FOR HAND MODIFICATION\r\n" +
        " * This file is automatically generated by the MPFS2 Utility\r\n" +
        " * ALL MODIFICATIONS WILL BE OVERWRITTEN BY THE MPFS2 GENERATOR\r\n" +
        " * Generated " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + "\r\n" +
        " ***************************************************************/\r\n" +
        "\r\n" +
        "#define __MPFSIMG_C\r\n" +
        "\r\n" +
        "#include \"TCPIP Stack/TCPIP.h\"\r\n" +
        "\r\n" +
        "#if defined(STACK_USE_MPFS) && !defined(MPFS_USE_EEPROM) && !defined(MPFS_USE_SPI_FLASH)\r\n" +
        "\r\n";
    String MPFS_C_FOOTER =
        "/**************************************************************\r\n" +
        " * End of MPFS\r\n" +
        " **************************************************************/\r\n" +
        "#endif // #if defined(STACK_USE_MPFS) && !defined(MPFS_USE_EEPROM) && !defined(MPFS_USE_SPI_FLASH)" +
        "\r\n\r\n";
    String MPFS_ASM_HEADER =
        ";**************************************************************\r\n" +
        "; MPFSImg.s\r\n" +
        "; Defines an MPFS2 image to be stored in program memory.\r\n" +
        "; Defined in ASM30 assembly for optimal storage size.\r\n" +
        ";\r\n" +
        "; NOT FOR HAND MODIFICATION\r\n" +
        "; This file is automatically generated by the MPFS2 Utility\r\n" +
        "; ALL MODIFICATIONS WILL BE OVERWRITTEN BY THE MPFS2 GENERATOR\r\n" +
        "; Generated " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + "\r\n" +
        ";**************************************************************\r\n\r\n" +
        ".equ VALID_ID,0\r\n" +
        ".ifdecl __dsPIC30F\r\n" +
        "    .include \"p30fxxxx.inc\"\r\n" +
        ".endif\r\n" +
        ".ifdecl __dsPIC33F\r\n" +
        "    .include \"p33fxxxx.inc\"\r\n" +
        ".endif\r\n" +
        ".ifdecl __PIC24H\r\n" +
        "    .include \"p24hxxxx.inc\"\r\n" +
        ".endif\r\n" +
        ".ifdecl __PIC24F\r\n" +
        "    .include \"p24fxxxx.inc\"\r\n" +
        ".endif\r\n" +
        ".if VALID_ID <> 1\r\n" +
        "    .error \"Processor ID not specified in generic include files.  New ASM30 assembler needs to be downloaded?\"\r\n" +
        ".endif\r\n" +
        "	 .text\r\n" +
        "	 .section	MPFSData,code\r\n\r\n" +
        "	 goto END_OF_MPFS	; Prevent accidental execution of constant data.\r\n" +
        "	 .global BEGIN_MPFS\r\n" +
        "BEGIN_MPFS:\r\n";
    String MPFS_ASM_FOOTER = "\r\n\r\n; End of Generated Image\r\n";
    #endregion

    #region Public Methods
    /// <summary>
    /// Adds a file to the MPFS image
    /// </summary>
    /// <param name="localName">Local file name to read</param>
    /// <param name="imageName">Name to use in image file</param>
    public override bool AddFile(String localName, String imageName)
    {
        // Skip if can't be opened
        if (!File.Exists(localName))
        {
            log.Add("\r\nERROR: Could not read " + localName);
            return false;
        }

        // Set up the new file record
        MPFSFileRecord newFile = new MPFSFileRecord();
        newFile.FileName = imageName;

        // Read the data in, escaping as it is read
        byte b;
        List<byte> data = new List<byte>(1024);
        FileStream fs = new FileStream(localName, FileMode.Open, FileAccess.Read);
        BinaryReader fin = new BinaryReader(fs);
        for (int i = 0; i < fs.Length; i++)
        {
            if(data.Count == data.Capacity)
                data.Capacity *= 2;

            b = fin.ReadByte();
            if (b == MPFS_DLE || b == MPFS_ETX)
                data.Add(MPFS_DLE);
            data.Add(b);
        }
        fin.Close();
        newFile.data = data.ToArray();

        // Add the file and return
        log.Add("    " + imageName + ": " + newFile.data.Length + " bytes");
        files.Add(newFile);

        return true;
    }

    /// <summary>
    /// Adds a directory to the MPFS image.  All non-hidden files will be included.
    /// </summary>
    /// <param name="dataPath">The local directory to search</param>
    /// <param name="imagePath">Ignored for MPFS Classic</param>
    /// <returns></returns>
    public override bool AddDirectory(String dataPath, String imagePath)
    {
        // Make sure directory exists
        if (!Directory.Exists(dataPath))
        {
            log.Add("\r\nERROR: Directory " + dataPath + " does not exist.");
            return false;
        }

        // Make sure directory is not the project directory
        if (this.localPath.Contains(dataPath))
        {
            log.Add("\r\nERROR: The project directory is located in the source " +
                    "directory.  The generator cannot run if the image is to be placed " +
                    "in the source directory.  Please select the base MPLAB project " +
                    "directory before continuing.");
            return false;
        }

        // Load directory members
        DirectoryInfo dir = new DirectoryInfo(dataPath);
        FileInfo[] filelist = dir.GetFiles();

        log.Add(dataPath + " :");

        // Add all sub files
        for (int i = 0; i < filelist.Length; i++)
            if ((filelist[i].Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
                this.AddFile(filelist[i].FullName, imagePath + filelist[i].Name);

        return true;
    }

    public override bool Generate(MPFSOutputFormat format)
    {
        // Start with nothing
        generatedImageName = null;

        // Make sure all paths exist
        if (!Directory.Exists(localPath))
        {
            log.Add("\r\nERROR: Output directory \"" + localPath + "\" does not exist!");
            return false;
        }

        // Make sure we have some files
        if(files.Count == 0)
            return false;

        // Generate based on format
        try
        {
            switch (format)
            {
                case MPFSOutputFormat.BIN:
                    return GenerateBIN(localPath + localFile);
                case MPFSOutputFormat.C18:
                case MPFSOutputFormat.C32:
                    return GenerateC(localPath + localFile);
                case MPFSOutputFormat.ASM30:
                    return GenerateASM(localPath + localFile);
                default:
                    log.Add("\r\nERROR: Invalid output format was specified.");
                    return false;
            }
        }
        catch (Exception e)
        {
            log.Add("\r\nERROR: " + e.Message);
            return false;
        }

    }

    private bool GenerateBIN(String filename)
    {
        // Open the file
        if (!filename.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
            filename += ".bin";
        BinaryWriter fout = new BinaryWriter(new FileStream(filename, FileMode.Create), Encoding.ASCII);

        // Write the FAT data
        UInt32 baseAddr = ReserveBlock + 17 * ((UInt32)files.Count + 1);
        foreach (MPFSFileRecord file in files)
        {
            fout.Write(MPFS_DATA);
            fout.Write((byte)(baseAddr));
            fout.Write((byte)(baseAddr >> 8));
            fout.Write((byte)(baseAddr >> 16));
            fout.Write((byte)(baseAddr >> 24));
            fout.Write(NormalizeFileName(file.FileName).ToCharArray());
            baseAddr += (UInt32)file.data.Length + 5;
        }
        fout.Write(MPFS_ETX);
        fout.Write((uint)0xffffffff);
        fout.Write("END OF FAT  ".ToCharArray());

        // Write the files
        foreach (MPFSFileRecord file in files)
        {
            fout.Write(file.data);
            fout.Write(MPFS_ETX);
            fout.Write((uint)0xffffffff);
        }

        // Flush the output and store the file name
        fout.Flush();
        fout.Close();
        generatedImageName = filename;
        return true;
    }

    private bool GenerateC(String filename)
    {
        // Open the file
        if (!filename.EndsWith(".c", StringComparison.OrdinalIgnoreCase))
            filename += ".c";
        StreamWriter fout = new StreamWriter(filename, false, Encoding.ASCII);

        fout.Write(MPFS_C_HEADER);

        // Write the files
        int fileIndex = 0;
        foreach (MPFSFileRecord file in files)
        {
            fout.Write("\r\n/*******************************\r\n * Begin ");
            fout.Write(NormalizeFileName(file.FileName));
            fout.Write("\r\n ******************************/\r\nstatic ROM unsigned char MPFS_");
            fout.Write(Convert.ToString(fileIndex++, 16).PadLeft(4, '0') + "[] = \r\n{");

            for (int i = 0; i < file.data.Length; i++)
            {
                if (i % 12 == 0)
                    fout.Write("\r\n\t");
                fout.Write("0x" + Convert.ToString(file.data[i], 16).PadLeft(2, '0') + ",");
            }

            fout.Write("\r\n\t0x04,0xff,0xff,0xff\r\n};\r\n");
        }

        // Write the FAT
        fileIndex = 0;
        fout.Write(
            "/**************************************************\r\n" +
            " * Start of MPFS FAT\r\n" +
            " **************************************************/\r\n" +
            "typedef struct\r\n" +
            "{\r\n" +
            "    unsigned char Flags;\r\n" +
            "    ROM unsigned char* Address;\r\n" +
            "    unsigned char Name[12];\r\n" +
            "} FAT_TABLE_ENTRY;\r\n" +
            "\r\n" +
            "ROM FAT_TABLE_ENTRY MPFS_Start[] = \r\n" +
            "{"
        );
        foreach (MPFSFileRecord file in files)
        {
            fout.Write("\r\n\t{ 0x00, MPFS_" + Convert.ToString(fileIndex++, 16).PadLeft(4, '0'));
            foreach (byte b in NormalizeFileName(file.FileName))
            {
                fout.Write(", '" + (char)b + "'");
            }
            fout.Write(" },");
        }
        fout.Write(
            "\r\n\t{ 0x04, (ROM unsigned char*)0xffffff, 'E', 'N', 'D', ' ', 'O', 'F', ' ', 'F', 'A', 'T', ' ', ' ' },\r\n" +
            "};\r\n" +
            "/**************************************************\r\n" +
            " * End of MPFS FAT\r\n" +
            " **************************************************/\r\n\r\n"
        );

        fout.Write(MPFS_C_FOOTER);

        // Flush the output and store the file name
        fout.Flush();
        fout.Close();
        generatedImageName = filename;
        return true;
    }

    private bool GenerateASM(String filename)
    {
        // Open the file
        if (!filename.EndsWith(".s", StringComparison.OrdinalIgnoreCase))
            filename += ".s";
        StreamWriter fout = new StreamWriter(filename, false, Encoding.ASCII);

        fout.Write(MPFS_ASM_HEADER);

        // Write the files
        int fileIndex = 0;
        foreach (MPFSFileRecord file in files)
        {
            fout.Write("\r\n;*******************************\r\n;  Begin ");
            fout.Write(NormalizeFileName(file.FileName));
            fout.Write("\r\n;*******************************\r\n" +
                "\tgoto\tEND_OF_MPFS_");
            fout.Write(Convert.ToString(fileIndex, 16).PadLeft(4, '0'));
            fout.Write("\t\t; Prevent accidental execution of constant data\r\n\t.global _MPFS_");
            fout.Write(Convert.ToString(fileIndex, 16).PadLeft(4, '0'));
            fout.Write("\r\n_MPFS_");
            fout.Write(Convert.ToString(fileIndex, 16).PadLeft(4, '0'));
            fout.Write(":");

            for (int i = 0; i < file.data.Length; i++)
            {
                if (i % 12 == 0)
                    fout.Write("\r\n\t.pbyte\t");
                fout.Write("0x" + Convert.ToString(file.data[i], 16).PadLeft(2, '0'));
                if (i % 12 != 11 && i != file.data.Length-1)
                    fout.Write(",");
            }

            fout.Write("\r\n\t.pbyte\t0x04,0xff,0xff,0xff,0xff\r\nEND_OF_MPFS_");
            fout.Write(Convert.ToString(fileIndex++, 16).PadLeft(4, '0'));
            fout.Write(":\r\n");
        }

        // Write the FAT
        fileIndex = 0;
        fout.Write(
            ";*************************************************\r\n" +
            ";  Start of MPFS FAT\r\n" +
            ";*************************************************\r\n" +
            "\t.section\t.const,psv\r\n" +
            "\t.global _MPFS_Start\r\n" +
            "_MPFS_Start:"
        );
        foreach (MPFSFileRecord file in files)
        {
            fout.Write("\r\n\t.byte\t0x00,0x00\r\n\t.long\tpaddr(_MPFS_");
            fout.Write(Convert.ToString(fileIndex++, 16).PadLeft(4, '0'));
            fout.Write(")\r\n\t.byte\t");
            int i = 0;
            foreach (byte b in NormalizeFileName(file.FileName))
            {
                fout.Write("'" + (char)b + "'");
                if (++i < 12)
                    fout.Write(",");
            }
        }
        fout.Write(
            "\r\n\t.byte\t0x04,0x00\r\n\t.long\t0xffffffff" +
            "\r\n\t.byte\t'E','N','D',' ','O','F',' ','F','A','T',' ',' '\r\n\r\n" + 
            "\t.section MPFSEnd,code\r\n" +
            "END_OF_MPFS:\r\n"
        );

        fout.Write(MPFS_ASM_FOOTER);

        // Flush the output and store the file name
        fout.Flush();
        fout.Close();
        generatedImageName = filename;
        return true;
    }

    private String NormalizeFileName(String name)
    {
        if (name.Length > 12)
            name = name.Substring(0, 12);
        return name.PadRight(12).ToUpper();
    }

    #endregion
}

#endif

//Inspired from microchip Bootloader_SW_102811\Boot24HUART\x86 Host\16-Bit Flash Programmer.cpp
#define HEXM_24BITS_BLOCK	0x80
#define HEXM_8BITS_BLOCK	0x81
#define HEXM_32BITS_BLOCK	0x82
#define HEXM_16BITS_BLOCK	0x83
class hexMemWriter:public xbBase
{
protected:
	MPFS2Builder *m_pBuilder;
	uint32 m_extAddr;

	class memFrag:public xbBase
	{
	protected:
		uint32 m_startAddr;
		xbData m_data;
		MPFS2Builder *m_pBuilder;
	public:
		memFrag(MPFS2Builder *pBuilder,uint32 addr)
			:m_pBuilder(pBuilder),m_startAddr(addr)
		{
		}

		void add(const char *line)
		{
			int data = -1;
			if (sscanf(line, "%2x", &data) != 1
				|| data < 0)
				eMpfs2::ThrowV("Invalid data record found in hex file");
			uint8 ch = (uint8)data;
			m_data.Add(&ch,1);
		}
		void ffPad(uint32 padLength)
		{
			if (padLength >= m_pBuilder->HexRow * 4)
				eMpfs2::ThrowV("Invalid padding");

			uint8 chFF = 0xFF;
			uint8 ch00 = 0x00;
			while(padLength--)
			{
				if ((m_data.Length%4) == 3)
					m_data.Add(&ch00,1);
				else
					m_data.Add(&chFF,1);
			}
		}
		uint32 startAddr() const {return m_startAddr;}
		uint32 endAddr() const {return m_startAddr + m_data.Length;}

		static int FindCb(memFrag* const &pItem1,const void *addr)
		{
			if (pItem1->m_startAddr > (uint32)addr)
				return 1;
			if (pItem1->m_startAddr < (uint32)addr)
				return -1;
			return 0;
		}
		void OutputTo(xbData *pData,MPFShexFormat fmt)
		{
			switch(fmt)
			{
			case MPFShexFormat_8bits:
				Output8bits_To(pData);
				break;
			case MPFShexFormat_16bits:
				Output16bits_To(pData);
				break;
			case MPFShexFormat_24bits:
				Output24bits_To(pData);
				break;
			case MPFShexFormat_32bits:
				Output32bits_To(pData);
				break;
			default:
				eMpfs2::ThrowV("Invalid hex format !");
			}
		}
		void Output24bits_To(xbData *pData)
		{
			uint32 addr = m_startAddr;
			if (addr & 0x1)
				eMpfs2::ThrowV("Data record at invalid offset (must be word aligned)");
			addr >>=1;
			BYTE op[6];
			if (m_data.IsEmpty())
				return;

			if (!is24bitsBlock())
				eMpfs2::ThrowV("Data record invalid for 24bits mode (high order byte in a dword found != 0)");

			int blockLimit;
			if (m_pBuilder->HexRow)
				blockLimit = (0xFFFF)/(m_pBuilder->HexRow*2)*(m_pBuilder->HexRow*2);
			else
				blockLimit = 0xFFF0;

			int lg = (m_data.Length / 4) * 3;

			const BYTE *ptr = m_data.Begin();
			while(lg > 0)
			{
				if (addr > 0xFFFFFF)
					eMpfs2::ThrowV("Data record at invalid offset (>0xFFFFFF)");
				if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < m_pBuilder->HexRowLimit)
				{
					if ((addr % (m_pBuilder->HexRow*2)) != 0)
						eMpfs2::ThrowV("Data record not aligned on rows (address=%08lX, row size=%d)",addr,m_pBuilder->HexRow*2);
				}
				op[0] = HEXM_24BITS_BLOCK;
				op[1] = (addr)       & 0xFF;
				op[2] = (addr >> 8)  & 0xFF;
				op[3] = (addr >> 16) & 0xFF;
				int lgWr = lg;
				if (lgWr > blockLimit)
					lgWr = blockLimit;
					
				op[4] = lgWr & 0xFF;
				op[5] = (lgWr >> 8) & 0xFF;
				pData->Add(&op,sizeof(op));

				const BYTE *ptrEnd = ptr + (lgWr/3)*4;
				while(ptr < ptrEnd)
				{
					pData->Add(ptr,3);
					ptr+=4;
				}
				lg -= lgWr;
				addr += (lgWr/3)*2;
			}
		}
		bool is24bitsBlock()
		{
			if ((m_data.Length % 4) != 0)
				return false;
			const BYTE *ptr = m_data.Begin();
			const BYTE *ptrEnd = m_data.End();
			while(ptr < ptrEnd)
			{
				if (*(ptr+3) != 0)
					return false;
				ptr+=4;
			}
			return true;
		}
		void Output8bits_To(xbData *pData)
		{
			uint32 addr = m_startAddr;
			BYTE op[6];
			if (m_data.IsEmpty())
				return;

			int blockLimit;
			if (m_pBuilder->HexRow)
				blockLimit = (0xFFFF)/(m_pBuilder->HexRow*4)*(m_pBuilder->HexRow*4);
			else
				blockLimit = 0xFFF0;

			int lg = m_data.Length;

			const BYTE *ptr = m_data.Begin();
			while(lg > 0)
			{
				if (addr > 0xFFFFFF)
					eMpfs2::ThrowV("Data record at invalid offset (>0xFFFFFF)");
				if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < m_pBuilder->HexRowLimit)
				{
					if ((addr % (m_pBuilder->HexRow*4)) != 0)
						eMpfs2::ThrowV("Data record not aligned on rows (address=%08lX, row size=%d)",addr,m_pBuilder->HexRow*4);
				}
				op[0] = HEXM_8BITS_BLOCK;
				op[1] = (addr)       & 0xFF;
				op[2] = (addr >> 8)  & 0xFF;
				op[3] = (addr >> 16) & 0xFF;
				int lgWr = lg;
				if (lgWr > blockLimit)
					lgWr = blockLimit;
					
				op[4] = lgWr & 0xFF;
				op[5] = (lgWr >> 8) & 0xFF;
				pData->Add(&op,sizeof(op));
				pData->Add(ptr, lgWr);
				ptr += lgWr;
				lg -= lgWr;
				addr += lgWr;
			}
		}
		void Output32bits_To(xbData *pData)
		{
			uint32 addr = m_startAddr;
			if (addr & 0x3)
				eMpfs2::ThrowV("Data record at invalid offset (must be dword aligned)");
			addr >>=2;
			BYTE op[8];
			if (m_data.IsEmpty())
				return;

			int blockLimit;
			if (m_pBuilder->HexRow)
				blockLimit = (0xFFFFFF)/(m_pBuilder->HexRow*2)*(m_pBuilder->HexRow*2);
			else
				blockLimit = 0xFFFFF0;

			int lg = m_data.Length;

			const BYTE *ptr = m_data.Begin();
			while(lg > 0)
			{
				if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < m_pBuilder->HexRowLimit)
				{
					if ((addr % m_pBuilder->HexRow) != 0)
						eMpfs2::ThrowV("Data record not aligned on rows (address=%08lX, row size=%d)",addr,m_pBuilder->HexRow);
				}

				int lgWr = lg;
				if (lgWr > blockLimit)
					lgWr = blockLimit;

				op[0] = HEXM_32BITS_BLOCK;
				op[1] = (lgWr)       & 0xFF;
				op[2] = (lgWr >> 8)  & 0xFF;
				op[3] = (lgWr >> 16) & 0xFF;
					
				op[4] = (addr)       & 0xFF;
				op[5] = (addr >> 8)  & 0xFF;
				op[6] = (addr >> 16) & 0xFF;
				op[7] = (addr >> 24) & 0xFF;

				pData->Add(&op,sizeof(op));
				pData->Add(ptr, lgWr);
				ptr += lgWr;
				lg -= lgWr;
				addr += lgWr/4;
			}
		}
		void Output16bits_To(xbData *pData)
		{
			uint32 addr = m_startAddr;
			if (addr & 0x1)
				eMpfs2::ThrowV("Data record at invalid offset (must be word aligned)");
			addr >>=1;
			BYTE op[6];
			if (m_data.IsEmpty())
				return;

			int blockLimit;
			if (m_pBuilder->HexRow)
				blockLimit = (0xFFFF)/(m_pBuilder->HexRow*2)*(m_pBuilder->HexRow*2);
			else
				blockLimit = 0xFFF0;

			int lg = m_data.Length;

			const BYTE *ptr = m_data.Begin();
			while(lg > 0)
			{
				if (addr > 0xFFFFFF)
					eMpfs2::ThrowV("Data record at invalid offset (>0xFFFFFF)");
				if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < m_pBuilder->HexRowLimit)
				{
					if ((addr % (m_pBuilder->HexRow*2)) != 0)
						eMpfs2::ThrowV("Data record not aligned on rows (address=%08lX, row size=%d)",addr,m_pBuilder->HexRow*2);
				}
				op[0] = HEXM_16BITS_BLOCK;
				op[1] = (addr)       & 0xFF;
				op[2] = (addr >> 8)  & 0xFF;
				op[3] = (addr >> 16) & 0xFF;
				int lgWr = lg;
				if (lgWr > blockLimit)
					lgWr = blockLimit;
					
				op[4] = lgWr & 0xFF;
				op[5] = (lgWr >> 8) & 0xFF;
				pData->Add(&op,sizeof(op));
				pData->Add(ptr, lgWr);
				ptr += lgWr;
				lg -= lgWr;
				addr += lgWr/2;
			}
		}
	};

	xbPtrArray<memFrag> m_arrFrag;
	memFrag				*m_pLastFrag;

	memFrag *memFragGet(uint32 addr)
	{
		if (m_pLastFrag)
		{
			if (m_pLastFrag->endAddr() == addr)
				return m_pLastFrag;

			if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < (m_pBuilder->HexRowLimit*2))
			{
				uint32 alignAddr = addr / (m_pBuilder->HexRow*4) * (m_pBuilder->HexRow * 4);
				if (alignAddr != addr
					&& m_pLastFrag->endAddr() == addr)
				{
					m_pLastFrag->ffPad(addr - alignAddr);
					return m_pLastFrag;
				}
			}
		}

		uint32 alignAddr;
		if (m_pBuilder->HexRow && m_pBuilder->HexRowLimit && addr < m_pBuilder->HexRowLimit*2)
			alignAddr = addr / (m_pBuilder->HexRow*4) * (m_pBuilder->HexRow * 4);
		else
			alignAddr = addr;

		int insertPos;
		memFrag *const *ppFrag = m_arrFrag.SortedFind((const void*)alignAddr,memFrag::FindCb,&insertPos);
		if (ppFrag)
			eMpfs2::ThrowV("Invalid data record found in hex file (overlap case 1)");
		if (insertPos > 0)
		{
			memFrag *pFrag = m_arrFrag.GetAt(insertPos-1);
			if (alignAddr < pFrag->endAddr())
				eMpfs2::ThrowV("Invalid data record found in hex file (overlap case 2)");
			if (alignAddr == pFrag->endAddr())
			{
				m_pLastFrag = pFrag;
				if (addr != alignAddr)
					pFrag->ffPad(addr - alignAddr);
				return pFrag;
			}
		}
		memFrag *pFrag = xbDnew memFrag(m_pBuilder,alignAddr);
		m_arrFrag.Insert(insertPos,pFrag);
		m_pLastFrag = pFrag;
		if (addr != alignAddr)
			pFrag->ffPad(addr - alignAddr);
		return pFrag;
	}
public:
	hexMemWriter(MPFS2Builder *pBuilder)
	{
		m_extAddr = 0;
		m_pLastFrag = NULL;
		m_pBuilder = pBuilder;
	}
	void WriteData(int Address,int ByteCount,const char *line)
	{
		uint32 addr = m_extAddr + (uint32)Address;
		while(ByteCount--)
		{
			memFragGet(addr)->add(line);
			line+=2;
			addr++;
		}
	}
	void SetExtAddr(const char *line)
	{
		int ExtAddr = -1;
		if (sscanf(line, "%4x", &ExtAddr) != 1
			|| ExtAddr < 0)
			eMpfs2::ThrowV("Invalid ext address record found in hex file");
		m_extAddr = ((uint32)ExtAddr)<<16;
	}
	void OutputTo(xbData *pData,MPFShexFormat fmt)
	{
		pData->Empty();

		for(int i = 0; i < m_arrFrag.Length; i++)
		{
			m_arrFrag.GetAt(i)->OutputTo(pData,fmt);
		}
	}
};

void MPFS2Builder::hexCompress(MPFSFileRecord *pFile)
{
	hexMemWriter memWriter(this);

	const char *ptr = (const char*)pFile->data.Begin();
	const char *ptrEnd = (const char*)pFile->data.End();
	int lineNo = 1;
	eCtxBegin();
	while(ptr < ptrEnd)
	{
		const char *line = ptr;
		while(ptr < ptrEnd && *ptr != '\n')
			ptr++;
		const char *lineEnd = ptr;
		if (ptr < ptrEnd)
			ptr++;

		if (lineEnd > line && *(lineEnd-1) == '\r')
			lineEnd--;

		if (*line != ':')
			eMpfs2::ThrowV("Invalid line found in hex file");
		if (lineEnd < ptrEnd)
		{
			*((char*)lineEnd) = 0;
		}
		line++;
		int ByteCount=-1;
		int Address=-1;
		int RecordType=-1;

		if (sscanf(line, "%2x%4x%2x", &ByteCount, &Address, &RecordType) != 3
			|| ByteCount < 0 || Address < 0 || RecordType < 0)
			eMpfs2::ThrowV("Invalid line found in hex file");

		line += 8;

		switch(RecordType)
		{
		case 0:
			memWriter.WriteData(Address,ByteCount,line);
		case 1: // End record ()
			break;
		case 4:
			//http://www.lucidtechnologies.info/inhx32.htm

			memWriter.SetExtAddr(line);
			break;
		default:
			eMpfs2::ThrowV("Invalid record(%d) found in hex file",RecordType);
			break;
		}
		lineNo++;
	}
	eCtxEndNoLoc("In file "+pFile->FileName+"("+xbString(lineNo)+")");

	memWriter.OutputTo(&pFile->data,HexFormat);
	pFile->FileName = pFile->FileName.RetReplaceNC(".hex",".hexm");
}
