/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

!!IMPORTANT!!
This software(mcTool) uses parts of sources owned by Microchip Technology, Inc, 
and so these parts are not covered by this licence. 
See header of concerned files (mpfs2dynVar.cpp, mpfs2lib.cpp) for their own licence. 
////////////////////////////////////////////////////////////////////////////////
#Title: mpfs2mLib
#Project: mcTool
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<05/02/2014: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"
#include "mpfs2mLib.h"
//Extract from HTTP2.h / TCPIP Stack from Microchip
// Caution: orders/indices are important (must be the same between HTTP2.h and mctool/mpfs2mLib.cpp)
typedef enum
{
	HTTP2m_TXT = 0u,		// File is a text document
	HTTP2m_HTML,			// File is HTML (extension .htm, .html, .cgi)
	HTTP2m_XML,			// File is XML (extension .xml)
	HTTP2m_CSS,			// File is stylesheet (extension .css)
	HTTP2m_GIF,			// File is GIF image (extension .gif)
	HTTP2m_PNG,			// File is PNG image (extension .png)
	HTTP2m_JPG,			// File is JPG image (extension .jpg)
	HTTP2m_JAVA,			// File is java (extension .java)
	HTTP2m_WAV,			// File is audio (extension .wav)
    HTTP2m_JS,                        // (mmm:added)
    HTTP2m_ICO,                       // (mmm:added)
	HTTP2m_UNKNOWN		// File type is unknown
} HTTP2m_CONTENT_TYPE;
#define HTTP2m_EXTCT 0x8000

struct extToCt
{
	const char *		pExt;
	HTTP2m_CONTENT_TYPE ct;
};
static extToCt s_http2m_FileExtensions[] =
{
	{"txt",HTTP2m_TXT},
	{"htm",HTTP2m_HTML},
	{"html",HTTP2m_HTML},
	{"cgi",HTTP2m_HTML},
	{"inc",HTTP2m_HTML},
	{"xml",HTTP2m_XML},
	{"css",HTTP2m_CSS},
	{"gif",HTTP2m_GIF},
	{"png",HTTP2m_PNG},
	{"jpg",HTTP2m_JPG},
	{"cla",HTTP2m_JAVA},
	{"wav",HTTP2m_WAV},
	{"js",HTTP2m_JS},
	{"ico",HTTP2m_ICO},
	{"hexm",HTTP2m_UNKNOWN},
	{NULL,HTTP2m_UNKNOWN}
};

const char * http2m_ContentTypes[] =
{
    "text/plain",            // HTTP2m_TXT
    "text/html",             // HTTP2m_HTML
    "text/xml",              // HTTP2m_XML
    "text/css",              // HTTP2m_CSS
    "image/gif",             // HTTP2m_GIF
    "image/png",             // HTTP2m_PNG
    "image/jpeg",            // HTTP2m_JPG
    "application/java-vm",   // HTTP2m_JAVA
    "audio/x-wave",          // HTTP2m_WAV
    "text/javascript",       // HTTP2m_JS (mmm:added)
    "image/x-icon",          // HTTP2m_ICO (mmm:added)
	"application/data",		 // HTTP2m_UNKNOWN
	NULL
};

void MPFS2mBuilder::contentTypeCheck(MPFSFileRecord *pFile,bool bUtf8)
{
	xbString ext(::xbGetFileExt(pFile->FileName,false));
	xbCfgKey ckey(xbCfgAppli,"MPFS2m/ContentTypes");
	if (ckey.Exists() && ckey.ValueExists(ext))
	{
		xbString sct(ckey.Value[ext].AsString);
		if (sct.IsValid())
		{
			int id = dynVarParser.MPFS2mContentTypeAdd(pFile->FileName,sct,FileLoc(pFile->origFileName));
			if (id < 0)
			{
				pFile->extType = HTTP2m_UNKNOWN;
				warningLog("File "+pFile->FileName+" has an unsupported extension",FileLoc(pFile->origFileName));
				return;
			}
			pFile->extType = HTTP2m_EXTCT + id;
			return;
		}
	}
	extToCt *pExtSupported = s_http2m_FileExtensions;
	while(pExtSupported->pExt)
	{
		if(ext.CompareNC(pExtSupported->pExt) == 0)
		{
			pFile->extType = pExtSupported->ct;
			return;
		}
		pExtSupported++;
	}
	for(;;)
	{
		xbString sct(g_xbCfgEngine.GetContentTypeFromExt("."+ext,xbOp_Optional));
		if (sct.IsEmpty())
			break;
		if (bUtf8)
			sct += "; charset=utf-8";
		int id = dynVarParser.MPFS2mContentTypeAdd(pFile->FileName,sct,FileLoc(pFile->origFileName));
		if (id < 0)
			break;
		pFile->extType = HTTP2m_EXTCT + id;
		return;
	}
	pFile->extType = HTTP2m_UNKNOWN;
	warningLog("File "+pFile->FileName+" has an unsupported extension",FileLoc(pFile->origFileName));
}

void MPFS2mBuilder::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();
	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)
		{
			dynVarParser.MPFS2mParse(*pnewFile);
		}

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

		contentTypeCheck(pnewFile,bUtf8);

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

void MPFS2mBuilder::WriteImage(MPFS2Writer &w)
{
    // Determine address of each file and string
    uint32 numFiles = (uint32)files.Length;
    uint32 lenHeader = 16;
    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("MPFM");
    w.Write((byte)0x02);
    w.Write((byte)0x02);
    w.Write((uint16)files.Length);
	w.WriteCRCblock();

	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((UInt32)(((*ptr)->fileDate.ToUniversalTime().Ticks - 621355968000000000) / 10000000));
        w.Write(dateTimeToPicTime((*ptr)->fileDate));
		w.Write((uint32)(*ptr)->extType); //MPFS2m reuse (0 for MPFS2)
		
		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 MPFS2m IMAGE: " + xbString(w.ImageLength) + " bytes (index updated:"+xbString(IndexUpdated?"yes":"no")+")\n -> "+generatedImageName+"\n");
}

xbRefPtr<MPFS2mBuilder::MPFS2Writer> MPFS2mBuilder::MPFS2WriterCreate(MPFSOutputFormat format)
{
	if (format == MPFSOutputFormat_BIN)
	{
		xbRefPtr<MPFS2Writer> w;
		MPFS2mBINWriter::CreateObject(&w,projectDir + outputFile);
		return w;
	}
	return MPFS2Builder::MPFS2WriterCreate(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_BIN:
			MPFS2BINWriter::CreateObject(&w,projectDir + outputFile);
            break;
	    default:
			eMpfs2::Throw("MPFS2 generation format not supported !");
    }
	return w;*/
}
