//	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 <stdio.h>
#include <stdlib.h>
#include <direct.h>
#include <search.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <mbstring.h>
#include "resource.h"
#include "albumlist.h"
#include "frontend.h"
#include "wa_ipc.h"
#include "gen.h"
#include "config.h"
#include "fileinfo.h"
#include "CMP3Info.h"
#include "CMPCInfo.h"
#include "CSPCInfo.h"
#include "CPSFInfo.h"
#include "CWMAInfo.h"
#include "CAPEInfo.h"
#include "CWAVInfo.h"
#include "util.h"

#define ENSURE			do
#define FAIL			break
#define END_ENSURE		while (FALSE)

static char gszArtist[128] = "";
static char gszAlbumName[128] = "";
static int gnAlbumIndex = -1;

extern winampGeneralPurposePlugin plugin;

void InitAlbumInfo(LPALBUMINFO pAlbumInfo)
{
	memset(pAlbumInfo, 0, sizeof(ALBUMINFO));
}

static CRITICAL_SECTION	myCriticalSection;
DWORD WINAPI CAlbumList::ThreadProc(LPVOID lpParameter)
{
	CAlbumList *pAlbumList = (CAlbumList *)lpParameter;

	while (WaitForSingleObject(pAlbumList->m_hEvent, INFINITE) == WAIT_OBJECT_0)
	{
		while (TRUE)
		{
			// update link list
			EnterCriticalSection(&myCriticalSection);
			LPSCANDIR pScanDir = pAlbumList->m_pScanDir;
			if (pAlbumList->m_pScanDir == NULL)
			{
				LeaveCriticalSection(&myCriticalSection);
				break;
			}
			pAlbumList->m_pScanDir = pAlbumList->m_pScanDir->pNext;
			LeaveCriticalSection(&myCriticalSection);

			switch (pScanDir->nAction)
			{
			case 0:		// add
				pAlbumList->AddAlbumsToList(pScanDir->path, TRUE);
				break;

			case 1:		// remove
				pAlbumList->RemoveAlbumsFromList(pScanDir->path);
				break;

			case 2:		// remove old
				pAlbumList->RemoveOldAlbumsFromList();
				break;

			case 3:		// add CD
				if (pAlbumList->AddAlbumsToList(pScanDir->path, TRUE))
				{
#if 0
					// write to CD cache
					DWORD dwVolumeSerialNumber;
					if (GetVolumeInformation(pScanDir->path, NULL, 0, &dwVolumeSerialNumber, NULL, NULL, NULL, NULL))
					{
						OutputDebugString("TTEST");
					}
#endif
 				}
				break;

			case 4:		// remove not in search path
				pAlbumList->RemoveAlbumsNotInSearchPath();
				break;

			case 5:		// refresh 1 album
				pAlbumList->RefreshAlbumThread(pScanDir->pAlbumInfo);
				break;
			}

			delete pScanDir;

			pAlbumList->m_bStopScanning = FALSE;

			RedrawWindow(pAlbumList->m_hWnd, NULL, NULL, RDW_INVALIDATE);
		}
	}

	return 0;
}

//////////////////////////////////////////////////////////////////
// CAlbumList class

CAlbumList::CAlbumList()
{
	m_nTotalTime			= 0;
	m_nRootLen				= 0;
	m_szCurrentDir[0]		= 0;
	m_pScanDir				= NULL;
	m_bStopScanning			= FALSE;
	m_dwCDROMs				= 0;

	// stuff for the scanning thread
	m_hEvent				= CreateEvent(NULL, FALSE, FALSE, NULL);
	m_hThread				= CreateThread(NULL, 0, ThreadProc, this, 0, &m_dwThreadId);
	SetThreadPriority(m_hThread, THREAD_PRIORITY_BELOW_NORMAL);
}

CAlbumList::~CAlbumList()
{
	CloseHandle(m_hEvent);
	CloseHandle(m_hThread);
}

void CAlbumList::Startup(HWND hWnd)
{
	m_hWnd = hWnd;

	char szDrive[8] = "C:\\";
	m_dwCDROMs = GetLogicalDrives();
	for (int i=0; i<26; i++)
	{
		if ((m_dwCDROMs >> i) & 1)
		{
			szDrive[0] = 'A' + i;
			if (GetDriveType(szDrive) != DRIVE_CDROM)
				m_dwCDROMs &= ~(1 << i);
		}
	}

	LoadExternalFileReader();

	InitializeCriticalSection(&myCriticalSection);
}

void CAlbumList::Refresh()
{
}

void CAlbumList::Shutdown()
{
	int size = m_AlbumList.GetSize();
	for (int i=0; i<size; i++)
	{
		delete m_AlbumList[i];
	}
	m_AlbumList.RemoveAll();

	m_nTotalTime = 0;

	DeleteCriticalSection(&myCriticalSection);

	UnloadExternalFileReader();
}

BOOL CAlbumList::PathExists(char *path)
{
	int size = m_AlbumList.GetSize();
	for (int i=0; i<size; i++)
	{
		LPALBUMINFO pAlbumInfo = m_AlbumList[i];
		if (lstrcmpi(path, pAlbumInfo->path) == 0) return TRUE;
	}

	return FALSE;
}

BOOL CAlbumList::IsCDROM(LPCTSTR path)
{
	int nDrive = toupper(path[0]) - 'A';
	return ((m_dwCDROMs & (1 << nDrive)) != 0);
}

BOOL CAlbumList::M3UExists(char *path, char *filename)
{
	char path1[MAX_PATH];

	int size = m_AlbumList.GetSize();
	for (int i=0; i<size; i++)
	{
		LPALBUMINFO pAlbumInfo = m_AlbumList[i];

		wsprintf(path1, "%s\\%s", path, filename);
		if (lstrcmpi(path1, pAlbumInfo->m3u_path) == 0) return TRUE;
	}

	return FALSE;
}

void CAlbumList::RemoveOldAlbumsFromList()
{
	int size = m_AlbumList.GetSize();
	for (int i=0; i<size; i++)
	{
		LPALBUMINFO pAlbumInfo = m_AlbumList[i];

		struct _stat buf1;
		if (_stat(pAlbumInfo->path, &buf1) != 0)
		{	// error occurs, directory not found
			// set the path to NULL and we will remove it from later
			lstrcpy(pAlbumInfo->path, "");
		}
	}

	// remove all the old ones
	LPVOID *pData = m_AlbumList.GetData();
	int j=0;
	for (i=0; i<size; i++)
	{
		LPALBUMINFO pAlbumInfo = m_AlbumList[i];

		if (lstrlen(pAlbumInfo->path) != 0)
		{
			if (i != j)
			{
				memcpy(&pData[j], &pData[i], sizeof(LPVOID));
			}
			j++;
		}
		else
		{
			m_nTotalTime -= pAlbumInfo->nTotalTime;
		}
	}
	m_AlbumList.SetSize(j);

	RedrawWindow(m_hWnd, NULL, NULL, RDW_INVALIDATE);
}

void CAlbumList::StopScanning(char *path)
{
	EnterCriticalSection(&myCriticalSection);
	SuspendThread(m_hThread);
	LeaveCriticalSection(&myCriticalSection);

	// remove any that is queued
	if (m_pScanDir)
	{
		if (lstrcmpi(m_pScanDir->path, path) == 0)
		{
			if (m_pScanDir->nAction == 0)
			{
				LPSCANDIR pScanDir = m_pScanDir;
				m_pScanDir = m_pScanDir->pNext;
				delete pScanDir;
			}
		}
		else
		{
			LPSCANDIR pScanDirPrev = m_pScanDir;
			LPSCANDIR pScanDir = m_pScanDir->pNext;

			while (pScanDir)
			{
				if (lstrcmpi(pScanDir->path, path) == 0)
				{
					if (pScanDir->nAction == 0)
					{
						pScanDirPrev->pNext = pScanDir->pNext;
						delete pScanDir;
					}
				}
				else
				{
					pScanDirPrev = pScanDir;
				}

				pScanDir = pScanDir->pNext;
			}
		}
	}

	if (lstrcmpi(m_szCurrentDir, path) == 0)
	{
		m_bStopScanning = TRUE;
	}
	ResumeThread(m_hThread);

	while (m_bStopScanning) Sleep(100);
}

BOOL CAlbumList::AddAlbumsToList(char *path, BOOL bSearchRoot)
{
	BOOL bFound = FALSE;
	WIN32_FIND_DATA FindFileData;
	HANDLE hFile;

	// strip the '\' at the back if present
	int len = lstrlen(path);
	if (len < 2) return TRUE;
	char *p = (char *)_mbsrchr((unsigned char *)path,'\\');
	if (p)
	{
		if (&path[len-1] == p)
		{
			path[--len] = '\0';
		}
	}

	char pathlwr[MAX_PATH] = "";
	lstrcpy(pathlwr, path);
	if (_mbsstr(_mbslwr((unsigned char *)pathlwr), (const unsigned char*)":\\recycled") != NULL) return TRUE;

	BOOL bComplete = TRUE;

	// save and change directory
	char oldPath[MAX_PATH] = "";
	GetCurrentDirectory(MAX_PATH, oldPath);

	if (bSearchRoot)
	{
		m_bStopScanning = FALSE;
		m_nRootLen = lstrlen(path);
		lstrcpy(m_szCurrentDir, path);
	}

	ENSURE
	{
		// ignore M3U at root of search path
		if (!al_ignore_m3u && (!bSearchRoot || !al_ignore_pl))
		{
			// find the first playlist file (*.m3u) in this directory if it exists
			char m3upath[256];
			lstrcpy(m3upath, path);
			lstrcat(m3upath, "\\*.m3u");

			if ((hFile = FindFirstFile(m3upath, &FindFileData)) != INVALID_HANDLE_VALUE)
			{
				if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - 3, "m3u") == 0)
				{
					ALBUMINFO albumInfo;
					InitAlbumInfo(&albumInfo);

					if (M3UExists(path, FindFileData.cFileName))
					{
						bFound = TRUE;
					}
					else if (GetAlbumInfoFromM3U(path, FindFileData.cFileName, &albumInfo))
					{
						AddAlbum(&albumInfo);
						bFound = TRUE;
					}
				}

				// try to get the album from the rest of the .mp3 files
				// until we find the album name
				// get the file length of all the files
				while (FindNextFile(hFile, &FindFileData) != 0)
				{
					if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - 3, "m3u") == 0)
					{
						ALBUMINFO albumInfo;
						InitAlbumInfo(&albumInfo);

						if (m_bStopScanning)
						{
							bComplete = FALSE;
							FAIL;
						}

						if (M3UExists(path, FindFileData.cFileName))
						{
							bFound = TRUE;
						}
						else if (GetAlbumInfoFromM3U(path, FindFileData.cFileName, &albumInfo))
						{
							AddAlbum(&albumInfo);
							bFound = TRUE;
						}
					}
				}

				FindClose(hFile);
			}
		}

		// found! check sub-directories
		if (PathExists(path))
		{
			bFound = TRUE;
		}

		if (m_bStopScanning)
		{
			bComplete = FALSE;
			FAIL;
		}

		// check for MP3 files in this directory
		if (!bFound)
		{
			char mypath[256];

			CTokens extensions(al_extlist, " ?,\"'~!.-:;\t\n");

			ALBUMINFO albumInfo;
			InitAlbumInfo(&albumInfo);

			lstrcpyn(albumInfo.path, path, MAX_PATH);

			BOOL bAlbumAdded = FALSE;

			// for all the extensions we have
			int size = extensions.GetSize();
			for (int i=0; i<size; i++)
			{
				// find all the *.mp3 in this directory
				lstrcpy(mypath, path);
				lstrcat(mypath, "\\*.");
				lstrcat(mypath, extensions[i]);

				int nExtLen = lstrlen(extensions[i]);

				if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
				{
					if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
					{
						// get file length (in seconds)
						GetAlbumInfo(path, FindFileData.cFileName, &albumInfo);
						bAlbumAdded = TRUE;
					}

					// try to get the album from the rest of the .mp3 files
					// until we find the album name
					// get the file length of all the files
					while (FindNextFile(hFile, &FindFileData) != 0)
					{
						if (m_bStopScanning)
						{
							bComplete = FALSE;
							break;
						}

						if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
						{
							GetAlbumInfo(path, FindFileData.cFileName, &albumInfo);
						}
					}

					FindClose(hFile);
				}

				if (m_bStopScanning)
				{
					bComplete = FALSE;
					break;
				}
			}

			if (m_bStopScanning)
			{
				bComplete = FALSE;
				FAIL;
			}

			// add to database
			if (bAlbumAdded)
			{
				CreateAlbumDisplayName(path, &albumInfo);
				AddAlbum(&albumInfo);
			}
		}

		if (m_bStopScanning)
		{
			bComplete = FALSE;
			FAIL;
		}

		// check all sub-directories
		if (al_searchsubdir)
		{
			char mypath[256];
			lstrcpy(mypath, path);
			lstrcat(mypath, "\\*.*");
			if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
			{
				if (!CheckSubDirectories(path, &FindFileData))
				{
					bComplete = FALSE;
				}

				while (FindNextFile(hFile, &FindFileData) != 0)
				{
					if (m_bStopScanning)
					{
						bComplete = FALSE;
						FAIL;
					}

					if (!CheckSubDirectories(path, &FindFileData))
					{
						bComplete = FALSE;
					}
				}
				FindClose(hFile);
			}
		}
	}
	END_ENSURE;

	if (bSearchRoot)
	{
		m_bStopScanning = FALSE;
		m_nRootLen = 0;
		m_szCurrentDir[0] = 0;
	}

	// restore directory
	SetCurrentDirectory(oldPath);

	return bComplete;
}

void CAlbumList::RefreshAlbum(LPALBUMINFO album)
{
	LPSCANDIR pScanDir = new SCANDIR;
	pScanDir->pAlbumInfo = album;
	pScanDir->nAction = 5;
	pScanDir->pNext = NULL;
	
	EnterCriticalSection(&myCriticalSection);
	if (m_pScanDir)
	{
		LPSCANDIR ptr = m_pScanDir;
		while (ptr->pNext)
		{
			ptr = ptr->pNext;
		}
		ptr->pNext = pScanDir;
	}
	else
	{
		m_pScanDir = pScanDir;
	}
	LeaveCriticalSection(&myCriticalSection);

	SetEvent(m_hEvent);
}

void CAlbumList::RefreshAlbumThread(LPALBUMINFO album)
{
	if (album == NULL) return;

	char path[MAX_PATH] = "";
	lstrcpy(path, album->path);

	ALBUMINFO albuminfo;
	InitAlbumInfo(&albuminfo);

	// this is a playlist file
	if (lstrlen(album->m3u_path))
	{
		char name[MAX_PATH] = "";
		lstrcpy(name, album->m3u_path + lstrlen(path) + 1);
			
		GetAlbumInfoFromM3U(path, name, &albuminfo);
	}
	else
	{
		char mypath[256];

		CTokens extensions(al_extlist, " ?,\"'~!.-:;\t\n");

		// for all the extensions we have
		int size = extensions.GetSize();
		for (int i=0; i<size; i++)
		{
			// find all the *.mp3 in this directory
			lstrcpy(mypath, path);
			lstrcat(mypath, "\\*.");
			lstrcat(mypath, (char*)extensions[i]);

			lstrcpyn(albuminfo.path, path, MAX_PATH);

			WIN32_FIND_DATA FindFileData;
			HANDLE hFile;

			int nExtLen = lstrlen(extensions[i]);

			// check for MP3 files in this directory
			if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
			{
				lstrcpyn(album->path, path, MAX_PATH);

				if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
				{
					// get file length (in seconds)
					GetAlbumInfo(path, FindFileData.cFileName, &albuminfo);
				}

				// try to get the album from the rest of the .mp3 files
				// until we find the album name
				// get the file length of all the files
				while (FindNextFile(hFile, &FindFileData) != 0)
				{
					if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
					{
						GetAlbumInfo(path, FindFileData.cFileName, &albuminfo);
					}
				}

				FindClose(hFile);
			}
		}

		CreateAlbumDisplayName(path, &albuminfo);
	}

	memcpy(album, &albuminfo, sizeof(ALBUMINFO));

	RedrawWindow(m_hWnd, NULL, NULL, RDW_INVALIDATE);
}

void CAlbumList::AddMP3ToList(char *path, BOOL bCD /*=FALSE*/)
{
	LPSCANDIR pScanDir = new SCANDIR;
	lstrcpy(pScanDir->path, path);
	pScanDir->nAction = bCD ? 3 : 0;
	pScanDir->pNext = NULL;
	
	EnterCriticalSection(&myCriticalSection);
	if (m_pScanDir)
	{
		LPSCANDIR ptr = m_pScanDir;
		while (ptr->pNext)
		{
			ptr = ptr->pNext;
		}
		ptr->pNext = pScanDir;
	}
	else
	{
		m_pScanDir = pScanDir;
	}
	LeaveCriticalSection(&myCriticalSection);

	SetEvent(m_hEvent);
}

void CAlbumList::AddMP3CDsToList()
{
	// go through all drives
	char szDrive[8] = "C:\\";
	for (int i=0; i<26; i++)
	{
		if ((m_dwCDROMs >> i) & 1)
		{
			int curdrive = _getdrive();
			if (_chdrive(i+1) == 0)
			{
				szDrive[0] = 'A' + i;
				AddMP3ToList(szDrive, TRUE);
			}
			_chdrive(curdrive);
		}
	}
}

void CAlbumList::RemoveMP3FromList(char *path)
{
	LPSCANDIR pScanDir = new SCANDIR;
	lstrcpy(pScanDir->path, path);
	pScanDir->nAction = 1;
	pScanDir->pNext = NULL;
	
	EnterCriticalSection(&myCriticalSection);
	if (m_pScanDir)
	{
		LPSCANDIR ptr = m_pScanDir;
		while (ptr->pNext)
		{
			ptr = ptr->pNext;
		}
		ptr->pNext = pScanDir;
	}
	else
	{
		m_pScanDir = pScanDir;
	}
	LeaveCriticalSection(&myCriticalSection);

	SetEvent(m_hEvent);
}

void CAlbumList::RemoveMP3NotInSearchPath()
{
	LPSCANDIR pScanDir = new SCANDIR;
	pScanDir->nAction = 4;
	pScanDir->pNext = NULL;
	
	EnterCriticalSection(&myCriticalSection);
	if (m_pScanDir)
	{
		LPSCANDIR ptr = m_pScanDir;
		while (ptr->pNext)
		{
			ptr = ptr->pNext;
		}
		ptr->pNext = pScanDir;
	}
	else
	{
		m_pScanDir = pScanDir;
	}
	LeaveCriticalSection(&myCriticalSection);

	SetEvent(m_hEvent);
}

void CAlbumList::RemoveAlbumsNotInSearchPath()
{
	// get number of search pathes
	int i=0, count=0;
	char paths[40] = "al_path";
	char al_path[MAX_PATH] = "";
	while (1)
	{
		DWORD dwRet = GetPrivateProfileString(szAppName, paths, "", al_path, MAX_PATH, szPluginINI);
		if (dwRet == 0 && i != 0)	break;
		if (lstrlen(al_path))
		{
			count++;
		}
		wsprintf(paths, "al_path_%ld", i++);
	}
	// get search paths for CDs
	if (al_add_cdroms)
	{
		// go through all drives
		for (int i=0; i<26; i++)
		{
			if ((m_dwCDROMs >> i ) & 1)
			{
				int curdrive = _getdrive();
				if (_chdrive(i+1) == 0)
				{
					count++;
				}
				_chdrive(curdrive);
			}
		}
	}

	// no search path, no albums
	if (count == 0)
	{
		int size = m_AlbumList.GetSize();
		for (int i=0; i<size; i++)
		{
			delete m_AlbumList[i];
		}
		m_AlbumList.RemoveAll();

		m_nTotalTime = 0;
		return;
	}
	else
	{
		char *searchpath = new char [count*MAX_PATH];
		int index = 0;

		int i=0;
		char paths[40] = "al_path";
		char al_path[MAX_PATH] = "";
		while (1)
		{
			DWORD dwRet = GetPrivateProfileString(szAppName, paths, "", al_path, MAX_PATH, szPluginINI);
			if (dwRet == 0 && i != 0)	break;
			if (lstrlen(al_path))
			{
				_mbscpy((unsigned char *)searchpath+MAX_PATH*(index++), _mbslwr((unsigned char *)al_path));
			}
			wsprintf(paths, "al_path_%ld", i++);
		}
		// get search paths for CDs
		if (al_add_cdroms)
		{
			char szDrive[8] = "C:\\";
			// go through all drives
			for (int i=0; i<26; i++)
			{
				if ((m_dwCDROMs >> i ) & 1)
				{
					int curdrive = _getdrive();
					if (_chdrive(i+1) == 0)
					{
						szDrive[0] = 'A' + i;
						_mbscpy((unsigned char *)searchpath+MAX_PATH*(index++), _mbslwr((unsigned char *)szDrive));
					}
					_chdrive(curdrive);
				}
			}
		}

		// check all albums
		LPVOID *pData = m_AlbumList.GetData();
		int size = 0;
		for (i=0; i<m_AlbumList.GetSize(); i++)
		{
			LPALBUMINFO pAlbumInfo = m_AlbumList[i];

			char pathlwr[MAX_PATH] = "";
			lstrcpy(pathlwr, pAlbumInfo->path);

			// check if the path exists in the search path
			BOOL bExists = FALSE;
			for (int j=0; j<count; j++)
			{
				if (_mbsstr(_mbslwr((unsigned char *)pathlwr), (unsigned char*)searchpath+MAX_PATH*j) != NULL)
				{
					// if this is a playlist file
					// we need to make sure that playlist file still exists
					if (lstrlen(pAlbumInfo->m3u_path) != 0)
					{
						struct _stat buf;
						if (_stat(pAlbumInfo->m3u_path, &buf) == 0)
						{
							bExists = TRUE;
						}
					}
					// regular MP3 directory
					// check file count, update if necessary
					else
					{
						int nCount = 0;
						WIN32_FIND_DATA FindFileData;
						HANDLE hFile;
						char mypath[MAX_PATH];

						CTokens extensions(al_extlist, " ?,\"'~!.-:;\t\n");

						int size = extensions.GetSize();
						for (int i=0; i<size; i++)
						{
							// find all the *.mp3 in this directory
							wsprintf(mypath, "%s\\*.%s", pAlbumInfo->path, extensions[i]);

							int nExtLen = lstrlen(extensions[i]);

							if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
							{
								if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
								{
									nCount++;
								}

								/* Find the rest of the .mp3 files */
								while(FindNextFile(hFile, &FindFileData) != 0)
								{
									if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
									{
										nCount++;
									}
								}
								FindClose(hFile);
							}
						}

						if (pAlbumInfo->nCount == nCount)
						{
							bExists = TRUE;
						}
					}
					break;
				}
			}

			if (bExists)
			{
				if (i > size)
				{
					pData[size] = pData[i];
				}

				size ++;
			}
			else
			{
				m_nTotalTime -= pAlbumInfo->nTotalTime;
			}
		}
		
		m_AlbumList.SetSize(size);

		RedrawWindow(m_hWnd, NULL, NULL, RDW_INVALIDATE);

		delete [] searchpath;
	}
}

void CAlbumList::RemoveOMP3FromList()
{
	LPSCANDIR pScanDir = new SCANDIR;
	lstrcpy(pScanDir->path, "");
	pScanDir->nAction = 2;
	pScanDir->pNext = NULL;
	
	EnterCriticalSection(&myCriticalSection);
	if (m_pScanDir)
	{
		LPSCANDIR ptr = m_pScanDir;
		while (ptr->pNext)
		{
			ptr = ptr->pNext;
		}
		ptr->pNext = pScanDir;
	}
	else
	{
		m_pScanDir = pScanDir;
	}
	LeaveCriticalSection(&myCriticalSection);

	SetEvent(m_hEvent);
}

BOOL CAlbumList::GetAlbumInfoFromM3U(char *path, char *name, LPALBUMINFO pAlbumInfo)
{
	if (pAlbumInfo == NULL) return FALSE;

	char pathname[MAX_PATH];
	BOOL bRet = FALSE;

	// change directory
	SetCurrentDirectory(path);

	// create pathname using the path and filename
	lstrcpy(pathname, path);
	lstrcat(pathname, "\\");
	lstrcat(pathname, name);

	lstrcpyn(pAlbumInfo->path, path, MAX_PATH);
	lstrcpyn(pAlbumInfo->m3u_path, pathname, MAX_PATH);

	HANDLE fp;
	if (INVALID_HANDLE_VALUE != (fp = CreateFile(pathname, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))
	{
		DWORD dwSize = GetFileSize (fp, NULL);

		char *buffer = new char [dwSize];

		DWORD nBytesRead;
		BOOL bResult = ReadFile(fp, buffer, dwSize, &nBytesRead, NULL);
		if (bResult)
		{

			// convert all the newline characters to NULL
			// for better processing of lines
			for (DWORD dwByte=0; dwByte<nBytesRead; dwByte++)
			{
				if (buffer[dwByte] == '\n') buffer[dwByte] = 0;
				else if (buffer[dwByte] == '\r') buffer[dwByte] = 0;
			}

			char *ptr = buffer;
			char *ptrend = buffer + nBytesRead;

			BOOL bTrackHasTimeInfo = FALSE;
			while ((ptr + 8) < ptrend)
			{
				char tag[9];
				lstrcpyn(tag, ptr, 9);

				// file header
				// #EXTM3U
				if (0 == lstrcmp(tag, "#EXTM3U"))
				{
				}
				// artist name
				// #EXTART:Vivian Lai
				else if (0 == lstrcmp(tag, "#EXTART:"))
				{
					lstrcpyn(pAlbumInfo->artist, ptr+8, MAX_PATH);
				}
				// album name
				// #EXTALB:On the Sunshine Road
				else if (0 == lstrcmp(tag, "#EXTALB:"))
				{
					lstrcpyn(pAlbumInfo->album, ptr+8, MAX_PATH);
				}
				// file descriptions
				// #EXTINF:225,Vivian Lai - Super Kid
				// for shoutcast streams
				// #EXTINF:-1,Monkey Radio: Grooving sexy beats.
				else if (0 == lstrcmp(tag, "#EXTINF:"))
				{
					char szTime[8] = "";
					char *p = ptr+8;
					while (*p++ != ',') ;
					
					lstrcpyn(szTime, ptr+8, p-(ptr+8));

					if (atoi(szTime) > 0)
					{
						pAlbumInfo->nTotalTime += atoi(szTime);

						bTrackHasTimeInfo = TRUE;
					}
				}
				// filenames
				// c:\mp3\vivianlai\vivianlai01.mp3
				// for shoutcast streams
				// http://216.32.166.79:11280
				else
				{
					// make sure this is not a shoutcast stream
					char szShoutcast[8] = "";
					lstrcpyn(szShoutcast, ptr, 5);
					if (lstrcmpi(szShoutcast, "http://") != 0)
					{
						// we need to parse the MP3 file if
						// there is no time info, or
						// the album/artist is not filled
						if ((bTrackHasTimeInfo == FALSE) ||
							(lstrlen(pAlbumInfo->album) == 0) ||
							(lstrlen(pAlbumInfo->artist) == 0) ||
							(pAlbumInfo->nYear == 0))
						{
							CFileInfo *pFileInfo = CreateFileInfo(ptr);
							if (pFileInfo)
							{
								if (pFileInfo->loadInfo(ptr) == 0)
								{
									if (!bTrackHasTimeInfo)
									{
										pAlbumInfo->nTotalTime += pFileInfo->getLengthInSeconds();
									}

									if (lstrlen(pAlbumInfo->album) == 0)
									{
										pFileInfo->getAlbum(pAlbumInfo->album);
									}

									if (lstrlen(pAlbumInfo->artist) == 0)
									{
										pFileInfo->getArtist(pAlbumInfo->artist);
									}

									if (pAlbumInfo->nYear == 0)
									{
										pAlbumInfo->nYear = pFileInfo->getYear();
									}
								}
								delete pFileInfo;
							}
						}
					}
					pAlbumInfo->nCount++;

					// reset it for next track
					bTrackHasTimeInfo = FALSE;
				}

				// skip this line, find next non-null
				for (ptr += lstrlen(ptr); ((*ptr == 0) && (ptr < ptrend)); ptr++);
			}
		}

		// make sure there is stuff in the M3U file
		// fix for the shoutcast only playlist files
		if (pAlbumInfo->nTotalTime)
		{
			CreateAlbumDisplayName(path, pAlbumInfo);
			bRet = TRUE;
		}

		delete [] buffer;

		// close the open file handle
		CloseHandle(fp);
	}

	return bRet;
}

BOOL CAlbumList::GetAlbumInfo(char *path, char *name, LPALBUMINFO pAlbumInfo)
{
	char pathname[MAX_PATH] = "";
	BOOL bRet = FALSE;

	// create pathname using the path and filename
	if (path != NULL)
	{
		lstrcpy(pathname, path);
		lstrcat(pathname, "\\");
	}
	lstrcat(pathname, name);

	CFileInfo *pFileInfo = CreateFileInfo(pathname);
	if (pFileInfo)
	{
		if (pFileInfo->loadInfo(pathname) == 0)
		{ 
			pAlbumInfo->nTotalTime += pFileInfo->getLengthInSeconds();

			if (lstrlen(pAlbumInfo->album) == 0)
			{
				pFileInfo->getAlbum(pAlbumInfo->album);
			}

			if (lstrlen(pAlbumInfo->artist) == 0)
			{
				pFileInfo->getArtist(pAlbumInfo->artist);
			}

			if (pAlbumInfo->nYear == 0)
			{
				pAlbumInfo->nYear = pFileInfo->getYear();
			}

			bRet = TRUE;
		}

		delete pFileInfo;
	}

	pAlbumInfo->nCount++;

	return bRet;
}

BOOL CAlbumList::CheckSubDirectories(char *path, WIN32_FIND_DATA *file)
{
	if (file == NULL) return TRUE;

	if ((file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && 
		lstrcmp(file->cFileName, ".") && 
		lstrcmp(file->cFileName, ".."))
	{
		char newpath[256];
		lstrcpy(newpath, path);
		lstrcat(newpath, "\\");
		lstrcat(newpath, file->cFileName);

		return AddAlbumsToList(newpath, FALSE);
	}
	else if (strstr(file->cAlternateFileName, ".LNK") != NULL)
	{
		char newpath[256];
		lstrcpy(newpath, path);
		lstrcat(newpath, "\\");
		lstrcat(newpath, file->cFileName);

		if (ResolveIt(m_hWnd, newpath, newpath))
		{
			DWORD dwAttrib = GetFileAttributes(newpath);
			if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)
			{
				return AddAlbumsToList(newpath, FALSE);
			}
		}
	}

	return TRUE;
}

void CAlbumList::StripTrailingSpaces(char *str, int len)
{
	int i;

	for (i=(len-1); i>=0; i--)
	{
		if (' ' != str[i] && 0 != str[i]) break;
		str[i] = 0;
	}
}

BOOL Win98GetFileAttributesEx(LPCTSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation)
{
	BOOL (WINAPI *pGetFileAttributesEx)(LPCTSTR, GET_FILEEX_INFO_LEVELS, LPVOID) = NULL;

	HMODULE hModule = GetModuleHandle("KERNEL32.DLL");
	if (hModule)
	{
		pGetFileAttributesEx = (BOOL(WINAPI *)(LPCTSTR, GET_FILEEX_INFO_LEVELS, LPVOID)) GetProcAddress(hModule, "GetFileAttributesExA");

		return pGetFileAttributesEx ? pGetFileAttributesEx(lpFileName, fInfoLevelId, lpFileInformation) : FALSE;
	}

	return FALSE;
}

void CAlbumList::Resort()
{
	// sort the album list
	if (al_sortalbum != 0)
	{
		m_AlbumList.Sort();
	}
}

void CAlbumList::LoadAlbumBitmap(LPALBUMINFO pAlbumInfo)
{
#if 0
	WIN32_FIND_DATA FindFileData;
	HANDLE hFile;

	// find the first bmp file (*.bmp) in this directory if it exists
	char bmppath[256];
	lstrcpy(bmppath, pAlbumInfo->path);
	lstrcat(bmppath, "\\*.bmp");

	if ((hFile = FindFirstFile(bmppath, &FindFileData)) != INVALID_HANDLE_VALUE)
	{
		char szPathName[MAX_PATH];
		lstrcpy(szPathName, pAlbumInfo->path);
		lstrcat(szPathName, "\\");
		lstrcat(szPathName, FindFileData.cFileName);
		pAlbumInfo->hBitmap = (HBITMAP)LoadImage(NULL, szPathName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

		FindClose(hFile);
	}
#endif
}

BOOL CAlbumList::AddAlbum(LPALBUMINFO pAlbumInfo)
{
	LPALBUMINFO pAI = new ALBUMINFO;
	*pAI = *pAlbumInfo;

	if (IsCDROM(pAI->path)) pAI->dwFlags |= AI_CDROM;

	m_nTotalTime += pAlbumInfo->nTotalTime;
	m_AlbumList.Add(pAI);

	// sort the album list
	if (al_sortalbum == 6)
	{
		ShuffleAlbums();
	}
	else if (al_sortalbum != 0)
	{
		m_AlbumList.Sort();
	}

	RedrawWindow(m_hWnd, NULL, NULL, RDW_INVALIDATE);

	return TRUE;
}

BOOL CAlbumList::PlayDirectory(LPALBUMINFO album, BOOL bEnqueue /*=FALSE*/, BOOL bStartPlay /*=TRUE*/)
{
	if (album == NULL) return FALSE;

	char *path = album->path;

	WIN32_FIND_DATA FindFileData;
	HANDLE hFile;
	char mypath[MAX_PATH];
	char tempfile[MAX_PATH];
	char temppath[MAX_PATH];

	// save directory
	char oldPath[MAX_PATH] = "";
	GetCurrentDirectory(MAX_PATH, oldPath);

	BOOL bRet = FALSE;

	CTokens extensions(al_extlist, " ?,\"'~!.-:;\t\n");
	CTrackArray trackArray;

	// for all the extensions we have
	int size = extensions.GetSize();
	for (int i=0; i<size; i++)
	{
		// find all the *.mp3 in this directory
		lstrcpy(mypath, path);
		lstrcat(mypath, "\\*.");
		lstrcat(mypath, (char*)extensions[i]);

		int nExtLen = lstrlen(extensions[i]);

		if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
		{
			if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
			{
				LPTRACKINFO pTrackInfo = new TRACKINFO;
				wsprintf(pTrackInfo->path, "%s\\%s", path, FindFileData.cFileName);
				pTrackInfo->nTrack = 0;
				trackArray.Add(pTrackInfo);

				if (!al_sortname)
				{
					CFileInfo *pFileInfo = CreateFileInfo(pTrackInfo->path);
					if (pFileInfo)
					{
						if (pFileInfo->loadInfo(pTrackInfo->path) == 0)
						{
							pTrackInfo->nTrack = pFileInfo->getTrackNumber();
						}
						delete pFileInfo;
					}
				}
			}

			/* Find the rest of the .mp3 files */
			while (FindNextFile(hFile, &FindFileData) != 0)
			{
				if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
				{
					LPTRACKINFO pTrackInfo = new TRACKINFO;
					wsprintf(pTrackInfo->path, "%s\\%s", path, FindFileData.cFileName);
					pTrackInfo->nTrack = 0;
					trackArray.Add(pTrackInfo);

					if (!al_sortname)
					{
						CFileInfo *pFileInfo = CreateFileInfo(pTrackInfo->path);
						if (pFileInfo)
						{
							if (pFileInfo->loadInfo(pTrackInfo->path) == 0)
							{
								pTrackInfo->nTrack = pFileInfo->getTrackNumber();
							}
							delete pFileInfo;
						}
					}
				}
			}
			FindClose(hFile);
		}
	}

	// sort the playlist (by track number)
	if (trackArray.Sort(al_sortname))
	{
		// if there are duplicate track numbers
		// sort by filename
		if (!al_sortname) trackArray.Sort(1);
	}

	HANDLE fp = NULL;
	int len = 0;

	// create temperary playlist
	if (GetTempPath(MAX_PATH, temppath) == 0)
	{
		lstrcpy(temppath, ".\\");
	}
	GetTempFileName(temppath, "wal", 0, tempfile);
	DeleteFile(tempfile);
	len = lstrlen(tempfile);
	tempfile[len-1] = 'u';
	tempfile[len-2] = '3';
	tempfile[len-3] = 'm';

	if (INVALID_HANDLE_VALUE != (fp = CreateFile(tempfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL)))
	{
		DWORD nBytesWritten;
		int i=0;
		int idx=-1;

		if (!bEnqueue && al_keepsp && (SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING) != 0))
		{
			char name[MAX_PATH] = "";
			char file[MAX_PATH] = "";
			char *str;
			int pos = SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETLISTPOS);

			str = (char *)SendMessage(plugin.hwndParent, WM_WA_IPC, pos, IPC_GETPLAYLISTTITLE);
			if (str) lstrcpyn(name, str, MAX_PATH);
			str = (char *)SendMessage(plugin.hwndParent, WM_WA_IPC, pos, IPC_GETPLAYLISTFILE);
			if (str) lstrcpyn(file, str, MAX_PATH);

			if (lstrlen(name) != 0)
			{
				// is it in our current playlist
				int size = trackArray.GetSize();
				for (idx=0; idx<size; idx++)
				{
					char *str = trackArray[idx]->path;
					if (lstrcmpi(str, file) == 0) break;
				}

				if (idx == size)
				{
					WriteFile(fp, "#EXTM3U\n", 8, &nBytesWritten, NULL);
					WriteFile(fp, "#EXTINF:-1,", 11, &nBytesWritten, NULL);
					WriteFile(fp, name, lstrlen(name), &nBytesWritten, NULL);
					WriteFile(fp, "\n", 1, &nBytesWritten, NULL);
					WriteFile(fp, "c:\\[...].mp3\n", 13, &nBytesWritten, NULL);
					WriteFile(fp, "\n", 1, &nBytesWritten, NULL);
					idx = -1;
				}
			}
			else
			{
				//nothing in playlist, so just put [...]
				WriteFile(fp, "#EXTM3U\n", 8, &nBytesWritten, NULL);
				WriteFile(fp, "#EXTINF:-1,[...]\n", 17, &nBytesWritten, NULL);
				WriteFile(fp, "c:\\[...].mp3\n", 13, &nBytesWritten, NULL);
			}
		}

		int size = trackArray.GetSize();
		for (i=0; i<size; i++)
		{
			char *str = trackArray[i]->path;

			WriteFile(fp, str, lstrlen(str), &nBytesWritten, NULL);
			WriteFile(fp, "\n", 1, &nBytesWritten, NULL);
		}


		FlushFileBuffers(fp);
		CloseHandle(fp);

		{ // send the playlist to winamp
			COPYDATASTRUCT cds;

			if (!bEnqueue)
			{
				SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);
			}
			
			cds.dwData = IPC_PLAYFILE;
			cds.lpData = (void *) tempfile;
			cds.cbData = lstrlen((char *) cds.lpData)+1; // include space for null
			SendMessage(plugin.hwndParent, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);
			if (bStartPlay)
			{
				if (!bEnqueue && (!al_keepsp || (SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING) == 0)))
				{
					SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_STARTPLAY);
				}
			}
			if (!bEnqueue && (idx != -1)) SendMessage(plugin.hwndParent, WM_WA_IPC, idx, IPC_SETPLAYLISTPOS);

			bRet = TRUE;
		}
		DeleteFile(tempfile);
	}

	size = trackArray.GetSize();
	for (i=0; i<size; i++)
	{
		delete trackArray[i];
	}
	trackArray.RemoveAll();

	// restore directory
	SetCurrentDirectory(oldPath);

	// update current album
	if (!bEnqueue && bRet)
	{
		lstrcpy(al_index, album->path);
	}

	return bRet;
}

BOOL CAlbumList::PlayM3U(LPALBUMINFO album, BOOL bEnqueue /*=FALSE*/, BOOL bStartPlay /*=TRUE*/)
{
	if (album == NULL) return FALSE;
	if (lstrlen(album->m3u_path) == 0) return FALSE;

	// save directory
	char oldPath[MAX_PATH] = "";
	GetCurrentDirectory(MAX_PATH, oldPath);

	COPYDATASTRUCT cds;

	if (!bEnqueue)
	{
		if (al_keepsp && (SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING) != 0))
		{
			HANDLE fp = NULL;
			char tempfile[MAX_PATH];
			char temppath[MAX_PATH];
			int len = 0;

			// create temperary playlist
			if (GetTempPath(MAX_PATH, temppath) == 0) lstrcpy(temppath, ".\\");
			GetTempFileName(temppath, "wal", 0, tempfile);
			DeleteFile(tempfile);
			len = lstrlen(tempfile);
			lstrcpy(&tempfile[len-3], "m3u");

			if (INVALID_HANDLE_VALUE != (fp = CreateFile(tempfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL)))
			{
				DWORD nBytesWritten;
				int pos = SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_GETLISTPOS);
				char *name = (char *)SendMessage(plugin.hwndParent, WM_WA_IPC, pos, IPC_GETPLAYLISTTITLE);
				if (name != NULL)
				{
					WriteFile(fp, "#EXTM3U\n", 8, &nBytesWritten, NULL);
					WriteFile(fp, "#EXTINF:-1,", 11, &nBytesWritten, NULL);
					WriteFile(fp, name, lstrlen(name), &nBytesWritten, NULL);
					WriteFile(fp, "\n", 1, &nBytesWritten, NULL);
					WriteFile(fp, "c:\\[...].mp3\n", 13, &nBytesWritten, NULL);
					WriteFile(fp, "\n", 1, &nBytesWritten, NULL);
				}
				else
				{
					//nothing in playlist, so just put [...]
					WriteFile(fp, "#EXTM3U\n", 8, &nBytesWritten, NULL);
					WriteFile(fp, "#EXTINF:-1,[...]\n", 17, &nBytesWritten, NULL);
					WriteFile(fp, "c:\\[...].mp3\n", 13, &nBytesWritten, NULL);
				}

				FlushFileBuffers(fp);
				CloseHandle(fp);

				SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);

				cds.dwData = IPC_PLAYFILE;
				cds.lpData = (void *) tempfile;
				cds.cbData = lstrlen((char *) cds.lpData)+1; // include space for null
				SendMessage(plugin.hwndParent, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);

				DeleteFile(tempfile);
			}
			else
			{
				SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);
			}
		}
		else
		{
			SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_DELETE);
		}
	}

	cds.dwData = IPC_PLAYFILE;
	cds.lpData = (void *) album->m3u_path;
	cds.cbData = lstrlen((char *) cds.lpData)+1; // include space for null
	SendMessage(plugin.hwndParent, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);

	if (bStartPlay)
	{
		if (!bEnqueue && (!al_keepsp || (SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING) == 0)))
		{
			SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_STARTPLAY);
		}
	}

	// restore directory
	SetCurrentDirectory(oldPath);

	// update current album
	if (!bEnqueue)
	{
		lstrcpy(al_index, album->m3u_path);
	}

	return TRUE;
}

BOOL CALLBACK WritePlaylistProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	char title[128];
	char str[MAX_PATH*2];

	switch (uMsg)
	{
	case WM_INITDIALOG:
		GetWindowText(hwndDlg, title, 128);
		wsprintf(str, "%s [%ld. %s - %s]", ALSTRING(title), gnAlbumIndex+1, gszArtist, gszAlbumName);
		SetWindowText(hwndDlg, str);
		SetWindowText(GetDlgItem(hwndDlg, IDC_ARTIST), gszArtist);
		SetWindowText(GetDlgItem(hwndDlg, IDC_ALBUM), gszAlbumName);
		SetFocus(GetDlgItem(hwndDlg, IDC_ARTIST));
		CenterWindow(hwndDlg);
		SendMessage(hwndDlg, DM_REPOSITION, 0, 0);
		UpdateDialogStrings(hwndDlg);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			GetWindowText(GetDlgItem(hwndDlg, IDC_ARTIST), gszArtist, 128);
			GetWindowText(GetDlgItem(hwndDlg, IDC_ALBUM), gszAlbumName, 128);
			EndDialog(hwndDlg,IDOK);
			return FALSE;
		}
	}

	return FALSE;
}

BOOL CAlbumList::WritePlaylist(HWND hWnd, int nIndex)
{
	LPALBUMINFO album = GetAlbum(nIndex);
	if (album == NULL) return FALSE;

	// save directory
	char oldPath[MAX_PATH] = "";
	GetCurrentDirectory(MAX_PATH, oldPath);

	char *path = album->path;

	WIN32_FIND_DATA FindFileData;
	HANDLE hFile;
	char mypath[MAX_PATH];
	char tempfile[MAX_PATH];

	BOOL bRet = FALSE;

	CTokens extensions(al_extlist, " ?,\"'~!.-:;\t\n");
	CTrackExArray trackArray;

	char szArtist[128] = "";
	char szAlbumName[128] = "";
	lstrcpyn(szArtist, album->artist, 128);
	lstrcpyn(szAlbumName, album->album, 128);

	// for all the extensions we have
	int size = extensions.GetSize();
	for (int i=0; i<size; i++)
	{
		// find all the *.mp3 in this directory
		lstrcpy(mypath, path);
		lstrcat(mypath, "\\*.");
		lstrcat(mypath, extensions[i]);

		int nExtLen = lstrlen(extensions[i]);

		if ((hFile = FindFirstFile(mypath, &FindFileData)) != INVALID_HANDLE_VALUE)
		{
			if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
			{
				LPTRACKINFOEX pTrackInfo = new TRACKINFOEX;
				wsprintf(pTrackInfo->path, "%s\r\n", FindFileData.cFileName);
				pTrackInfo->nTrack = 0;
				pTrackInfo->desc[0] = 0;
				trackArray.Add(pTrackInfo);

				char filename[MAX_PATH];
				wsprintf(filename, "%s\\%s", path, FindFileData.cFileName);

				CFileInfo *pFileInfo = CreateFileInfo(filename);
				if (pFileInfo)
				{
					if (pFileInfo->loadInfo(filename) == 0)
					{
						char artist[80] = "", songname[80] = "";
						pFileInfo->getArtist(artist);
						pFileInfo->getTitle(songname);
						pTrackInfo->nTrack = pFileInfo->getTrackNumber();

						if (lstrlen(artist) == 0)			lstrcpy(artist, ALSTRING("Unknown Artist"));
						else if (lstrlen(szArtist) == 0)	lstrcpy(szArtist, artist);

						if (lstrlen(songname) == 0)			lstrcpy(songname, FindFileData.cFileName);
						if (lstrlen(szAlbumName) == 0)		pFileInfo->getAlbum(szAlbumName);

						wsprintf(pTrackInfo->desc, "#EXTINF:%d,%s - %s\r\n", pFileInfo->getLengthInSeconds(), artist, songname);
					}
					delete pFileInfo;
				}
			}

			/* Find the rest of the .mp3 files */
			while(FindNextFile(hFile, &FindFileData) != 0)
			{
				if (lstrcmpi(FindFileData.cFileName + lstrlen(FindFileData.cFileName) - nExtLen, extensions[i]) == 0)
				{
					LPTRACKINFOEX pTrackInfo = new TRACKINFOEX;
					wsprintf(pTrackInfo->path, "%s\r\n", FindFileData.cFileName);
					pTrackInfo->nTrack = 0;
					pTrackInfo->desc[0] = 0;
					trackArray.Add(pTrackInfo);

					char filename[MAX_PATH] = "";
					wsprintf(filename, "%s\\%s", path, FindFileData.cFileName);

					CFileInfo *pFileInfo = CreateFileInfo(filename);
					if (pFileInfo)
					{
						if (pFileInfo->loadInfo(filename) == 0)
						{
							char artist[80] = "", songname[80] = "";
							pFileInfo->getArtist(artist);
							pFileInfo->getTitle(songname);
							pTrackInfo->nTrack = pFileInfo->getTrackNumber();

							if (lstrlen(artist) == 0)			lstrcpy(artist, ALSTRING("Unknown Artist"));
							else if (lstrlen(szArtist) == 0)	lstrcpy(szArtist, artist);

							if (lstrlen(songname) == 0)			lstrcpy(songname, FindFileData.cFileName);
							if (lstrlen(szAlbumName) == 0)		pFileInfo->getAlbum(szAlbumName);

							wsprintf(pTrackInfo->desc, "#EXTINF:%d,%s - %s\r\n", pFileInfo->getLengthInSeconds(), artist, songname);
						}
						delete pFileInfo;
					}
				}
			}
			FindClose(hFile);
		}
	}
	
	size = trackArray.GetSize();
	if (size == 0) return FALSE;

	// sort the playlist (by track number)
	if (trackArray.Sort(al_sortname))
	{
		// if there are duplicate track numbers
		// sort by filename
		if (!al_sortname) trackArray.Sort(1);
	}

	lstrcpyn(gszArtist, szArtist, 128);
	lstrcpyn(gszAlbumName, szAlbumName, 128);
	gnAlbumIndex = nIndex;

	DialogBox(plugin.hDllInstance, MAKEINTRESOURCE(IDD_WRITEPLAYLIST), hWnd, WritePlaylistProc);

	lstrcpyn(szArtist, gszArtist, 128);
	lstrcpyn(szAlbumName, gszAlbumName, 128);

	lstrcpy(album->album, szAlbumName);
	lstrcpy(album->artist, szArtist);

	CreateAlbumDisplayName(path, album);

	// write to playlist file
	lstrcpy(tempfile, path);
	lstrcat(tempfile, "\\album.m3u");

	HANDLE fp = NULL;

	if (INVALID_HANDLE_VALUE != (fp = CreateFile(tempfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL)))
	{
		DWORD nBytesWritten;

		WriteFile(fp, "#EXTM3U\r\n", lstrlen("#EXTM3U\r\n"), &nBytesWritten, NULL);
		if (lstrlen(szArtist))
		{
			char artist[80];
			wsprintf(artist, "#EXTART:%s\r\n", szArtist);
			WriteFile(fp, artist, lstrlen(artist), &nBytesWritten, NULL);
		}
		if (lstrlen(szAlbumName))
		{
			char szAlbum[80];
			wsprintf(szAlbum, "#EXTALB:%s\r\n", szAlbumName);
			WriteFile(fp, szAlbum, lstrlen(szAlbum), &nBytesWritten, NULL);
		}

		for (int i=0; i<size; i++)
		{
			LPTRACKINFOEX pTrackInfo = trackArray[i];
			if (lstrlen(pTrackInfo->desc)) WriteFile(fp, pTrackInfo->desc, lstrlen(pTrackInfo->desc), &nBytesWritten, NULL);
			if (lstrlen(pTrackInfo->path)) WriteFile(fp, pTrackInfo->path, lstrlen(pTrackInfo->path), &nBytesWritten, NULL);
		}

		FlushFileBuffers(fp);
		CloseHandle(fp);

		lstrcpyn(album->m3u_path, tempfile, MAX_PATH);
	}

	for (i=0; i<size; i++)
	{
		delete trackArray[i];
	}
	trackArray.RemoveAll();

	// restore directory
	SetCurrentDirectory(oldPath);

	return bRet;
}

BOOL CAlbumList::PlayAlbum(LPALBUMINFO album)
{
	if (lstrlen(album->m3u_path) == 0)
	{
		return PlayDirectory(album);
	}
	else
	{
		return PlayM3U(album);
	}
	return FALSE;
}

BOOL CAlbumList::EnqueueAlbum(LPALBUMINFO album)
{
	if (lstrlen(album->m3u_path) == 0)
	{
		return PlayDirectory(album, TRUE /*Enqueue*/, FALSE /*Do not start play*/);
	}
	else
	{
		return PlayM3U(album, TRUE /*Enqueue*/, FALSE /*Do not start play*/);
	}
	return FALSE;
}

void CAlbumList::RemoveAlbumsFromList(char *path)
{
	if (path == NULL) return;
	if (lstrlen(path) == 0) return;
	_mbslwr((unsigned char *)path);

	int size = 0;
	int nSize = m_AlbumList.GetSize();
	LPVOID *pData = m_AlbumList.GetData();
	for (int i=0; i<nSize; i++)
	{
		LPALBUMINFO pAlbumInfo = m_AlbumList[i];

		char pathlwr[MAX_PATH] = "";
		lstrcpy(pathlwr, pAlbumInfo->path);

		// not part of the remove path
		if (_mbsstr(_mbslwr((unsigned char *)pathlwr), (unsigned char *)path) == NULL)
		{
			if (i > size)
			{
				pData[size] = pData[i];
			}

			size ++;
		}
		// skip all the removed paths
		else
		{
			m_nTotalTime -= pAlbumInfo->nTotalTime;
		}
	}
	
	m_AlbumList.SetSize(size);

	RedrawWindow(m_hWnd, NULL, NULL, RDW_INVALIDATE);
}

void CAlbumList::ShuffleAlbums()
{
	srand((unsigned)time(NULL));

	// Now "shuffle the deck" by swapping pairs of cards. We will swap 
	// 2 * "number of albums" times to ensure a good randomness.
	int size = m_AlbumList.GetSize();
	for (int x=0; x< 2*size; x++)
	{
		// Generate 2 random numbers, each between 0 and m_nSize-1
		int from	= rand() % size;
		int to		= rand() % size;

		// Swap them
		m_AlbumList.Swap(to, from);
	}
}

void CAlbumList::SwapAlbum(int from, int to)
{
	if (from < 0) return;
	if (to < 0) return;
	if (m_AlbumList.GetSize() < from) return;
	if (m_AlbumList.GetSize() < to) return;

	m_AlbumList.Swap(to, from);
}

void CAlbumList::CreateAlbumDisplayName(char *path, LPALBUMINFO pAlbumInfo)
{
	BOOL bUsedid3 = FALSE;	//ANDREAS: in case we didn't get any id3 data

	pAlbumInfo->name[MAX_PATH-1] = 0;

	if (al_useid3)
	{
		if ((lstrlen(pAlbumInfo->album) != 0) && (lstrlen(pAlbumInfo->artist) != 0))
		{
			wsprintf(pAlbumInfo->name, "%s - %s", pAlbumInfo->artist, pAlbumInfo->album);
			bUsedid3 = TRUE;
		}
	}

	if (!bUsedid3)
	{
		char szTemp[MAX_PATH] = "";

		char szArtist[MAX_PATH] = "";
		char szAlbum[MAX_PATH] = "";
		char szAppend[MAX_PATH] = "";

		//ANDREAS: Strip out the root path, but leave a '\' at the beginning
		lstrcpy(szTemp, pAlbumInfo->path + m_nRootLen);

		
		//ANDREAS: If this is a file at the root then fix it's name
		if(strlen(szTemp) == 0)
		{
			strcat(szTemp, "\\(Files in root directory)");
		}

		//ANDREAS: Strip underscores etc...
		if (al_fixtitles == 1)
		{
			FixTitles(szTemp);
		}

		char *ptr = NULL;

		if (al_multidiscfix) 
		{
			// find the last "\"
			ptr = strrchr(szTemp, '\\');

			//Make sure we arenīt pointing at the first and only "\"
			if (ptr != &szTemp[0]) {
				//This is a hack so "\\Artist - Double Album\\CD 2" gets treated properly by removing
				//the "CD 2" part from the dir and then appending it to name deduced from the
				//parent directory
				if (IsDirMultiDisc(ptr + 1))
				{
					wsprintf(szAppend, " (%s)", ptr + 1);
					*ptr = '\0';
				}
			
				ptr = NULL;
			}
		}

		// "...\Artist\Album"
		if (al_dirstyle == 0)
		{
			// find the last directory 
			ptr = strrchr(szTemp, '\\');
			strcpy(szAlbum, ptr + 1);
			
			// find second last directory
			*ptr = '\0';
			ptr = strrchr(szTemp, '\\');
			if(ptr) { 
				strcpy(szArtist, ptr + 1);
			}

			if (lstrlen(szArtist))
			{
				lstrcpy(pAlbumInfo->album, szAlbum);
				lstrcpy(pAlbumInfo->artist, szArtist);
				wsprintf(pAlbumInfo->name, "%s - %s", szArtist, szAlbum);
			}
			else
			{
				lstrcpy(pAlbumInfo->name, szAlbum);
			}
		}
		// "...\Artist - Album"
		else if (al_dirstyle == 1)
		{
			// find the last directory
			ptr = strrchr(szTemp, '\\') + 1;
			strcpy(szAlbum, ptr);

			ptr = strstr(szAlbum, " - ");

			if (ptr)
			{
				lstrcpyn(pAlbumInfo->artist, szAlbum, ptr - szAlbum + 1);
				lstrcpy(pAlbumInfo->album, ptr + 3);
				wsprintf(pAlbumInfo->name, "%s - %s", pAlbumInfo->artist, pAlbumInfo->album);
			}
			else
			{
				lstrcpy(pAlbumInfo->name, szAlbum);
			}
		}
		// "...\Artist - Album - Year"
		else if (al_dirstyle == 2)
		{
			// find the last directory
			ptr = strrchr(szTemp, '\\') + 1;
			strcpy(szAlbum, ptr);

			ptr = strstr(szAlbum, " - ");

			if (ptr)
			{
				int nYear = 0;

				// ok, we got artist
				lstrcpyn(szArtist, szAlbum, ptr - szAlbum + 1);

				// try album and year
				lstrcpy(szTemp, ptr + 3);
				lstrcpy(szAlbum, szTemp);
				ptr = strstr(szAlbum, " - ");
				if (ptr)
				{
					nYear = atoi(ptr+3);
					lstrcpyn(szTemp, szAlbum, ptr - szAlbum + 1);
					lstrcpy(szAlbum, szTemp);
				}

				if (nYear) pAlbumInfo->nYear = nYear;
				lstrcpy(pAlbumInfo->artist, szArtist);
				lstrcpy(pAlbumInfo->album, szAlbum);
				wsprintf(pAlbumInfo->name, "%s - %s", pAlbumInfo->artist, pAlbumInfo->album);
			}
			else
			{
				lstrcpy(pAlbumInfo->name, szAlbum);
			}
		}
		// "...\Artist\Year - Album"
		else if (al_dirstyle == 3)
		{
			// find the last directory 
			ptr = strrchr(szTemp, '\\');
			strcpy(szAlbum, ptr + 1);
			
			// find second last directory
			*ptr = '\0';
			ptr = strrchr(szTemp, '\\');
			if(ptr) { 
				strcpy(szArtist, ptr + 1);
			}

			int nYear = 0;

			// try year and album
			ptr = strstr(szAlbum, " - ");
			if (ptr)
			{
				lstrcpyn(szTemp, szAlbum, ptr - szAlbum + 1);
				nYear = atoi(szTemp);
				lstrcpy(szAlbum, ptr+3);
			}

			if (nYear) pAlbumInfo->nYear = nYear;

			if (lstrlen(szArtist))
			{
				lstrcpy(pAlbumInfo->album, szAlbum);
				lstrcpy(pAlbumInfo->artist, szArtist);
				wsprintf(pAlbumInfo->name, "%s - %s", szArtist, szAlbum);
			}
			else
			{
				lstrcpy(pAlbumInfo->name, szAlbum);
			}
		}
		// "...\Artist - Year - Album"
		else if (al_dirstyle == 4)
		{
			// find the last directory
			ptr = strrchr(szTemp, '\\') + 1;
			strcpy(szAlbum, ptr);

			ptr = strstr(szAlbum, " - ");

			if (ptr)
			{
				int nYear = 0;

				// ok, we got artist
				lstrcpyn(szArtist, szAlbum, ptr - szAlbum + 1);

				// try year and album
				lstrcpy(szTemp, ptr + 3);
				lstrcpy(szAlbum, szTemp);
				ptr = strstr(szAlbum, " - ");
				if (ptr)
				{
					lstrcpyn(szTemp, szAlbum, ptr - szAlbum + 1);
					nYear = atoi(szTemp);
					lstrcpy(szTemp, ptr+3);
					lstrcpy(szAlbum, szTemp);
				}

				if (nYear) pAlbumInfo->nYear = nYear;
				lstrcpy(pAlbumInfo->artist, szArtist);
				lstrcpy(pAlbumInfo->album, szAlbum);
				wsprintf(pAlbumInfo->name, "%s - %s", pAlbumInfo->artist, pAlbumInfo->album);
			}
			else
			{
				lstrcpy(pAlbumInfo->name, szAlbum);
			}
		}

		if (pAlbumInfo->nYear == 0)
		{
			WIN32_FILE_ATTRIBUTE_DATA fileData;

			if (Win98GetFileAttributesEx(pAlbumInfo->path, GetFileExInfoStandard, &fileData))
			{
				SYSTEMTIME st;
				if (FileTimeToSystemTime(&fileData.ftCreationTime, &st))
				{
					pAlbumInfo->nYear = st.wYear;
				}
			}
			else
			{
				struct _stat buf;
				if (_stat(pAlbumInfo->path, &buf) == 0)
				{
					struct tm *t = NULL;
					if (t = localtime(&buf.st_ctime))
					{
						pAlbumInfo->nYear = t->tm_year;
					}
				}
			}
		}
	
		//add (Disc X) etc to the end of the name directory contained multiple discs.
		strcat(pAlbumInfo->name, szAppend);
	}
}

void CAlbumList::WriteAlbumList()
{
	char cache_file[MAX_PATH] = "";
	char *p;
	GetModuleFileName(plugin.hDllInstance, cache_file, sizeof(cache_file));
	p = (char *)_mbsrchr((unsigned char *)cache_file,'\\');
	if (p == NULL)
	{
		p = cache_file;
	}
	if (++p >= cache_file) *p = 0;

	if (al_instance)
	{
		char filename[MAX_PATH] = "";
		wsprintf(filename, "GEN_M3A%ld.DAT", al_instance);
		lstrcat(cache_file, filename);
	}
	else
	{
		lstrcat(cache_file, "GEN_M3A.DAT");
	}

	HANDLE fp;
	if (INVALID_HANDLE_VALUE != (fp = CreateFile(cache_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL)))
	{
		DWORD nBytesWritten;
		DWORD nSizeofALBUMINFO = sizeof(ALBUMINFO);

		// write signature
		WriteFile(fp, "GENM3A", 6, &nBytesWritten, NULL);

		// write structure size (acts as version number)
		WriteFile(fp, &nSizeofALBUMINFO, sizeof(nSizeofALBUMINFO), &nBytesWritten, NULL);

		// write the number of entries
		int nSize = m_AlbumList.GetSize();
		WriteFile(fp, &nSize, sizeof(nSize), &nBytesWritten, NULL);

		// we have albums
		if (nSize)
		{
			// write the album list
			if (!al_usecompression)
			{
				// not using compression
				WriteFile(fp, "\0", 1, &nBytesWritten, NULL);

				// write albums
				for (int i=0; i<nSize; i++)
				{
					LPALBUMINFO pAlbumInfo = m_AlbumList[i];
					WriteFile(fp, pAlbumInfo, sizeof(ALBUMINFO), &nBytesWritten, NULL);
				}
			}
			else
			{
				void RLE_Encoding(HANDLE fp, BYTE *buf, int size);

				// rle compression
				WriteFile(fp, "\2", 1, &nBytesWritten, NULL);

				// write albums
				for (int i=0; i<nSize; i++)
				{
					LPALBUMINFO pAlbumInfo = m_AlbumList[i];
					RLE_Encoding(fp, (LPBYTE)pAlbumInfo, sizeof(ALBUMINFO));
				}
			}
		}

		FlushFileBuffers(fp);
		CloseHandle(fp);
	}
}

BOOL CAlbumList::ReadAlbumList()
{
	char cache_file[MAX_PATH] = "";
	char *p;
	GetModuleFileName(plugin.hDllInstance, cache_file, sizeof(cache_file));
	p = (char *)_mbsrchr((unsigned char *)cache_file,'\\');
	if (p == NULL)
	{
		p = cache_file;
	}
	if (++p >= cache_file) *p = 0;

	if (al_instance)
	{
		char filename[MAX_PATH] = "";
		wsprintf(filename, "GEN_M3A%ld.DAT", al_instance);
		lstrcat(cache_file, filename);
	}
	else
	{
		lstrcat(cache_file, "GEN_M3A.DAT");
	}

	HANDLE		fp			= NULL;
	LPALBUMINFO pAlbumList	= NULL;

	m_nTotalTime = 0;

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

		DWORD nBytesRead;
		int nSize = 0;
		DWORD nSizeofALBUMINFO;

		char signature[7] = "\0\0\0\0\0\0";
		// read signature
		if (!ReadFile(fp, signature, 6, &nBytesRead, NULL)) FAIL;
		if (6 != nBytesRead) FAIL;

		// check signature
		if (lstrcmp(signature, "GENM3A") != 0) FAIL;

		// read size of album list info structure (acts as version number)
		if (!ReadFile(fp, &nSizeofALBUMINFO, sizeof(nSizeofALBUMINFO), &nBytesRead, NULL)) FAIL;
		if (sizeof(nSizeofALBUMINFO) != nBytesRead) FAIL;

		// structure size check OK?
		if (nSizeofALBUMINFO != sizeof(ALBUMINFO)) FAIL;

		// read size of album list successful
		if (!ReadFile(fp, &nSize, sizeof(nSize), &nBytesRead, NULL)) FAIL;
		if (sizeof(nSize) != nBytesRead) FAIL;
		if (nSize == 0) break;

		// read compression method
		// 0 - no compression
		// 1 - RLE encoding
		BYTE bCompression = 0;
		if (!ReadFile(fp, &bCompression, 1, &nBytesRead, NULL)) FAIL;

		// read the albumlist
		if (bCompression == 0)		// no compression
		{
			for (int i=0; i<nSize; i++)
			{
				LPALBUMINFO pAL = new ALBUMINFO;

				if (ReadFile(fp, pAL, sizeof(ALBUMINFO), &nBytesRead, NULL))
				{
					if (al_startup_scan)
					{
						struct _stat buf;
						if (_stat(pAL->path, &buf) != 0)
						{
							delete pAL;
							pAL = NULL;
						}
						else if ((pAL->m3u_path[0] != 0) &&
								 (_stat(pAL->m3u_path, &buf) != 0))
						{
							delete pAL;
							pAL = NULL;
						}
					}
				}
				else
				{
					delete pAL;
					pAL = NULL;
				}

				if (pAL != NULL)
				{
					m_AlbumList.Add(pAL);
					m_nTotalTime += pAL->nTotalTime;
				}
			}
		}
		else if (bCompression == 1) // RLE Encoding
		{
			// allocate memory
			if ((pAlbumList = (LPALBUMINFO)GlobalAlloc(GPTR, nSize*sizeof(ALBUMINFO))) == NULL) FAIL;

			void RLE_Decoding(HANDLE fp, BYTE *buf, int size);
			RLE_Decoding(fp, (LPBYTE)pAlbumList, nSize*sizeof(ALBUMINFO));

			for (int i=0; i<nSize; i++)
			{
				LPALBUMINFO pAL = NULL;

				pAL = new ALBUMINFO;
				*pAL = pAlbumList[i];

				if (al_startup_scan)
				{
					struct _stat buf;
					if (_stat(pAL->path, &buf) != 0)
					{
						delete pAL;
						pAL = NULL;
					}
					else if ((pAL->m3u_path[0] != 0) &&
							 (_stat(pAL->m3u_path, &buf) != 0))
					{
						delete pAL;
						pAL = NULL;
					}
				}

				if (pAL != NULL)
				{
					m_AlbumList.Add(pAL);
					m_nTotalTime += pAlbumList[i].nTotalTime;
				}
			}

			if (pAlbumList) GlobalFree(pAlbumList);
		}
		else if (bCompression == 2)	// RLE Encoding (new per album basis)
		{
			void RLE_Decoding(HANDLE fp, BYTE *buf, int size);
			
			for (int i=0; i<nSize; i++)
			{
				LPALBUMINFO pAL = new ALBUMINFO;

				RLE_Decoding(fp, (LPBYTE)pAL, sizeof(ALBUMINFO));

				if (al_startup_scan)
				{
					struct _stat buf;
					if (_stat(pAL->path, &buf) != 0)
					{
						delete pAL;
						pAL = NULL;
					}
					else if ((pAL->m3u_path[0] != 0) &&
							 (_stat(pAL->m3u_path, &buf) != 0))
					{
						delete pAL;
						pAL = NULL;
					}
				}

				if (pAL != NULL)
				{
					m_AlbumList.Add(pAL);
					m_nTotalTime += pAL->nTotalTime;
				}
			}
		}
		else
		{
			FAIL;
		}

		CloseHandle(fp);

		return TRUE;
	}
	END_ENSURE;

	if (pAlbumList) GlobalFree((LPVOID)pAlbumList);
	if (fp) CloseHandle(fp);

	return FALSE;
}

void CAlbumList::ExportList()
{
	char str[256];

	if (GetSize())
	{
		// create temperary playlist
		char tempfile[MAX_PATH] = "";
		char temppath[MAX_PATH] = "";
		if (GetTempPath(MAX_PATH, temppath) == 0)
		{
			lstrcpy(temppath, ".\\");
		}
		GetTempFileName(temppath, "wal", 0, tempfile);
		DeleteFile(tempfile);
		lstrcat(tempfile, ".htm");

		CTemplateFile templatefile;

		//---------------------------
		// output filename
		templatefile.AddReplace("outfilename", tempfile);

		//---------------------------
		// character set
		switch (al_language)
		{
		case IDC_LANGUAGE_CHT:	templatefile.AddReplace("charset", "big5");			break;
		case IDC_LANGUAGE_CHS:	templatefile.AddReplace("charset", "gb3212");		break;
		case IDC_LANGUAGE_JPN:	templatefile.AddReplace("charset", "shift-jis");	break;
		case IDC_LANGUAGE_KOR:	templatefile.AddReplace("charset", "euc-kr");		break;
		default:				templatefile.AddReplace("charset", "");				break;
		}
		
		templatefile.AddReplace("totalalbums", GetSize());

		//---------------------------
		// write average time
		int nAvgLength = m_nTotalTime/GetSize();
		if (nAvgLength > 3600)
			wsprintf(str, "%ld:%02ld:%02ld", nAvgLength/3600, (nAvgLength%3600)/60, nAvgLength%60);
		else
			wsprintf(str, "%ld:%02ld", nAvgLength/60, nAvgLength%60);

		templatefile.AddReplace("averagelength", str);

		//---------------------------
		// album list
		int nCount = 1;
		for (int i=0; i<GetSize(); i++)
		{
			int hour = GetAlbum(i)->nTotalTime/3600;
			int min = (GetAlbum(i)->nTotalTime%3600)/60;
			int sec = GetAlbum(i)->nTotalTime%60;

			if (hour)
				wsprintf(str, "%ld:%02ld:%02ld", hour, min, sec);
			else
				wsprintf(str, "%ld:%02ld", min, sec);

			templatefile.AddReplace("albumindex", i, i+1);
			templatefile.AddReplace("albumname", i, GetAlbum(i)->album);
			templatefile.AddReplace("albumcount", i, GetAlbum(i)->nCount);
			templatefile.AddReplace("displayname", i, GetAlbum(i)->name);
			templatefile.AddReplace("albumartist", i, GetAlbum(i)->artist);
			templatefile.AddReplace("albumyear", i, GetAlbum(i)->nYear);
			templatefile.AddReplace("albumlengthhrs", i, hour);
			templatefile.AddReplace("albumlengthmin", i, min);
			templatefile.AddReplace("albumlengthsec", i, sec);
			templatefile.AddReplace("albumlength", i, str);
			templatefile.AddReplace("songcounter", i, nCount);
			
			nCount += GetAlbum(i)->nCount;
		}

		//---------------------------
		// total time
		if (m_nTotalTime > 3600)	// write hours
		{
			templatefile.AddReplace("totallengthhrs", m_nTotalTime/3600);
		}
		if (m_nTotalTime > 60)		// write minutes
		{
			templatefile.AddReplace("totallengthmin", (m_nTotalTime%3600)/60);
		}
		if (m_nTotalTime)			// write seconds
		{
			templatefile.AddReplace("totallengthsec", m_nTotalTime%60);
		}

		//---------------------------
		// convert the file
		char szTemplateFile[MAX_PATH];
		wsprintf(szTemplateFile, "%s%s", szAlbumListDir, "Album List\\Templates\\export.htt");
		if (!templatefile.Convert(tempfile, szTemplateFile)) return;

		//---------------------------
		// show the thing in the minibrowser
		HWND hWinampMB = ALFindWindow("Winamp MB", NULL);
		if (IsWindowVisible(hWinampMB))
		{
			char url[MAX_PATH*2] = "file:///";
			lstrcat(url, tempfile);
			SendMessage(plugin.hwndParent, WM_WA_IPC, 0, IPC_MBOPEN);
			SendMessage(plugin.hwndParent, WM_WA_IPC, (WPARAM)url, IPC_MBOPEN);
		}
		else
		{
			GotoURL(tempfile, SW_SHOWNORMAL);
		}
	}
}

CFileInfo *CAlbumList::CreateFileInfo(LPCTSTR szFile)
{
	int len = lstrlen(szFile);
	if (len < 5) return NULL;

	if (al_logfile)
	{
		FILE *fp = fopen("c:\\gen_m3a.log", "a+t");
		if (fp)
		{
			fputs(szFile, fp);
			fputs("\n", fp);
			fclose(fp);
		}
	}

	// try external file info readers first
	// so others can override the built-in readers
	int size = m_FileReaders.GetSize();
	for (int i=0; i<size; i++)
	{
		LPFILEREADER pFileReader = (LPFILEREADER)m_FileReaders[i];

		CTokens extensions(pFileReader->exts, " ,.;");
		int size = extensions.GetSize();
		for (int i=0; i<size; i++)
		{
			int extlen = lstrlen(extensions[i]);

			if (strcmpi(szFile + len - extlen, extensions[i]) == 0)
				return pFileReader->pCreate(pFileReader->nIndex);
		}
	}
	
	// try the built-in MP3 reader
	if (strcmpi(szFile + len - 3, "MP3") == 0)
		return new CMP3Info;

	// try the built-in MPC reader
	if ((strcmpi(szFile + len - 3, "MPC") == 0) ||
		(strcmpi(szFile + len - 3, "MP+") == 0) ||
		(strcmpi(szFile + len - 3, "MPP") == 0))
		return new CMPCInfo;

	// try the built-in SPC reader
	if (strcmpi(szFile + len - 3, "SPC") == 0)
		return new CSPCInfo;

	// try the built-in WMA reader
	if (strcmpi(szFile + len - 3, "WMA") == 0)
		return new CWMAInfo;

	// try the built-in APE reader
	if ((strcmpi(szFile + len - 3, "APE") == 0) ||
		(strcmpi(szFile + len - 3, "MAC") == 0))
		return new CAPEInfo;

	// try the built-in WAV reader
	if (strcmpi(szFile + len - 3, "WAV") == 0)
		return new CWAVInfo;

	// try the built-in PSF reader
	if ((strcmpi(szFile + len - 3, "PSF") == 0) ||
		(strcmpi(szFile + len - 7, "MINIPSF") == 0))
		return new CPSFInfo;

	return NULL;
}

void CAlbumList::AddFileReader(char *reader)
{
	HMODULE hModule = NULL;
	char	   *(WINAPI *pGetExt)(int);
	CFileInfo  *(WINAPI *pCreate)(int);

	if (hModule = LoadLibrary(reader))
	{
		pGetExt = (char*     (WINAPI *)(int))GetProcAddress(hModule, "GetExtension");
		pCreate = (CFileInfo*(WINAPI *)(int))GetProcAddress(hModule, "CreateFileInfo");

		if (pGetExt && pCreate)
		{
			char *ptr = NULL;
			int i=0;

			while (pGetExt(i))
			{
				LPFILEREADER pFileReader = new FILEREADER;
				memset(pFileReader, 0, sizeof(FILEREADER));

				lstrcpyn(pFileReader->exts, pGetExt(i), MAX_PATH);
				pFileReader->nIndex = i;
				pFileReader->hModule = hModule;
				pFileReader->pCreate = pCreate;
				pFileReader->pGetExt = pGetExt;
				m_FileReaders.Add(pFileReader);

				i++;
			}
			if (i == 0) FreeLibrary(hModule);
		}
		else
		{
			FreeLibrary(hModule);
		}
	}
}

void CAlbumList::LoadExternalFileReader()
{
	char path[MAX_PATH] = "";
	wsprintf(path, "%s*.afp", szAlbumListPluginDir);

	HANDLE hFile;
	WIN32_FIND_DATA FindFileData;
	if ((hFile = FindFirstFile(path, &FindFileData)) != INVALID_HANDLE_VALUE)
	{
		char reader[MAX_PATH] = "";
		wsprintf(reader, "%s%s", szAlbumListPluginDir, FindFileData.cFileName);

		AddFileReader(reader);

		while (FindNextFile(hFile, &FindFileData) != 0)
		{
			wsprintf(reader, "%s%s", szAlbumListPluginDir, FindFileData.cFileName);

			AddFileReader(reader);
		}
		FindClose(hFile);
	}
}

void CAlbumList::UnloadExternalFileReader()
{
	int size = m_FileReaders.GetSize();
	for (int i=0; i<size; i++)
	{
		LPFILEREADER pFileReader = (LPFILEREADER)m_FileReaders[i];

		if (pFileReader->nIndex == 0)
		{
			FreeLibrary(pFileReader->hModule);
		}

		delete pFileReader;
	}
	m_FileReaders.RemoveAll();
}

void CAlbumList::FixTitles(char *str)
{
	// no need to fix title
	if (fixtitles == 0) return;

	int len = lstrlen(str);

	for (int i=0; i<len; i++)
	{
		// convert %20 to space in titles
		if ((fixtitles & 1) && (str[i] == '%'))
		{
			char temp[4];
			lstrcpyn(temp, str+i, 4);
			if (lstrcmp(temp, "%20") == 0)
			{
				str[i] = ' ';

				// move the remaining characters forward
				for (int j=i+3; j<len; j++)
				{
					str[j-2] = str[j];
				}

				// adjust the length
				len -= 2;
				str[len] = 0;
			}
		}

		// convert underscore to space in titles
		if ((fixtitles & 2) && (str[i] == '_'))
		{
			str[i] = ' ';
		}
	}
}

BOOL CAlbumList::IsDirMultiDisc(char *album)
{
	CTokens disc(al_multidiscnames, " ?,\"'~!.-:;\t\n");

	for (int i=0; i<disc.GetSize(); i++)
	{
		//Check for "cd" or "disc" etc...
		if (_strnicmp(disc[i], album, lstrlen(disc[i])) == 0)
		{
			//Make sure it's followed by a number (whitespace ignored)
			if (atoi(album + lstrlen(disc[i])) > 0)
			{
				return TRUE;
			}
		}
	}
	
	return FALSE;
}

//////////////////////////////////////////////////////////////////
// CAlbumArray class

CAlbumArray::CAlbumArray() : CPtrArray()
{
}

CAlbumArray::~CAlbumArray()
{
}

int CAlbumArray::Add(ALBUMINFO *pAlbumInfo)
{
	return CPtrArray::Add(pAlbumInfo);
}

void CAlbumArray::SetAt(int nIndex, LPALBUMINFO pAlbumInfo)
{
	CPtrArray::SetAt(nIndex, pAlbumInfo);
}

LPALBUMINFO CAlbumArray::operator[](int nIndex)
{
	return (LPALBUMINFO)GetAt(nIndex);
}

void CAlbumArray::Sort()
{
	if (GetSize())
	{
		qsort(GetData(), GetSize(), sizeof(LPVOID), Compare);
	}
}

int CAlbumArray::Compare(const void *elem1, const void *elem2)
{
	LPALBUMINFO pAlbumInfo1 = *(LPALBUMINFO*)elem1;
	LPALBUMINFO pAlbumInfo2 = *(LPALBUMINFO*)elem2;

	int nRet = 0;
	switch(al_sortalbum)
	{
	case 1:	// path
		if ((nRet = lstrcmp(pAlbumInfo1->path, pAlbumInfo2->path)) == 0)
		{
			return lstrcmp(pAlbumInfo1->m3u_path, pAlbumInfo2->m3u_path);
		}
		return nRet;

	case 2:	// name
		if ((nRet = lstrcmp(pAlbumInfo1->name, pAlbumInfo2->name)) == 0)
		{
			if ((nRet = lstrcmp(pAlbumInfo1->path, pAlbumInfo2->path)) == 0)
			{
				return lstrcmp(pAlbumInfo1->m3u_path, pAlbumInfo2->m3u_path);
			}
		}
		return nRet;

	case 3: // artist
		if ((nRet = lstrcmp(pAlbumInfo1->artist, pAlbumInfo2->artist)) != 0)
		{
			return nRet;
		}
		// else fall through

	case 4: // year
		if (pAlbumInfo1->nYear < pAlbumInfo2->nYear)
		{
			return -1;
		}
		else if (pAlbumInfo1->nYear > pAlbumInfo2->nYear)
		{
			return 1;
		}
		else	// try comparing directory creation time
		{
 			WIN32_FILE_ATTRIBUTE_DATA fileData1, fileData2;

			if (Win98GetFileAttributesEx(pAlbumInfo1->path, GetFileExInfoStandard, &fileData1) &&
				Win98GetFileAttributesEx(pAlbumInfo2->path, GetFileExInfoStandard, &fileData2))
			{
				return CompareFileTime(&fileData1.ftCreationTime, &fileData2.ftCreationTime);
			}
			else
			{
				struct _stat buf1, buf2;
				if ((_stat(pAlbumInfo1->path, &buf1) == 0) &&
					(_stat(pAlbumInfo2->path, &buf2) == 0))
				{
					if (buf1.st_ctime < buf2.st_ctime)
					{
						return -1;
					}
					else if (buf1.st_ctime > buf2.st_ctime)
					{
						return 1;
					}
				}
				return lstrcmp(pAlbumInfo1->name, pAlbumInfo2->name);
			}
		}
		break;

	case 5: // num. of songs
		if (pAlbumInfo1->nCount < pAlbumInfo2->nCount)
		{
			return -1;
		}
		else if (pAlbumInfo1->nCount > pAlbumInfo2->nCount)
		{
			return 1;
		}
		else
		{
			return lstrcmp(pAlbumInfo1->name, pAlbumInfo2->name);
		}
		break;

	case 6: // total time
		if (pAlbumInfo1->nTotalTime < pAlbumInfo2->nTotalTime)
		{
			return -1;
		}
		else if (pAlbumInfo1->nTotalTime > pAlbumInfo2->nTotalTime)
		{
			return 1;
		}
		else
		{
			return lstrcmp(pAlbumInfo1->name, pAlbumInfo2->name);
		}
		break;
	}

	return 0;
}

//////////////////////////////////////////////////////////////////
// CTrackArray class

BOOL CTrackArray::m_bDuplicate = FALSE;
int CTrackArray::m_nSortName = 0;

CTrackArray::CTrackArray() : CPtrArray()
{
}

CTrackArray::~CTrackArray()
{
}

int CTrackArray::Add(LPTRACKINFO pTrackInfo)
{
	return CPtrArray::Add(pTrackInfo);
}

LPTRACKINFO CTrackArray::operator[](int nIndex)
{
	return (LPTRACKINFO)GetAt(nIndex);
}

BOOL CTrackArray::Sort(int sortname)
{
	m_bDuplicate = FALSE;
	m_nSortName = sortname;
	if (GetSize())
	{
		qsort(GetData(), GetSize(), sizeof(LPVOID), Compare);
	}
	return m_bDuplicate;
}

int CTrackArray::Compare(const void *elem1, const void *elem2)
{
	LPTRACKINFO pTrackInfo1 = *(LPTRACKINFO*)elem1;
	LPTRACKINFO pTrackInfo2 = *(LPTRACKINFO*)elem2;

	if (!m_nSortName)
	{
		if (pTrackInfo1->nTrack < pTrackInfo2->nTrack) return -1;
		if (pTrackInfo1->nTrack > pTrackInfo2->nTrack) return 1;
		m_bDuplicate = TRUE;
	}
	return lstrcmp(pTrackInfo1->path, pTrackInfo2->path);
}

//////////////////////////////////////////////////////////////////
// CTrackExArray class

int CTrackExArray::Add(LPTRACKINFOEX pTrackInfo)
{
	return CPtrArray::Add(pTrackInfo);
}

LPTRACKINFOEX CTrackExArray::operator[](int nIndex)
{
	return (LPTRACKINFOEX)GetAt(nIndex);
}

