/*
Basic History plugin
Copyright (C) 2011-2012 Krzysztof Kral

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "StdAfx.h"
#include "RichHtmlExport.h"
#include "Options.h"
#include "resource.h"
#define EXP_FILE (*stream)

RichHtmlExport::~RichHtmlExport()
{
}

extern HINSTANCE hInst;
extern HANDLE *hEventIcons;
extern HANDLE hPlusExIcon, hMinusExIcon;
extern bool g_SmileyAddAvail;

std::wstring MakeTextHtmled(const std::wstring& message, std::queue<std::pair<size_t, size_t> >* positionMap = NULL)
{
	std::wstring ret;
	std::wstring search = _T("&<>\t\r\n");
	size_t start = 0;
	size_t find;
	size_t currentAdd = 0;
	while((find = message.find_first_of(search, start)) < message.length())
	{
		ret += message.substr(start, find - start);
		switch(message[find])
		{
		case _T('&'):
			ret += _T("&amp;");
			break;
		case _T('<'):
			ret += _T("&lt;");
			break;
		case _T('>'):
			ret += _T("&gt;");
			break;
		case _T('\t'):
			ret += _T(" ");
			break;
		case _T('\n'):
			ret += _T("<br>");
			break;
		}

		start = find + 1;
		if(positionMap != NULL)
		{
			size_t len = ret.length() - start - currentAdd;
			if(len != 0)
			{
				positionMap->push(std::pair<size_t, size_t>(start + currentAdd, len));
				currentAdd += len;
			}
		}
	}
	
	ret += message.substr(start, message.length() - start);
	return ret;
}

std::wstring UrlHighlightHtml(const std::wstring& message, bool& isUrl)
{
	std::wstring ret;
	std::wstring htmlStop = _T("\'\" []<>\r\n");
	std::wstring search = _T("://");
	size_t start = 0;
	size_t find;
	while((find = message.find(search, start)) < message.length())
	{
		size_t urlStart = message.find_last_of(htmlStop, find);
		size_t urlEnd = message.find_first_of(htmlStop, find + 3);
		if(urlStart >= message.length())
			urlStart = -1;
		if(urlEnd >= message.length())
			urlEnd = message.length();
		if(((int)urlEnd -3 - (int)find > 0) && ((int)find - (int)urlStart -1 > 0))
		{
			ret += message.substr(start, (urlStart + 1) - start);
			std::wstring url = message.substr(urlStart + 1, urlEnd - urlStart - 1);
			start = urlEnd;
			ret += _T("<a class=url target=_blank href=\"");
			ret += url + _T("\">") + url + _T("</a>");
			isUrl = true;
		}
		else
		{
			ret += message.substr(start, (find + 3) - start);
			start = find + 3;
		}
	}

	ret += message.substr(start, message.length() - start);
	return ret;
}

std::wstring RemoveExt(const std::wstring &fileName)
{
	size_t find = fileName.find_last_of(L'.');
	if(find < fileName.length())
	{
		return fileName.substr(0, find);
	}

	return fileName;
}

std::wstring GetName(const std::wstring &path)
{
	size_t find = path.find_last_of(L"\\/");
	if(find < path.length())
	{
		return path.substr(find + 1);
	}

	return path;
}

void ExtractFile(short int res, const std::wstring &fileName)
{
	HRSRC rSrc = FindResource(hInst, MAKEINTRESOURCE(res), MAKEINTRESOURCE(CUSTOMRES));
	if(rSrc != NULL)
	{
		HGLOBAL res = LoadResource(hInst, rSrc);
		int size = SizeofResource(hInst, rSrc);
		if(res != NULL)
		{
			char* resData = (char*)LockResource(res);
			if(resData != NULL)
			{
				std::ofstream stream (fileName.c_str(), std::ios_base::binary);
				if(stream.is_open())
				{
					stream.write(resData, size);
					stream.close();
				}
			}

			FreeResource(res);
		}
	}
}

#pragma pack(push, 2)
typedef struct
{
    BYTE        bWidth;          // Width, in pixels, of the image
    BYTE        bHeight;         // Height, in pixels, of the image
    BYTE        bColorCount;     // Number of colors in image (0 if >=8bpp)
    BYTE        bReserved;       // Reserved ( must be 0)
    WORD        wPlanes;         // Color Planes
    WORD        wBitCount;       // Bits per pixel
    DWORD       dwBytesInRes;    // How many bytes in this resource?
    DWORD       dwImageOffset;   // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;

typedef struct
{
    WORD           idReserved;   // Reserved (must be 0)
    WORD           idType;       // Resource Type (1 for icons)
    WORD           idCount;      // How many images?
    //ICONDIRENTRY   idEntries; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;

#pragma pack(pop)

typedef struct tagMyBITMAPINFO {
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[256];
} MYBITMAPINFO;

void IcoSave(const std::wstring &fileName, HICON hicon)
{
	std::ofstream store (fileName.c_str(), std::ios_base::binary);
	if(!store.is_open())
		return;
	ICONINFO		ii;
	if(!GetIconInfo(hicon,&ii))
	{
		store.close();
		return;
	}

	HBITMAP hbmMask = ii.hbmMask;
	HBITMAP hbmColor = ii.hbmColor;
	BITMAP  bmiMask;
	BITMAP  bmiColor;
	if(
	GetObject(hbmColor,sizeof(bmiColor),&bmiColor) &&
	GetObject(hbmMask,sizeof(bmiMask),&bmiMask) &&
	(bmiColor.bmWidth==bmiMask.bmWidth) &&
	(bmiColor.bmHeight==bmiMask.bmHeight) &&
	(bmiMask.bmHeight) > 0 &&
	(bmiMask.bmWidth) > 0
	)
	{
		BITMAPINFOHEADER  icobmi = {0};
		MYBITMAPINFO info1 = {0};
		MYBITMAPINFO info2 = {0};
		
		HDC hDC = CreateCompatibleDC(NULL);
		info1.bmiHeader.biSize = sizeof(info1.bmiHeader);
		info1.bmiHeader.biWidth     = bmiColor.bmWidth;
		info1.bmiHeader.biHeight    = bmiColor.bmHeight;
		info1.bmiHeader.biPlanes    = 1;
		info1.bmiHeader.biBitCount  = bmiColor.bmBitsPixel;
		unsigned int size = GetDIBits(hDC,hbmColor,0,info1.bmiHeader.biHeight,NULL,(BITMAPINFO*)&info1,DIB_RGB_COLORS);
		char* bits1 = new char[info1.bmiHeader.biSizeImage];
		size = GetDIBits(hDC,hbmColor,0,info1.bmiHeader.biHeight,bits1,(BITMAPINFO*)&info1,DIB_RGB_COLORS);
		info2.bmiHeader.biSize = sizeof(info2.bmiHeader);
		info2.bmiHeader.biWidth     = bmiMask.bmWidth;
		info2.bmiHeader.biHeight    = bmiMask.bmHeight;
		info2.bmiHeader.biPlanes    = 1;
		info2.bmiHeader.biBitCount  = bmiMask.bmBitsPixel;
		size = GetDIBits(hDC,hbmColor,0,info1.bmiHeader.biHeight,NULL,(BITMAPINFO*)&info2,DIB_RGB_COLORS);
		char* bits2 = new char[info2.bmiHeader.biSizeImage];
		size = GetDIBits(hDC,hbmMask,0,info2.bmiHeader.biHeight,bits2,(BITMAPINFO*)&info2,DIB_RGB_COLORS);

		ICONDIR            icodir;
		ICONDIRENTRY      icoent;
		icodir.idReserved = 0;
		icodir.idType     = 1;
		icodir.idCount    = 1;
 
		icoent.bWidth        = (unsigned char)bmiColor.bmWidth;
		icoent.bHeight       = (unsigned char)bmiColor.bmHeight;
		icoent.bColorCount   = 8<=bmiColor.bmBitsPixel?0:1<<bmiColor.bmBitsPixel;
		icoent.bReserved     = 0;
		icoent.wPlanes       = bmiColor.bmPlanes;
		icoent.wBitCount     = bmiColor.bmBitsPixel;
		icoent.dwBytesInRes  = sizeof(BITMAPINFOHEADER) + info1.bmiHeader.biSizeImage + info2.bmiHeader.biSizeImage;

		icoent.dwImageOffset = sizeof(icodir) + sizeof(icoent);
 
		store.write((char*)&icodir,sizeof(icodir));
		store.write((char*)&icoent,sizeof(icoent));
 
		icobmi.biSize      = sizeof(icobmi);
		icobmi.biWidth     = bmiColor.bmWidth;
		icobmi.biHeight    = bmiColor.bmHeight + bmiMask.bmHeight;
		icobmi.biPlanes    = info1.bmiHeader.biPlanes;
		icobmi.biBitCount  = bmiColor.bmBitsPixel;
		icobmi.biSizeImage = 0;
 
		store.write((char*)&icobmi,sizeof(icobmi));

		store.write(bits1, info1.bmiHeader.biSizeImage);
		store.write(bits2, info2.bmiHeader.biSizeImage);
		DeleteDC(hDC);
		delete [] bits1;
		delete [] bits2;
	}
	
	store.close();
	if(ii.hbmColor) DeleteObject(ii.hbmColor);
	if(ii.hbmMask ) DeleteObject(ii.hbmMask );
}

bool DeleteDirectory(LPCTSTR lpszDir, bool noRecycleBin = true)
{
  size_t len = _tcslen(lpszDir);
  TCHAR *pszFrom = new TCHAR[len+2];
  _tcscpy_s(pszFrom, len+2, lpszDir);
  pszFrom[len] = 0;
  pszFrom[len+1] = 0;
  
  SHFILEOPSTRUCT fileop;
  fileop.hwnd   = NULL;    // no status display
  fileop.wFunc  = FO_DELETE;  // delete operation
  fileop.pFrom  = pszFrom;  // source file name as double null terminated string
  fileop.pTo    = NULL;    // no destination needed
  fileop.fFlags = FOF_NOCONFIRMATION|FOF_SILENT;  // do not prompt the user
  
  if(!noRecycleBin)
    fileop.fFlags |= FOF_ALLOWUNDO;

  fileop.fAnyOperationsAborted = FALSE;
  fileop.lpszProgressTitle     = NULL;
  fileop.hNameMappings         = NULL;

  int ret = SHFileOperation(&fileop);
  delete [] pszFrom;  
  return (ret == 0);
}

void RichHtmlExport::WriteHeader(const std::wstring &fileName, const std::wstring &filterName, const std::wstring &myName, const std::wstring &myId, const std::wstring &name1, const std::wstring &proto1, const std::wstring &id1, const std::string& baseProto1, const std::wstring& encoding)
{
	baseProto = baseProto1;
	folder = RemoveExt(fileName) + TranslateT("_files");
	folderName = GetName(folder);
	DeleteDirectory(folder.c_str());
	CreateDirectory(folder.c_str(), NULL);
	std::wstring css =  folder + _T("\\history.css");
	BOOL cssCopied = FALSE;
	if(!Options::instance->extCssHtml2.empty())
	{
		cssCopied = CopyFile(Options::instance->extCssHtml2.c_str(), css.c_str(), FALSE);
	}

	if(!cssCopied)
		ExtractFile(IDR_CSS, css);
	ExtractFile(IDR_JS, folder + _T("\\history.js"));

	HICON ico = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, (LPARAM)hPlusExIcon);
	IcoSave(folder + _T("\\pnode.ico"), ico);
	CallService(MS_SKIN2_RELEASEICON, (LPARAM)ico, 0);
	ico = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, (LPARAM)hMinusExIcon);
	IcoSave(folder + _T("\\mnode.ico"), ico);
	CallService(MS_SKIN2_RELEASEICON, (LPARAM)ico, 0);
	ico = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, (LPARAM)hEventIcons[0]);
	IcoSave(folder + _T("\\event0.ico"), ico);
	CallService(MS_SKIN2_RELEASEICON, (LPARAM)ico, 0);
	ico = (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, 0, (LPARAM)hEventIcons[1]);
	IcoSave(folder + _T("\\event1.ico"), ico);
	CallService(MS_SKIN2_RELEASEICON, (LPARAM)ico, 0);

	EXP_FILE << _T("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n");
	EXP_FILE << _T("<html><head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=") << encoding << _T("\">\n");
	EXP_FILE << _T("<title>") << TranslateT("History Log") << _T(" [") << MakeTextHtmled(myName) << _T("] - [") << MakeTextHtmled(name1) << _T("]</title>\n");
	EXP_FILE << _T("<link rel=\"Stylesheet\" href=\"") << folderName << _T("\\history.css\" type=\"text/css\">\n");
	EXP_FILE << _T("<script type=\"text/javascript\" src=\"") << folderName << _T("\\history.js\"></script>\n");
	EXP_FILE << _T("</head><body>\n");

	EXP_FILE << _T("<span id=\"menubar\">\n");
	EXP_FILE << _T("<a class=mainmenu onmouseover='this.className=\"mainmenusel\";' href=\"javascript:void(0)\" onClick=\"ShowMenu(1)\" onMouseOut='HideMenu();this.className=\"mainmenu\";'>") << TranslateT("Menu") << _T("</a></span>\n");
	EXP_FILE << _T("<span class=floatmenu id=L1 onmouseover=clearTimeout(timer) onmouseout=HideMenu()>\n");
	EXP_FILE << _T("<table><tr>\n");
	EXP_FILE << _T("<td class=menuitemunsel onmouseover='this.className=\"menuitemsel\"' onmouseout='this.className=\"menuitemunsel\"'>\n");
	EXP_FILE << _T("<a class=menuitem onmouseover=ShowMenu(1) href=\"javascript:void(0)\" onclick=OpenAll(1)>") << TranslateT("Open all") << _T("</a>\n");
	EXP_FILE << _T("</td></tr><tr>\n");
	EXP_FILE << _T("<td class=menuitemunsel onmouseover='this.className=\"menuitemsel\"' onmouseout='this.className=\"menuitemunsel\"'>\n");
	EXP_FILE << _T("<a class=menuitem onmouseover=ShowMenu(1) href=\"javascript:void(0)\" onclick=OpenAll(0)>") << TranslateT("Close all") << _T("</a>\n");
	EXP_FILE << _T("</td></tr></table></span>\n");
	EXP_FILE << _T("<script language=\"JavaScript\">\n");
	EXP_FILE << _T("<!--\n");
	EXP_FILE << _T("var menu = document.getElementById(\"menubar\");\n");
	EXP_FILE << _T("if(menu != null)\n");
	EXP_FILE << _T("  menu.style.visibility = \"visible\";\n");
	EXP_FILE << _T("// -->\n");
	EXP_FILE << _T("</script>\n");

	EXP_FILE << _T("<h4>") << TranslateT("History Log") << _T("</h4>\n<h3>");
	EXP_FILE << MakeTextHtmled(myName);
	if(proto1.length() || myId.length())
	{
		EXP_FILE << _T(" (") << MakeTextHtmled(proto1) << _T(": ") << MakeTextHtmled(myId) << _T(") - ");
	}
	else
	{
		EXP_FILE << _T(" - ");
	}

	EXP_FILE << MakeTextHtmled(name1);
	if(proto1.length() || id1.length())
	{
		EXP_FILE << _T(" (") << MakeTextHtmled(proto1) << _T(": ") << MakeTextHtmled(id1) << _T(")</h3>\n");
	}
	else
	{
		EXP_FILE << _T("</h3>\n");
	}

	EXP_FILE << _T("<h6>") << TranslateT("Filter:") << _T(" ") << MakeTextHtmled(filterName) << _T("</h6>\n");
	groupId = 0;
}

void RichHtmlExport::WriteFooter()
{
	if(groupId > 0)
	{
		EXP_FILE << _T("</div>\n");
	}

	EXP_FILE << _T("<div class=mes id=bottom></div>\n</body></html>\n");
}

void RichHtmlExport::WriteGroup(bool isMe, const std::wstring &time, const std::wstring &user, const std::wstring &eventText)
{
	TCHAR *id = isMe ? _T("out") : _T("inc");
	TCHAR* ev = (isMe ? _T("1") : _T("0"));
	if(groupId > 0)
	{
		EXP_FILE << _T("</div>\n");
	}
	
	bool isUrl = false;
	std::wstring& mes = ReplaceSmileys(isMe, eventText, isUrl);
	EXP_FILE << _T("<div class=mes id=session>\n");
	EXP_FILE << _T("<span class=eventimg id=") << id << _T("><img src=\"") << folderName << _T("\\pnode.ico\" class=sessionimage width=\"16\" height=\"16\" onclick=\"toggleFolder('group") << groupId << _T("', this)\"/>");
	EXP_FILE << _T("<img src=\"") << folderName << _T("\\event") << ev << _T(".ico\" class=sessionimage width=\"16\" height=\"16\" onclick=\"toggleFolder('group") << groupId << _T("', this)\"/></span>\n");
	EXP_FILE << _T("<span class=date id=") << id << _T(">") << time << _T("</span>\n<span class=text>\n") << mes;
	EXP_FILE << _T("</span>\n</div>\n");
	EXP_FILE << _T("<div class=group id=group") << groupId << _T(">\n");
	++groupId;
}

void RichHtmlExport::WriteMessage(bool isMe, const std::wstring &longDate, const std::wstring &shortDate, const std::wstring &user, const std::wstring &message, const DBEVENTINFO& dbei)
{
	TCHAR *id = isMe ? _T("out") : _T("inc");
	TCHAR* ev = (isMe ? _T("1") : _T("0"));
	TCHAR* ev1 = ev;
	bool isUrl = false;
	std::wstring& mes = ReplaceSmileys(isMe, message, isUrl);
	if(isUrl)
		ev = _T("2");
	EXP_FILE << _T("<div class=mes id=event") << ev << _T(">\n");
	EXP_FILE << _T("<div class=eventimg id=") << id << _T(">")  << _T("<img src=\"") << folderName << _T("\\event") << ev1 << _T(".ico\" class=sessionimage width=\"16\" height=\"16\"/></div>\n");
	EXP_FILE << _T("<div class=date id=") << id << _T(">") << (Options::instance->exportHtml2ShowDate ? longDate : shortDate) << _T("</div>\n");
	EXP_FILE << _T("<div class=nick id=") << id << _T(">") << MakeTextHtmled(user) << _T("</div>\n");
	EXP_FILE << _T("<div class=text>\n");
	EXP_FILE << mes;
	EXP_FILE << _T("\n</div>\n");
	EXP_FILE << _T("</div>\n");
}

std::wstring RichHtmlExport::ReplaceSmileys(bool isMe, const std::wstring &msg, bool &isUrl)
{
	if(Options::instance->exportHtml2UseSmileys && g_SmileyAddAvail)
	{
		TCHAR* msgbuf = new TCHAR[msg.length() + 1];
		memcpy_s(msgbuf, (msg.length() + 1) * sizeof(TCHAR), msg.c_str(), (msg.length() + 1) * sizeof(TCHAR));
		SMADD_BATCHPARSE2 sp = {0};
		SMADD_BATCHPARSERES *spr;
		sp.cbSize = sizeof(sp);
		sp.Protocolname = baseProto.length() == 0 ? NULL : baseProto.c_str();
		sp.str = msgbuf;
		sp.flag = SAFL_TCHAR | SAFL_PATH | (isMe ? SAFL_OUTGOING : 0);
		spr = (SMADD_BATCHPARSERES*)CallService(MS_SMILEYADD_BATCHPARSE, 0, (LPARAM)&sp);
		delete[] msgbuf;

		if (spr == NULL || (INT_PTR)spr == CALLSERVICE_NOTFOUND)
		{
			// Did not find a simley
			return UrlHighlightHtml(MakeTextHtmled(msg), isUrl);
		}

		std::queue<std::pair<size_t, size_t> > positionMap;
		std::wstring newMsg = MakeTextHtmled(msg, &positionMap);
		std::wstring smileyMsg;
		
		size_t last_pos=0;
		std::pair<size_t, size_t> pos(0, 0);
		size_t currentAdd = 0;
		if(!positionMap.empty())
		{
			pos = positionMap.front();
			positionMap.pop();
		}

		for (unsigned i = 0; i < sp.numSmileys; ++i)
		{
			size_t startChar = spr[i].startChar + currentAdd;
			while(startChar >= pos.first && pos.second)
			{
				startChar += pos.second;
				currentAdd += pos.second;
				if(!positionMap.empty())
				{
					pos = positionMap.front();
					positionMap.pop();
				}
				else
				{
					pos = std::pair<size_t, size_t>(0, 0);
				}
			}

			size_t endChar = spr[i].startChar + spr[i].size + currentAdd;
			while(endChar >= pos.first && pos.second)
			{
				endChar += pos.second;
				currentAdd += pos.second;
				if(!positionMap.empty())
				{
					pos = positionMap.front();
					positionMap.pop();
				}
				else
				{
					pos = std::pair<size_t, size_t>(0, 0);
				}
			}

			size_t size = endChar - startChar;

			if (spr[i].filepath != NULL)	// For deffective smileypacks
			{
				// Add text
				if (startChar - last_pos > 0)
				{
					smileyMsg += newMsg.substr(last_pos, startChar - last_pos);
				}

				std::wstring smileyName = GetName(spr[i].filepath);
				if(smileys.find(smileyName) == smileys.end())
				{
					smileys.insert(smileyName);
					CopyFile(spr[i].filepath, (folder + _T("\\") + smileyName).c_str(), FALSE);
				}

				std::wstring smileyText = newMsg.substr(startChar, size);
				smileyMsg += _T("<img class=smiley src=\"");
				smileyMsg += folderName;
				smileyMsg += _T("\\");
				smileyMsg += smileyName;
				smileyMsg += _T("\" alt=\"");
				smileyMsg += smileyText;
				smileyMsg += _T("\"/>");
			}

			// Get next
			last_pos = endChar;
		}

		// Add rest of text
		if (last_pos < newMsg.length())
		{
			smileyMsg += newMsg.substr(last_pos);
		}

		CallService(MS_SMILEYADD_BATCHFREE, 0, (LPARAM)spr);
		return UrlHighlightHtml(smileyMsg, isUrl);
	}
	else
	{
		return UrlHighlightHtml(MakeTextHtmled(msg), isUrl);
	}
}