//	Album List for Winamp
//	Copyright (C) 1999-2001 Safai Ma
//
//	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; either version 2 of the License, or
//	(at your option) any later version.
//
//	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, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include <stdlib.h>
#include "util.h"

//////////////////////////////////////////////////////////////////
// CPtrArray class

CPtrArray::CPtrArray()
{
	m_nAllocated = 0;
	m_nGrowBy = 16;
	m_hData = NULL;
	m_pData = NULL;

	m_nSize = 0;
}

CPtrArray::~CPtrArray()
{
	if (m_pData) GlobalUnlock(m_hData);
	if (m_hData) GlobalFree(m_hData);
}

int CPtrArray::GetSize()
{
	return m_nSize;
}

void CPtrArray::SetSize( int nNewSize, int nGrowBy /*=-1*/)
{
	if (nNewSize == 0)
	{
		RemoveAll();
		if (nGrowBy > 0) m_nGrowBy = nGrowBy;
		return;
	}
	// array already allocated?
	else if (m_pData)
	{
		GlobalUnlock(m_hData);
		m_pData = NULL;

		HGLOBAL hData = GlobalReAlloc(m_hData, nNewSize*sizeof(LPVOID), GMEM_ZEROINIT);
		if (hData != NULL) m_hData = hData;
	}
	else
	{
		m_hData = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, nNewSize*sizeof(LPVOID));
	}

	// lock the handle to get the pointer
	m_pData = (LPVOID*)GlobalLock(m_hData);

	// update some internal variables
	m_nAllocated = nNewSize;
	if (nGrowBy > 0) m_nGrowBy = nGrowBy;

	// reducing the size? update the number of valid elements
	if (m_nSize > nNewSize) m_nSize = nNewSize;
}

int CPtrArray::Add(LPVOID pItem)
{
	// allocate memory if neccessary
	if (m_nAllocated <= m_nSize)
	{
		SetSize(m_nAllocated + m_nGrowBy);
	}

	m_pData[m_nSize] = pItem;
	m_nSize++;

	return m_nSize;
}

void CPtrArray::SetAt(int nIndex, LPVOID pItem)
{
	if (nIndex < 0) return;
	if (m_nSize < nIndex + 1) return;

	m_pData[nIndex] = pItem;
	if (m_nSize <= nIndex) m_nSize = nIndex + 1;
}

LPVOID CPtrArray::GetAt(int nIndex)
{
	if (nIndex < 0) return NULL;
	if (m_nSize < nIndex + 1) return NULL;

	return m_pData[nIndex];
}

void CPtrArray::Swap(int from, int to)
{
	if (m_nSize <= from) return;
	if (m_nSize <= to) return;

	LPVOID temp		= m_pData[to];
	m_pData[to]		= m_pData[from];
	m_pData[from]	= temp;
}

void CPtrArray::InsertAt(int nIndex, LPVOID pItem)
{
	if (nIndex < 0) return;
	if (m_nSize < nIndex) return;

	// allocate memory if neccessary
	if (m_nAllocated <= m_nSize)
	{
		SetSize(m_nAllocated + m_nGrowBy);
	}

	memmove(&m_pData[nIndex+1], &m_pData[nIndex], sizeof(LPVOID)*(m_nSize - nIndex));
	m_pData[nIndex] = pItem;

	m_nSize ++;
}

void CPtrArray::RemoveAt(int nIndex, int nCount /*=1*/)
{
	if (nIndex < 0) return;
	if (m_nSize < nIndex + 1) return;

	if (m_nSize >= nIndex + nCount)
	{
		memmove(&m_pData[nIndex], &m_pData[nIndex+nCount], sizeof(LPVOID)*nCount);
	}

	m_nSize -= nCount;
}

void CPtrArray::RemoveAll()
{
	if (m_pData) GlobalUnlock(m_hData);
	if (m_hData) GlobalFree(m_hData);

	m_nAllocated = 0;
	m_hData = NULL;
	m_pData = NULL;
	m_nSize = 0;
}

LPVOID CPtrArray::operator[](int nIndex)
{
	if (nIndex < 0) return NULL;
	if (nIndex < m_nSize) return (LPVOID)m_pData[nIndex];

	return NULL;
}

LPVOID CPtrArray::operator[](int nIndex) const
{
	if (nIndex < 0) return NULL;
	if (nIndex < m_nSize) return (LPVOID)m_pData[nIndex];

	return NULL;
}

LPVOID *CPtrArray::GetData()
{
	return m_pData;
}

//////////////////////////////////////////////////////////////////
// CStringArray class

CStringArray::CStringArray() : CPtrArray()
{
}

CStringArray::~CStringArray()
{
	RemoveAll();
}

int CStringArray::Add(LPCTSTR szItem)
{
	int len = lstrlen(szItem);

	char *str = new char [len+1];
	lstrcpy(str, szItem);

	return CPtrArray::Add(str);
}

void CStringArray::SetAt(int nIndex, LPCTSTR szItem)
{
	int len = lstrlen(szItem);

	char *str = new char [len+1];
	lstrcpy(str, szItem);

	CPtrArray::SetAt(nIndex, str);
}

void CStringArray::InsertAt(int nIndex, LPCTSTR szItem)
{
	int len = lstrlen(szItem);

	char *str = new char [len+1];
	lstrcpy(str, szItem);

	CPtrArray::InsertAt(nIndex, str);
}

void CStringArray::RemoveAt(int nIndex, int nCount /*=1*/)
{
	for (int i=nIndex; i<nIndex+nCount; i++)
	{
		char *str = (char*)GetAt(i);
		if (str) delete [] str;
	}

	CPtrArray::RemoveAt(nIndex, nCount);
}

void CStringArray::RemoveAll()
{
	for (int i=0; i<m_nSize; i++)
	{
		delete [] (char*)GetAt(i);
	}

	CPtrArray::RemoveAll();
}

LPCTSTR CStringArray::operator[](int nIndex)
{
	return (LPCTSTR)CPtrArray::GetAt(nIndex);
}

//////////////////////////////////////////////////////////////////
// CTokens class

CTokens::CTokens(LPCTSTR str, LPCTSTR seps) : CPtrArray()
{
	lstrcpyn(string, str, MAX_PATH);

	// put the tokens into a pointer array
	char *token = strtok(string, seps);
	while (token != NULL)
	{
		Add(token);
		token = strtok(NULL, seps);
	}
}

CTokens::~CTokens()
{
}

LPCTSTR CTokens::operator[](int nIndex)
{
	return (LPCTSTR)GetAt(nIndex);
}

//////////////////////////////////////////////////////////////////
// CTextFile class

CTextFile::CTextFile(LPCTSTR lpszFilename, UINT nOpenFlags)
{
	m_pStream = NULL;

	m_hFile = CreateFile(lpszFilename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	{
	}
}

CTextFile::~CTextFile()
{
}

void CTextFile::WriteString(LPCTSTR lpsz)
{
}

LPTSTR CTextFile::ReadString(LPTSTR lpsz, UINT nMax)
{
	return lpsz;
}

UINT CTextFile::Read(void* lpBuf, UINT nCount)
{
	return 0;
}

void CTextFile::Write(const void* lpBuf, UINT nCount)
{
}

void CTextFile::Flush()
{
}

void CTextFile::Close()
{
	if (m_pStream)
		fclose(m_pStream);

	m_pStream = NULL;
	m_hFile	= NULL;
}

//////////////////////////////////////////////////////////////////
// CTemplateFile class

CTemplateFile::CTemplateFile()
{
}

CTemplateFile::~CTemplateFile()
{
	m_ReplaceMap.RemoveAll();
}

void CTemplateFile::AddReplace(LPCTSTR lpsz1, LPCTSTR lpsz2)
{
	if (lpsz1 == NULL) return;
	if (lpsz2 == NULL) return;

	m_ReplaceMap.SetAt(lpsz1, lpsz2);
}

void CTemplateFile::AddReplace(LPCTSTR lpsz1, int index, LPCTSTR lpsz2)
{
	if (lpsz1 == NULL) return;
	if (lpsz2 == NULL) return;

	char str[MAX_PATH];
	wsprintf(str, "%s%ld", lpsz1, index);

	m_ReplaceMap.SetAt(str, lpsz2);
}

void CTemplateFile::AddReplace(LPCTSTR lpsz, int nNum)
{
	if (lpsz == NULL) return;

	char str[64] = "";
	itoa(nNum, str, 10);

	m_ReplaceMap.SetAt(lpsz, str);
}

void CTemplateFile::AddReplace(LPCTSTR lpsz, int index, int nNum)
{
	if (lpsz == NULL) return;

	char str[64] = "";
	itoa(nNum, str, 10);

	char str2[MAX_PATH];
	wsprintf(str2, "%s%ld", lpsz, index);

	m_ReplaceMap.SetAt(str2, str);
}

void CTemplateFile::AddReplace(LPCTSTR lpsz, COLORREF cr)
{
	if (lpsz == NULL) return;

	char str[MAX_PATH];
	wsprintf(str, "#%02x%02x%02x", GetRValue(cr), GetGValue(cr), GetBValue(cr));

	m_ReplaceMap.SetAt(lpsz, str);
}

BOOL CTemplateFile::Convert(LPCTSTR lpszOutput, LPCTSTR lpszInput)
{
	HANDLE hIn = NULL;
	HANDLE hOut = NULL;

	char tempstr[8] = "";

	char *buffer = NULL;
	char *matchstr = NULL;

	ENSURE
	{
		hIn = CreateFile(lpszInput, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
		if (hIn == INVALID_HANDLE_VALUE) FAIL;

		hOut = CreateFile(lpszOutput, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
		if (hOut == INVALID_HANDLE_VALUE) FAIL;

		DWORD dwFileSize = GetFileSize(hIn, NULL);

		buffer = new char [dwFileSize];
		if (buffer == NULL) FAIL;

		matchstr = new char [256];
		if (matchstr == NULL) FAIL;

		DWORD nBytesRead = 0;
		DWORD nBytesWritten = 0;

		BOOL bResult = ReadFile(hIn, buffer, dwFileSize, &nBytesRead, NULL);
		if (!bResult) FAIL;

		int nLastWrote = 0;
		BOOL bInList = FALSE;
		int nListStartIndex = 0;
		int nLoopIndex = 0;
		BOOL bMatchFoundInList = FALSE;
		DWORD dwCurPos = 0;
		for (int i=0; i<(int)nBytesRead; i++)
		{
			// find the tag "<%"
			if ((buffer[i] == '<') &&
				(buffer[i+1] == '%'))
			{
				// write out whatever we have first
				if (i != nLastWrote)
				{
					WriteFile(hOut, &buffer[nLastWrote], i-nLastWrote, &nBytesWritten, NULL);
					nLastWrote = i;
				}

				// find the ending "%>
				for (int j=i+2; j<(int)nBytesRead; j++)
				{
					if ((buffer[j] == '%') &&
						(buffer[j+1] == '>'))
					{
						if (memcmp("liststart", &buffer[i+2], min(9, j-(i+2))) == 0)
						{
							i = j+2;
							nLastWrote = i;
							bInList = TRUE;
							nListStartIndex = i;
							nLoopIndex = 0;
							bMatchFoundInList = FALSE;
							dwCurPos = SetFilePointer(hOut, 0, NULL, FILE_CURRENT);
							break;
						}
						else if (memcmp("listend", &buffer[i+2], min(7, j-(i+2))) == 0)
						{
							if (bInList)
							{
								if (bMatchFoundInList)
								{
									i = nListStartIndex;
									nLastWrote = i;
									nLoopIndex++;
									bMatchFoundInList = FALSE;
									dwCurPos = SetFilePointer(hOut, 0, NULL, FILE_CURRENT);
								}
								else
								{
									i = j+2;
									nLastWrote = i;
									bInList = FALSE;
									SetFilePointer(hOut, dwCurPos, NULL, FILE_BEGIN);
								}
								break;
							}
						}

						int matchstrlen = j-(i+2);
						lstrcpyn(matchstr, &buffer[i+2], matchstrlen+1);

						if (bInList)
						{
							lstrcat(matchstr, itoa(nLoopIndex, tempstr, 10));
							matchstrlen = lstrlen(matchstr);
						}

						// match the replacement string then
						char *replacement = NULL;
						if (m_ReplaceMap.Lookup(matchstr, replacement))
						{
							WriteFile(hOut, replacement, lstrlen(replacement), &nBytesWritten, NULL);

							// update i and stuff
							i = j+2;
							nLastWrote = i;
							bMatchFoundInList = TRUE;
						}
						else
						{
							i = j+2;
						}
						break;
					}
				}
			}
		}

		// write the remaining stuff
		if (nLastWrote != (int)nBytesRead)
		{
			WriteFile(hOut, &buffer[nLastWrote], nBytesRead-nLastWrote, &nBytesWritten, NULL);
		}

		// flush the buffer to disk first
		FlushFileBuffers(hOut);

		// close handles
		CloseHandle(hIn);
		CloseHandle(hOut);

		// clean up
		if (buffer) delete [] buffer;
		if (matchstr) delete [] matchstr;

		return TRUE;
	}
	END_ENSURE;

	if (hIn) CloseHandle(hIn);
	if (hOut) CloseHandle(hOut);
	if (buffer) delete [] buffer;
	if (matchstr) delete [] matchstr;

	return FALSE;
}

//////////////////////////////////////////////////////////////////
// CMapStringToString class

CMapStringToString::CMapStringToString()
{
	m_pHashTable	 = NULL;
	m_nHashTableSize = 64;
	m_nCount		 = 0;
}

CMapStringToString::~CMapStringToString()
{
	RemoveAll();
}

int	CMapStringToString::GetCount() const
{
	return m_nCount;
}

BOOL CMapStringToString::IsEmpty() const
{
	return m_nCount == 0;
}

void CMapStringToString::InitHashTable(UINT nHashSize, BOOL bAllocNow)
{
	if (m_pHashTable != NULL)
	{
		// free hash table
		delete[] m_pHashTable;
		m_pHashTable = NULL;
	}

	if (bAllocNow)
	{
		m_pHashTable = new CAssoc* [nHashSize];
		memset(m_pHashTable, 0, sizeof(CAssoc*) * nHashSize);
	}
	m_nHashTableSize = nHashSize;
}

void CMapStringToString::SetAt(LPCTSTR key, LPCTSTR newValue)
{
	UINT nHash;
	CAssoc* pAssoc;
	if ((pAssoc = GetAssocAt(key, nHash)) == NULL)
	{
		if (m_pHashTable == NULL)
			InitHashTable(m_nHashTableSize);

		// it doesn't exist, add a new Association
		pAssoc = new CAssoc(key, newValue);

		// put into hash table
		pAssoc->pNext = m_pHashTable[nHash];
		m_pHashTable[nHash] = pAssoc;

		m_nCount++;
	}
}

BOOL CMapStringToString::Lookup(LPCTSTR key, LPTSTR &rValue) const
{
	UINT nHash;
	CAssoc* pAssoc = GetAssocAt(key, nHash);
	if (pAssoc == NULL)
		return FALSE;  // not in map

	rValue = pAssoc->value;
	return TRUE;
}

BOOL CMapStringToString::RemoveKey(LPCTSTR key)
{
	if (m_pHashTable == NULL)
		return FALSE;  // nothing in the table

	CAssoc** ppAssocPrev;
	ppAssocPrev = &m_pHashTable[HashKey(key) % m_nHashTableSize];

	CAssoc* pAssoc;
	for (pAssoc = *ppAssocPrev; pAssoc != NULL; pAssoc = pAssoc->pNext)
	{
		if (pAssoc->key == key)
		{
			// remove it
			*ppAssocPrev = pAssoc->pNext;  // remove from list
			delete pAssoc;
			m_nCount--;
			return TRUE;
		}
		ppAssocPrev = &pAssoc->pNext;
	}
	return FALSE;  // not found
}

void CMapStringToString::RemoveAll()
{
	if (m_pHashTable != NULL)
	{
		// destroy elements
		for (UINT nHash = 0; nHash < m_nHashTableSize; nHash++)
		{
			CAssoc* pAssoc = m_pHashTable[nHash];
			while (pAssoc)
			{
				CAssoc*old = pAssoc;
				pAssoc = pAssoc->pNext;
				delete old;
			}
		}

		// free hash table
		delete [] m_pHashTable;
		m_pHashTable = NULL;
	}

	m_nCount = 0;
}

UINT CMapStringToString::HashKey(LPCTSTR key) const
{
	UINT nHash = 0;
	while (*key)
		nHash = (nHash<<5) + nHash + *key++;
	return nHash;
}

CMapStringToString::CAssoc::CAssoc(LPCTSTR k, LPCTSTR v)
{
	pNext = NULL;

	key = new char [lstrlen(k)+1];
	value = new char [lstrlen(v)+1];

	lstrcpy(key, k);
	lstrcpy(value, v);
}

CMapStringToString::CAssoc::~CAssoc()
{
	if (key) delete [] key;
	if (value) delete [] value;
}

CMapStringToString::CAssoc* CMapStringToString::GetAssocAt(LPCTSTR key, UINT &nHash) const
{
	nHash = HashKey(key) % m_nHashTableSize;

	if (m_pHashTable == NULL)
		return NULL;

	// see if it exists
	CAssoc* pAssoc;
	for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL; pAssoc = pAssoc->pNext)
	{
		if (lstrcmp(pAssoc->key, key) == 0)
			return pAssoc;
	}
	return NULL;
}
