
/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

#include "stdafx.h"
#include "DsLavSplit.h"
#include "RadioBrowseDb.h"
#include "Curlinet.h"


///////////////////////////////////////////////////
// Class CRadioBrowse

CRadioBrowseDb::CRadioBrowseDb()
{
	// Build update thread.
	m_hwndMain = NULL;
	m_hwndDlg = NULL;
    m_pEvent = NULL;
	m_inThread = FALSE;
	m_nProccessed = -1;
}

CRadioBrowseDb::~CRadioBrowseDb()
{
}

void CRadioBrowseDb::ZeroStorage()
{
	m_stationAr.RemoveAll();
	m_countryCodeAr.RemoveAll();
	m_tagAr.RemoveAll();
	m_stateAr.RemoveAll();
	m_languageCodeAr.RemoveAll();
}

void CRadioBrowseDb::CopyStorage(CRadioBrowseDb & db)
{
	// Overwrites elements with db param data.
	m_stationAr.Copy(db.m_stationAr);
	m_countryCodeAr.Copy(db.m_countryCodeAr);
	m_tagAr.Copy(db.m_tagAr);
	m_stateAr.Copy(db.m_stateAr);
	m_languageCodeAr.Copy(db.m_languageCodeAr);
}


//////////////////////////////////////////////////////////////////////////////
// Building:
//
// Build the Radio-Browser database and serialize to RadioBrowse.dat.
// in a worker thread.	
// Called from: RadioBrowseUpdateThread() in DsLavDoc.cpp.
BOOL CRadioBrowseDb::BuildRadioBrowserThread(CString rbStationsUrl, CString tempFilePath, 
											 CString & errStr, HWND hwndMain, HWND hwndDlg, 
											 CEvent * pEvent)
{
	// Ensure.
	if(!hwndMain || !hwndDlg || !pEvent)
	{
		errStr = _T("CRadioBrowseDb::BuildRadioBrowserThread param errors.");
		TRACE(_T("CRadioBrowseDb::BuildRadioBrowserThread param errors.\n"));
		return FALSE;
	}
	// Set members.
	m_hwndMain = hwndMain;
	m_hwndDlg = hwndDlg;
    m_pEvent = pEvent;
	m_inThread = TRUE;
	m_nProccessed = -1; // progess count, 0 - 30
	CString str;

	/////////////////////////////////////////////////////////////////
	// Build Stations array - 28363 stations.
	//  Progress control set in function for download and processing.
	int ret = BuildStationArrayThread(rbStationsUrl, tempFilePath, errStr);
	if(!ret)
	{
		ShowDialogStatus(_T("Building Stations failed.")); //last
		TRACE(errStr);
		return FALSE;
	}
	else
	{
		str.Format(_T("%d stations proccessed."), ret);
		ShowDialogStatus(str);
	}

	////////////////////////////////////////////////////////////////////
	// Reset progress for 5 divs - one for each function below. 
	if(m_hwndDlg)
	{
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_MAX, 0, (LPARAM)5);
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)0);
	}

	////////////////////////////////////////////////////////////////////
	// Build CountryCodes array - 249 countries.
	ShowDialogStatus(_T("Loading countries."));
	ret = BuildCountryCodesArray();
	if(!ret)
	{
		errStr = _T("Loading countries failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// Check if User has aborted update.
	if(UpdateCanceled(_T("Post BuildCountryCodesArray")))
		return FALSE;
	// Update progress.
	PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)1);

	/////////////////////////////////////////////////////////////////////
	// Set CountryCodes used by stations in the Station Array - 210 used.
	ShowDialogStatus(_T("Setting country codes."));
	ret = SetCountryCodesUsed();
	if(!ret)
	{
		errStr = _T("Setting country codes used failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// If aborted.
	if(UpdateCanceled(_T("Post SetCountryCodesUsed")))
		return FALSE;
	// Update progress.
	PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)2);

	/////////////////////////////////////////////////////////////////////
	// Build tag list - all tags must be used by 80 stations.
	//  113 tags saved of 7621 tags in station Array.
	ShowDialogStatus(_T("Building tag list."));
	ret = BuildTagArray(80);
	if(!ret)
	{
		errStr = _T("Building tags failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// If aborted.
	if(UpdateCanceled(_T("BuildTagArray")))
		return FALSE;
	// Update progress.
	PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)3);

	///////////////////////////////////////////////////////////////////
	// Build states list - all countries:
	//  2680 states saved of 7621 states in station Array.
	ShowDialogStatus(_T("Building states list."));
	ret = BuildStatesArray(80);
	if(!ret)
	{
		errStr = _T("Building states failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// If aborted.
	if(UpdateCanceled(_T("BuildTagArray")))
		return FALSE;
	// Update progress.
	PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)4);

	////////////////////////////////////////////////////////////////////
	// Build Language Codes array - .
	//  139 Language Codes saved.
	ShowDialogStatus(_T("Loading languages."));
	ret = BuildLanguageCodesArray();
	if(!ret)
	{
		errStr = _T("Loading languages failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// Repair language codes in m_stationAr.
	//  nEmpty = 4617, nRepaired = 21984, nOk = 0, nFailed = 1916
	RepaireLanguageCodes();

	// Set language codes used by RB data.
	//  Codes used = 95
	ret = SetLanguageCodesUsed();

	// Update progress.
	PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)5);


	//////////////////////////////////////////////////////////////////
	// Check results - need some way to test if update is valid.
	// Use ballpark count for now.
	//
	BOOL fail = FALSE;

	// Stations - 28363 stations.
	if(GetStationArSize() < 20000)
		fail = TRUE;

	// Country Codes - 249 countries.
	if(GetCountryCodeArSize() < 200)
		fail = TRUE;

	// Tags - 113 tags.
	if(GetTagArSize() < 70)
		fail = TRUE;

	// States - 2680 states.
	if(GetStateArSize() < 2000)
		fail = TRUE;

	// Language Codes - 139 Language Codes .
	if(GetLanguageCodesArSize() < 100)
		fail = TRUE;


	// Bail.
	if(fail)
	{
		errStr = _T("Radio Browser data appears corrupt - update failed.");
		ShowDialogStatus(errStr);
		return FALSE;
	}
	// OK.
	return TRUE;	
}

// Get Station data from Radio-Browser - thread version.
//  filepath is writable temp file.
//  Called only from BuildRadioBrowserThread().
int CRadioBrowseDb::BuildStationArrayThread(CString rbUrl, CString tempFilePath, CString & errStr)
{
	int nStationsRead = 0;
	m_nProccessed = -1;
	CCurlinet inet;

	// Info user.
	ShowDialogStatus(_T("Getting Radio Browser station data from RB server.\r\nPlease wait."));

	// Set dlg progress ctrl for download. 30 steps for 30 MB.
	// 7/22 - use RB_UPDATE_PROG_STEPS - see DsLavDoc.h.
	if(m_hwndDlg)
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_MAX, 0, (LPARAM)RB_UPDATE_PROG_STEPS);

	// Fetch with threaded version.
	//  Thread messages and events handled by function.
	//  Returns xml data - stores in filePath temp file.
	//  65 seconds or so to download - 27.7 MB.
	// See note in CDsLavDoc::OnToolsUpdateRb() re skipping this
	// while debugging.
	CString strErr;
	if(!inet.GetInternetDataUnicodeThread(rbUrl, tempFilePath, strErr, 
										  m_hwndMain, m_hwndDlg, m_pEvent))
	{
		errStr.Format(_T("CRadioBrowseDb::BuildStationArrayThread - GetInternetDataThread failed: %s\n"), 
							strErr);
		TRACE(errStr);
		return 0;
	}

	// Info user.
	ShowDialogStatus(_T("Proccessing Radio Browser station data.\r\nPlease wait."));

	// Re-set dlg progress ctrl for parsing. 30 steps for 30k stations.
	// 7/22 - use RB_UPDATE_PROG_STEPS
	if(m_hwndDlg)
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_MAX, 0, (LPARAM)RB_UPDATE_PROG_STEPS);

	// Parse the XML.
	//
	// Open File for reading.
	CFile file;
	CFileException e;
	if(!file.Open(tempFilePath, CFile::modeRead | CFile::typeBinary, &e))
	{
		TCHAR szError[1024];
		e.GetErrorMessage(szError, 1024);
		CString er;
		er.Format(_T("Couldn't open file: %s"), szError);
		TRACE(er);
		errStr = er;
		return 0;
	}
	// Read vars.
	const UINT bufSz	= 4096;
	BYTE buf[bufSz];

	UINT	nRead		= 0;
	UINT	filePos		= 0;
	TCHAR	ch			= _T('0');
	TCHAR	space		= _T(' ');
	TCHAR	equals		= _T('=');
	TCHAR	quote		= _T('"');

	// XML Tags
	TCHAR	open		= _T('<');
	TCHAR	close		= _T('>');
	int		openPos		= 0;

	CString name, value;
	BOOL	bInString	= FALSE;

	// Prepending !!Playlist.
	CString tempName;
	int iStart = 0;

	// Prep station array.
	m_stationAr.RemoveAll();
	m_stationAr.SetSize(0, 512);
	int index = 0;

	// Read chunks.
	while((nRead = file.Read(buf, bufSz)) > 0)
	{
		// Parse the chunk.
		for(UINT i = 0; i < nRead; i++)
		{
			filePos ++;
			ch = buf[i];

			// Get a string.
			if(bInString)
			{
				// Ending quote - save the value.
				if(ch == quote)
				{
					// Save value.
					if(value != _T(""))
						SaveNameValue(name, value);

					// String complete.
					bInString = FALSE;
					value = _T("");
					continue;
				}
				value += ch;
				continue;
			}

			// A quote - begin string value.
			if(ch == quote)
			{
				bInString = TRUE;
				value = _T("");
				continue;
			}

			// At a space - start a new xml name.
			if(ch == space)
			{
				name = _T("");
				continue;
			}
			// At an equals char - end of xml name.
			if(ch == equals)
			{
				// Test / save name
				// Continue for value string.
				continue;
			}

			// Open tag < - keep count.
			if(ch == open)
			{
				openPos = filePos;
				continue;
			}
			// Close tag > - save the station.
			if(ch == close)
			{
				if(filePos - openPos > 9) // will skip </station>
				{
					// Save station to m_stationAr.
					//
					// If valid station. 
					if(m_tempStation.m_name != _T("") && m_tempStation.m_url != _T("") )
					{
						// If url is a .pls or .m3u/.m3u8, prepend with "!!Playlist:".
						//  Start search near end of string.
						//  Note - 2580 playlist urls in RB data.
						iStart = m_tempStation.m_url.GetLength() - 7;
						if(iStart < 0)
							iStart = 0;

						// 11/22 - m3u8 - fix - not a playlist.
						if(m_tempStation.m_url.Find(_T(".m3u8"), iStart) != -1)
						{
							// Ok - ffmpeg will play
							//  See CDsLavDoc::OnOpenMediaUrl()
						}
						// Playlists.
						else if(m_tempStation.m_url.Find(_T(".pls"), iStart) != -1 ||
								m_tempStation.m_url.Find(_T(".m3u"), iStart) != -1 )
						{
							tempName.Format(_T("!!Playlist: %s"), m_tempStation.m_name);
							m_tempStation.m_name = tempName;
						}
						//done !!Playlist

						index = m_stationAr.Add(m_tempStation);
						m_tempStation.Zero();
						nStationsRead ++;
						
						// Update dlg progress.
						// First.
						if(m_nProccessed == -1)
						{
							m_nProccessed = 0;
							// Post 0 step to dialog.
							if(m_hwndDlg)
								PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 
											0, (LPARAM)m_nProccessed);
						}
						// About 32,000 stations.
						// 7/22 - now using RB_UPDATE_PROG_STEPS
						else
						{
							int n = nStationsRead / 1000;
							if(n > m_nProccessed)
							{
								m_nProccessed = n;
								// Post current step to dialog.
								if(m_hwndDlg)
									PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 
												0, (LPARAM)m_nProccessed);
							}
						}
					}
				}
				openPos = 0;

				// Check if User has aborted update.
				if(UpdateCanceled(_T("Parsing stations")))
				{
					// Close temp file.
					file.Close();
					return 0;
				}
				// OK.
				continue;
			}
			// Add to name.
			name += ch;
		}
	}
	// Done.
	file.Close();

	// Results.
	int sz = m_stationAr.GetSize(); // 28,395 stations, 3 seconds to parse release, 36 seconds debug, 
	if(!sz)	
	{
		errStr = _T("CRadioBrowseDb::LoadDb - got no stations.\n");
		ShowDialogStatus(errStr);
		TRACE(errStr);
		return 0;
	}
	return sz;
}

// Get Station data from Radio-Browser.
//  filepath is writable temp file.
int CRadioBrowseDb::BuildStationArray(CString filePath, CString server, CString & errStr)
{
	int nStationsRead = 0;
	CCurlinet inet;

	// Build query.
	CString query = server;
	query += _T("/xml/stations"); // http://nl1.api.radio-browser.info/xml/stations

	// Fetch non threaded - returns xml data - stores in filePath temp file.
	//  65 seconds or so to download - 27.7 MB - .
	CString strErr;
	if(!inet.GetInternetDataUnicode(query, filePath, strErr))
	{
		errStr.Format(_T("CRadioBrowseDb::LoadDb - GetInternetData failed: %s\n"), strErr);
		TRACE(errStr);
		return 0;
	}

	// Parse the XML.
	//
	// Open File for reading.
	CFile file;
	CFileException e;
	if(!file.Open(filePath, CFile::modeRead | CFile::typeBinary, &e))
	{
		TCHAR szError[1024];
		e.GetErrorMessage(szError, 1024);
		CString er;
		er.Format(_T("Couldn't open file: %s"), szError);
		TRACE(er);
		errStr = er;
		return 0;
	}
	// Read vars.
	const UINT bufSz	= 4096;
	BYTE buf[bufSz];

	UINT	nRead		= 0;
	UINT	filePos		= 0;
	TCHAR	ch			= _T('0');
	TCHAR	space		= _T(' ');
	TCHAR	equals		= _T('=');
	TCHAR	quote		= _T('"');

	// XML Tags
	TCHAR	open		= _T('<');
	TCHAR	close		= _T('>');
	int		openPos		= 0;

	CString name, value;
	BOOL	bInString	= FALSE;

	// Prep station array.
	m_stationAr.RemoveAll();
	m_stationAr.SetSize(0, 512);
	int index = 0;

	// Read chunks.
	while((nRead = file.Read(buf, bufSz)) > 0)
	{
		// Parse the chunk.
		for(UINT i = 0; i < nRead; i++)
		{
			filePos ++;
			ch = buf[i];

			// Get a string.
			if(bInString)
			{
				// Ending quote - save the value.
				if(ch == quote)
				{
					// Save value.
					if(value != _T(""))
						SaveNameValue(name, value);

					// String complete.
					bInString = FALSE;
					value = _T("");
					continue;
				}
				value += ch;
				continue;
			}

			// A quote - begin string value.
			if(ch == quote)
			{
				bInString = TRUE;
				value = _T("");
				continue;
			}

			// At a space - start a new xml name.
			if(ch == space)
			{
				name = _T("");
				continue;
			}
			// At an equals char - end of xml name.
			if(ch == equals)
			{
				// Test / save name
				// Continue for value string.
				continue;
			}

			// Open tag < - keep count.
			if(ch == open)
			{
				openPos = filePos;
				continue;
			}
			// Close tag > - save the station.
			if(ch == close)
			{
				if(filePos - openPos > 9) // will skip </station>
				{
					// Save station to m_stationAr.
					if(m_tempStation.m_name != _T("") && m_tempStation.m_url != _T("") )
					{
						index = m_stationAr.Add(m_tempStation);
						m_tempStation.Zero();						
					}
				}
				openPos = 0;
				continue;
			}
			// Add to name.
			name += ch;
		}
	}
	// Done.
	file.Close();

	// Results.
	int sz = m_stationAr.GetSize(); // 28,395 stations, 3 seconds to parse release, 36 seconds debug, 
	if(!sz)	
	{
		errStr = _T("CRadioBrowseDb::LoadDb - got no stations.\n");
		TRACE(errStr);
		return 0;
	}
	return sz;
}

// Save xml name value pair to m_tempStation.
void CRadioBrowseDb::SaveNameValue(CString & name, CString & value)
{
	// m_tempStation
	if(name == _T("name"))
	{
		m_tempStation.m_name = value;
	}
	else if(name == _T("url"))
	{
		m_tempStation.m_url = value;
	}
	else if(name == _T("url_resolved"))
	{
		m_tempStation.m_url = value;
	}
	else if(name == _T("tags"))					// Comma seperated 
	{
		m_tempStation.m_tags =  value;
	}
	else if(name == _T("country"))
	{
		if(m_tempStation.m_country == _T(""))	// no overwrite country code.
			m_tempStation.m_country = value;
	}
	else if(name == _T("countrycode"))			// overwrite country str.
	{
		m_tempStation.m_country = value;
	}
	else if(name == _T("state"))
	{
		m_tempStation.m_state = value;
	}
	// Many comma seperated - "englisch,english,deutsch german,deutsch,german".
	//  It's ok as FindNoCase() will find country within string.
	else if(name == _T("language"))
	{
		if(m_tempStation.m_language == _T(""))	// no overwrite languagecode.
			m_tempStation.m_language = value;
	}
	else if(name == _T("languagecodes"))		// overwrite language - none?
	{
		m_tempStation.m_language = value;
	}	
	else if(name == _T("votes"))
	{
		m_tempStation.m_nVotes = _wtoi(value);	//_wtoi unicode, _ttoi TCHAR
	}
	else if(name == _T("clickcount"))
	{
		m_tempStation.m_nClicks = _wtoi(value);
	}
}

// Get CountryCodes data from RadioBrowse_CC.txt.
int CRadioBrowseDb::BuildCountryCodesArray()
{
	// Country codes could be gotten from the m_stationAr as
	// tags and states are, but using the text file allows
	// modifying country Name strings to better suit a dialog
	// dropdown list. The country Codes are standard ISO 3166
	// 2 letter codes.

	m_countryCodeAr.RemoveAll();
	CCountryCode cc;

	// Get current directory.
	CString curDir;
	curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	
	CString filePath = curDir;
	filePath += _T("\\RadioBrowse_CC.txt");

	// Open filePath.
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CRadioBrowseDb::BuildCountryCodesArray - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return 0;
	}

	// Parse countryCodes into array.
	CString fileStr;
	int pos;
	while(file.ReadString(fileStr))
	{
		// Skip empty lines.
		if(fileStr == _T(""))
			continue;

		// Skip comments or unused contries "//"
		if(fileStr.Find(_T("//")) != -1)
			continue;

		// Zero.
		cc.Zero();
		int len = fileStr.GetLength();

		// name,code
		pos = fileStr.Find(_T(','), 0);
		if(pos == -1)
		{
			TRACE(_T("CRadioBrowseDb::BuildCountryCodesArray - no comma in string.\n"));
			continue;
		}
		// Get pair.
		cc.m_name = fileStr.Left(pos);
		cc.m_code = fileStr.Right(len - pos - 1);

		// Add to array.
		m_countryCodeAr.Add(cc);
	}
	// Done reading.
	file.Close();

	// Check results.
	int sz = m_countryCodeAr.GetSize(); // 249 country codes full.
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::BuildCountryCodesArray - no country codes gotten.\n"));
		return 0;
	}
	// OK.
	return sz;
}

// Set the each CCountryCode's m_isUsed member in 
// the m_countryCodeAr per m_stationAr data.
int CRadioBrowseDb::SetCountryCodesUsed()
{
	// Ensure arrays are built.
	int sz = m_countryCodeAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::SetCountryCodesUsed - no m_countryCodeAr.\n"));
		return 0;
	}
	sz = m_stationAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::SetCountryCodesUsed - no m_stationAr.\n"));
		return 0;
	}
	// Run through m_stationAr.
	int nSet = 0;
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Set.
		if(m_countryCodeAr.SetUsed(station.m_country))
			nSet++;
	}
	return nSet;
}

//---------------------------------------------------

// Get LanguageCodes data from RadioBrowse_LC.txt.
int CRadioBrowseDb::BuildLanguageCodesArray()
{
	// This is identical to CountryCodes above, with one
	// difference being the RB data does not use the two
	// letter code. Instead. language strings are used, 
	// some of which are not standard iso 639.
	//
	// We attempt to convert to those strings to codes with
	// the xxxx function, then treat Languages the same as
	// Countries.

	m_languageCodeAr.RemoveAll();
	CLanguageCode lc;

	// Get current directory.
	CString curDir;
	curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	
	CString filePath = curDir;
	filePath += _T("\\RadioBrowse_LC.txt");

	// Open filePath.
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CRadioBrowseDb::BuildLanguageCodesArray - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return 0;
	}

	// Parse Language Codes into array.
	CString fileStr;
	int pos;
	while(file.ReadString(fileStr))
	{
		// Skip empty lines.
		if(fileStr == _T(""))
			continue;

		// Skip comments or unused languages "//"
		if(fileStr.Find(_T("//")) != -1)
			continue;

		// Zero.
		lc.Zero();
		int len = fileStr.GetLength();

		// name,code
		pos = fileStr.Find(_T(','), 0);
		if(pos == -1)
		{
			TRACE(_T("CRadioBrowseDb::BuildLanguageCodesArray - no comma in string.\n"));
			continue;
		}
		// Get pair.
		lc.m_name = fileStr.Left(pos);
		lc.m_code = fileStr.Right(len - pos - 1);

		// Add to array.
		m_languageCodeAr.Add(lc);
	}
	// Done reading.
	file.Close();

	// Check results.
	int sz = m_languageCodeAr.GetSize(); // 139 Language codes.
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::BuildLanguageCodesArray - no country codes gotten.\n"));
		return 0;
	}
	// OK.
	return sz;
}

// Set the each CLanguageCode's m_isUsed member in 
// the m_languageCodeAr per m_stationAr data.
int CRadioBrowseDb::SetLanguageCodesUsed()
{
	// Ensure arrays are built.
	int sz = m_languageCodeAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::SetLanguageCodesUsed - no m_languageCodeAr.\n"));
		return 0;
	}
	sz = m_stationAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::SetLanguageCodesUsed - no m_stationAr.\n"));
		return 0;
	}
	// Run through m_stationAr.
	int nSet = 0;
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Need string.
		if(station.m_language.IsEmpty())
			continue;

		// Set.
		if(m_languageCodeAr.SetUsed(station.m_language))
			nSet++;
	}
	return nSet;
}

// Try to fix up language code.
void CRadioBrowseDb::RepaireLanguageCodes()
{
	// Ensure arrays are built.
	int sz = m_languageCodeAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::RepaireLanguageCodes - no m_languageCodeAr.\n"));
		return;
	}
	sz = m_stationAr.GetSize();
	if(!sz)
	{
		TRACE(_T("CRadioBrowseDb::SetLanguageCodesUsed - no m_stationAr.\n"));
		return;
	}
	// Run through m_stationAr.
	CString langCode;
	int nRepaired = 0;
	int nOk = 0;
	int nEmpty = 0;
	int nFailed = 0;
	int ret = 0;
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Must have string.
		if(station.m_language.IsEmpty())
		{
			nEmpty ++;
			continue;
		}

		// Repair as needed.
		ret = GetRepairedLanguageCode(station.m_language, langCode);
		// Repaired
		if(ret == 1)
		{
			station.m_language = langCode;
			nRepaired ++;
		}
		// OK as is.
		else if(ret == 2)
			nOk ++;
		// No fix.
		else
			nFailed ++;
	}
	// Results: 
	//	nEmpty = 4617, nRepaired = 21984, nOk = 0, nFailed = 1916, sz = 28517: Good
	return;
}


// Currently RB xml data does not use the 2 letter iso 639 language code.
// This attempts to get the 2 letter code.
// Returns 0 if cannot fix, 1 if fixed, 2 if currently ok.
int CRadioBrowseDb::GetRepairedLanguageCode(const CString & name, CString & code)
{
	// Search Language Ar for codes.
	int sz = m_languageCodeAr.GetSize();
	int nameLen = name.GetLength();
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CLanguageCode & lc = m_languageCodeAr[i];

		// Check if valid.
		if(nameLen == 2)
		{ 
			if(name == lc.m_code)
				return 2;
			else
				continue;
		}
		// Else try to find.
		else
		{
			// If proper code name is somewhere in name param.
			//  Note some RB lang names are comma seperated lists 
			//  of languages - nonsensical?
			if(FindNoCase(name, lc.m_name) != -1)
			{
				code = lc.m_code;
				return TRUE;
			}
		}
	}
	return 0;
}

// Build m_tagAr with tags from m_stationAr stations.
int CRadioBrowseDb::BuildTagArray(int minStationCount)
{
	CString tagStr;
	CTag tag;

	// Prep m_tagAr - AddTagNoDupSetCount() sets with station tags.
	m_tagAr.RemoveAll();
	m_tagAr.SetSize(0, 128);

	// Run through m_stationAr.
	int sz = m_stationAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Get comma seperated tag string.
		tagStr = station.m_tags;
		if(tagStr == _T(""))
			continue;

		// Parse comma separated tags in value string.
		TCHAR comma = _T(',');
		CString node;
		int nPos = 0;

		while(tagStr.GetLength() > 0)
		{
			nPos = tagStr.Find(comma);
			if(nPos == -1)
			{
				tagStr.TrimLeft();
				tagStr.TrimRight();
				AddTagNoDupSetCount(tagStr);
				tagStr.Empty();
			}
			else
			{
				node = tagStr.Left(nPos);
				node.TrimLeft();
				node.TrimRight();
				AddTagNoDupSetCount(node);
				// Get remainder of string after comma.
				tagStr = tagStr.Mid(nPos + 1);
			}
		}
	}
	// Check results;
	sz = m_tagAr.GetSize(); // 7621 tags

	// Copy only stations with minStationCount to temp array.
	CTagArray tagAr;
	tagAr.SetSize(0, 128);

	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CTag & tag = m_tagAr[i];
		if(tag.m_nStations >= minStationCount)
			tagAr.Add(tag);
	}
	// Check results;
	sz = tagAr.GetSize(); //  tags

	// Sort the array ascending.
	tagAr.SortTagName(TRUE);

	// Copy temp array to m_tagAr member.
	m_tagAr.RemoveAll();
	m_tagAr.Copy(tagAr);

	// Get final size.
	sz = m_tagAr.GetSize(); //  tags

	/* dbg
	for(i = 0; i < sz; i++)
	{
		tag = m_tagAr[i];
	}*/

	// OK.
	return sz;
}

void CRadioBrowseDb::AddTagNoDupSetCount(CString & tagStr)
{
	// Check if tag exists.
	int sz = m_tagAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		CTag & tag = m_tagAr[i];
		if(tag.m_name == tagStr)
		{
			// Add count
			tag.m_nStations ++;
			// Done.
			return;
		}
	}
	// New tag, add to array & set count.
	CTag tag;
	tag.m_name = tagStr;
	tag.m_nStations = 1;
	m_tagAr.Add(tag);
}

// Build m_statesAr with states from m_stationAr stations.
// The state's country is included, so the search dialog
// can pull out states per selected country. 
int CRadioBrowseDb::BuildStatesArray(int minStationCount)
{
	CString stateStr, countryStr;
	CState state;

	// Prep m_stateAr - AddStateNoDupSetCount() sets with station state.
	m_stateAr.RemoveAll();
	m_stateAr.SetSize(0, 8);

	// Run through m_stationAr.
	int sz = m_stationAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Ensure strings exist.
		if(station.m_state == _T("")   ||
		   station.m_country == _T("") )
			continue;

		// Get strings.
		stateStr = station.m_state;
		countryStr = station.m_country;

		// Add to array.
		AddStateNoDupSetCount(stateStr, countryStr);
	}
	// Check results;
	sz = m_stateAr.GetSize(); // 2675 states

	// Sort the array ascending - by country then state.
	m_stateAr.SortStateName(TRUE);

	/* dbg
	for(i = 0; i < sz; i++)
	{
		state = m_stateAr[i];
	}*/
	// OK.
	return sz;
}

void CRadioBrowseDb::AddStateNoDupSetCount(CString & stateStr, CString & countryStr)
{
	// Check if state exists.
	int sz = m_stateAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		CState & state = m_stateAr[i];
		if(state.m_name == stateStr		 &&
		   state.m_country == countryStr )
		{
			// Add count
			state.m_nStations ++;
			// Done.
			return;
		}
	}
	// New state, add to array & set count.
	CState state;
	state.m_name = stateStr;
	state.m_country = countryStr;
	state.m_nStations = 1;
	m_stateAr.Add(state);
}

// Status String message to dialog during threaded opeation.
void CRadioBrowseDb::ShowDialogStatus(CString strStatus)
{
	// Set CApp string buff and Post message to dialog.
	if(m_hwndDlg)
	{
		((CDsLavApp*)AfxGetApp())->SetThreadMessage(strStatus);
		PostMessage(m_hwndDlg, WM_USER_THREAD_STATUS_STRING, 0, 0);

		// Let the UI thread get the string else jumbled output.
		::Sleep(250);
	}
}

// Progress control position message to dialog.
void CRadioBrowseDb::SetProgressPos(int pos)
{
	// Post to dialog.
	if(m_hwndDlg)
		PostMessage(m_hwndDlg, WM_USER_THREAD_PROGRESS_POS, 0, (LPARAM)pos);
}

// Check if user has canceled the update.
BOOL CRadioBrowseDb::UpdateCanceled(CString dbgMsg)
{
	// Ensure valid event member.
	if(!m_pEvent)
		return FALSE;

	// If User has aborted update.
	if(::WaitForSingleObject(m_pEvent->m_hObject, 0) == WAIT_OBJECT_0) 
	{
		// Post Status msg.
		ShowDialogStatus(_T("Canceling Update."));
		// Trace.
 		TRACE(_T("Canceling Update - %s\n"), dbgMsg);
		return TRUE;
	}
	return FALSE;
}

// Get RB servers - usually 4
//  filepath is writable temp file.
//  Store results in serversAr.
int CRadioBrowseDb::GetServers(CString filePath, CString & errStr, CStringArray & serversAr)
{
	serversAr.RemoveAll();
	int nItemsRead = 0;
	CCurlinet inet;
	CString strErr;

	// See: https://nl1.api.radio-browser.info/#Server_mirrors
	CString query = _T("http://nl1.api.radio-browser.info/json/servers");

	// Fetch - returns json data - stores in filePath temp file.
	if(!inet.GetInternetDataUnicode(query, filePath, strErr))
	{
		// Connection failed - simpler msg.
		if(strErr.Find(_T("Are you connected to the internet"), 0) != -1)
		{
			errStr = _T("Radio Browser server not available - are you connected to the internet?");
			return 0;
		}
		errStr.Format(_T("Could not connect to Radio Browser server: %s"), strErr);
		TRACE(_T("CRadioBrowseDb GetServers - GetInternetData failed.\n"));
		return 0;
	}

	// Open filePath.
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		errStr.Format(_T("CRadioBrowseDb GetServers - can not open file %s, error = %u\n"), 
							filePath, e.m_cause );
		TRACE(errStr);		
		return 0;
	}
	/* typical json data:
	[{"ip":"2001:19f0:5001:32a4:5400:2ff:fe37:75c2","name":"nl1.api.radio-browser.info"},
	 {"ip":"2001:19f0:6801:19d7:5400:2ff:fe71:bdb7","name":"fr1.api.radio-browser.info"},
	 {"ip":"2a03:4000:37:42:c4fe:4cff:fea7:8941","name":"de1.api.radio-browser.info"},
	 {"ip":"95.179.139.106","name":"nl1.api.radio-browser.info"},
	 {"ip":"45.77.62.161","name":"fr1.api.radio-browser.info"},
	 {"ip":"91.132.145.114","name":"de1.api.radio-browser.info"}]
	*/

	// Verify file size for CString below.
	ULONGLONG fileLen = file.GetLength();
	if(fileLen > 2048)
	{
		file.Close();
		errStr.Format(_T("CRadioBrowseDb GetServers - file %s is too long.\n"), filePath);
		TRACE(errStr);
		return 0;
	}

	// Parse into m_rbSeverAr.
	//
	CString strServer, fileStr, temp;
	CString https = _T("https://");
	int index = 0;
	int len = 0, nChars = 0;

	// Read the disk file into fileStr in one gulp - no newlines in json.
	while(file.ReadString(fileStr))
	{
		len = fileStr.GetLength(); //428
		nChars += len;

		// Parse server strings.
		CString term = _T("api.radio-browser.info");
		int nPos = 0, nStart = 0, nEnd = 0;

		nPos = fileStr.Find(term, nEnd);
		while(nPos != -1)
		{
			nStart = nPos - 4;
			nEnd = nPos + 22;

			// Verify bounds;
			if(nStart < 0 || nEnd >= len)
			{
				file.Close();
				errStr = _T("CRadioBrowseDb GetServers - parsing error.\n");
				TRACE(errStr);
				return 0;
			}

			// Get mid.
			temp = fileStr.Mid(nStart, nEnd - nStart);

			// Build url.
			strServer = https;
			strServer += temp;

			// Ensure no duplicates - usually 2 of each.
			int sz = serversAr.GetSize();
			BOOL exists = FALSE;
			for(int i = 0; i < sz; i++)
			{
				if(serversAr[i] == strServer)
				{
					exists = TRUE;
					break;
				}
			}
			if(!exists)
			{
				// Add to list
				serversAr.Add(strServer);
			}
			// Get next.
			nPos = fileStr.Find(term, nEnd);
		}
	}
	// Done with file.
	file.Close();

	// Check results.
	int sz = serversAr.GetSize();
	if(!sz)
	{
		errStr = _T("ERROR CRadioBrowseDb GetServers - no server urls gotten.\n");
		TRACE(errStr);
		return 0;
	}
	// dbg - print to output.
	for(int i = 0; i < sz; i++)
	{
		CString s;
		s.Format(_T("%s\n"), serversAr[i]);
		TRACE(s);
		/*
		https://nl1.api.radio-browser.info
		https://fr1.api.radio-browser.info
		https://de1.api.radio-browser.info
		*/
	}
	return sz;
}


////////////////////////////////////////////////////////////////////
// Search RB data.
//  Brute force search is < 1 second for 28363 stations
//  with 2.3 GH cpu and 4GB ram box.
int CRadioBrowseDb::SearchDb()
{
	// Search Logic:
	//  All search criteria entered by user must be
	//  found for the station to qualify.

	// Prep.
	m_searchResultsAr.RemoveAll();
	m_searchResultsAr.SetSize(0, 128);
	int nCriteriaNeeded = GetCriteriaCount();
	int nCriteriaFound = 0;
	if(nCriteriaNeeded == 0)
		return 0;

	// Search brute force.
	int sz = m_stationAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CStation & station = m_stationAr[i];

		// Zero for this station.
		nCriteriaFound = 0;

		// Name - "KCRW 89.9 Santa Monica, CA"
		if(m_bName && FindNoCase(station.m_name, m_searchName) != -1)
			nCriteriaFound ++;

		// Country Code - "us"
		if(m_bCountry && FindNoCase(station.m_country, m_searchCountry) != -1)
			nCriteriaFound ++;

		// State - 
		if(m_bState && FindNoCase(station.m_state, m_searchState) != -1)
			nCriteriaFound ++;

		// Language - german, english, deutsch
		if(m_bLanguage && FindNoCase(station.m_language, m_searchLanguage) != -1)
			nCriteriaFound ++;

		// Tags
		if(m_bTags && station.m_tags != _T("") )
		{
			// Search station's m_tag CS string for each user supplied tag
			int sz = m_searchTags.GetSize();
			for(int i = 0; i < sz; i++)
			{
				CString & tag = m_searchTags[i];
				if(FindNoCase(station.m_tags, tag) != -1)
				{
					nCriteriaFound ++;
					// Station qualifies - done.
					break;
				}
			}			
		}
		// Clicks
		if(m_bClicks && station.m_nClicks >= m_nClicksMinimum)
			nCriteriaFound ++;

		// Votes
		if(m_bVotes && station.m_nVotes >= m_nVotesMinimum)
			nCriteriaFound ++;

		// If all critera met for this station.
		if(nCriteriaFound == nCriteriaNeeded)
		{
			m_searchResultsAr.Add(station);
		}
	}
	// Check results.
	int nResults = m_searchResultsAr.GetSize();
	return nResults;
}

// CString Find() replacement - no case.
int CRadioBrowseDb::FindNoCase(const CString & str, CString & sub)
{
	m_strFull = str;
	m_strSub = sub;
	m_strFull.MakeUpper();
	m_strSub.MakeUpper();
	return m_strFull.Find(m_strSub);
}

// Prep for search.
void CRadioBrowseDb::ZeroSearchCritera()
{
	m_searchResultsAr.RemoveAll();

	// Critera
	m_bName = FALSE;
	m_bCountry = FALSE;
	m_bState = FALSE;
	m_bLanguage = FALSE;
	m_bTags = FALSE;
	m_bClicks = FALSE;
	m_bVotes = FALSE;

	m_searchName = _T("");
	m_searchCountry = _T("");
	m_searchState = _T("");
	m_searchLanguage = _T("");
	m_searchTags.RemoveAll();
	m_nClicksMinimum = 0;
	m_nVotesMinimum = 0;
}

// Count search criteria to be found for a station.
int CRadioBrowseDb::GetCriteriaCount()
{
	int count = 0;

	if(m_bName)
		count ++;
	if(m_bCountry)
		count ++;
	if(m_bState)
		count ++;
	if(m_bLanguage)
		count ++;
	if(m_bTags)
		count ++;
	if(m_bClicks)
		count ++;
	if(m_bVotes)
		count ++;
	return count;
}

// Called from CRbSearchDlg::OnColumnclickResultsList().
void CRadioBrowseDb::SortResultsArray(BOOL ascend, BOOL name, BOOL votes, BOOL clicks)
{
	int sz = m_searchResultsAr.GetSize();
	if(!sz)
		return;
	// Sort per param.
	m_searchResultsAr.SortStationsAr(ascend, name, votes, clicks);
}

// Called from CDsLavDoc::ProccessRbSearchResults() post
// search results import.
void CRadioBrowseDb::ZeroSearchResultsAndCritera()
{
	m_searchResultsAr.RemoveAll();
	ZeroSearchCritera();
}

/////////////////////////////////////////////////////////////////////
// Serialize

// Read all CRadioBrowseDb arrays from single archive.
// Called from CDsLavDoc::ReadRadioBrowserData().
void CRadioBrowseDb::Read(CArchive & ar, int & version)
{
	int sz, i;

	// Read m_stationAr from archive - 28363 stations.
	m_stationAr.RemoveAll();
	m_stationAr.SetSize(0, 512);
	CStation station;
	ar >> sz;
	for(i = 0; i < sz; i++)
	{
		station.Zero();
		station.Read(ar, version);
		m_stationAr.Add(station);
	}
	// Read m_countryCodeAr - 249 countries, 210 used.
	m_countryCodeAr.RemoveAll();
	m_countryCodeAr.SetSize(0, 64);
	CCountryCode cc;
	ar >> sz;
	for(i = 0; i < sz; i++)
	{
		cc.Zero();
		cc.Read(ar, version);
		m_countryCodeAr.Add(cc);
	}
	// Read m_tagAr - 113 tags saved of 7621 tags in station Array.
	m_tagAr.RemoveAll();
	m_tagAr.SetSize(0, 16);
	CTag tag;
	ar >> sz;
	for(i = 0; i < sz; i++)
	{
		tag.Zero();
		tag.Read(ar, version);
		m_tagAr.Add(tag);
	}
	// Read m_stateAr - 2675 states in many countries;
	m_stateAr.RemoveAll();
	m_stateAr.SetSize(0, 256);
	CState state;
	ar >> sz;
	for(i = 0; i < sz; i++)
	{
		state.Zero();
		state.Read(ar, version);
		m_stateAr.Add(state);
	}
	// Read m_languageCodeAr - 139 language codes;
	m_languageCodeAr.RemoveAll();
	m_languageCodeAr.SetSize(0, 32);
	CLanguageCode lc;
	ar >> sz;
	for(i = 0; i < sz; i++)
	{
		lc.Zero();
		lc.Read(ar, version);
		m_languageCodeAr.Add(lc);
	}
}

// Write all CRadioBrowseDb arrays to single archive.
// Called from CDsLavDoc::WriteRadioBrowserData().
void CRadioBrowseDb::Write(CArchive & ar, int & version)
{
	// Write m_stationAr.
	int sz = m_stationAr.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		m_stationAr[i].Write(ar, version);
	}

	// Write m_countryCodeAr.
	sz = m_countryCodeAr.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		m_countryCodeAr[i].Write(ar, version);
	}

	// Write m_tagAr.
	sz = m_tagAr.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		m_tagAr[i].Write(ar, version);
	}

	// Write m_stateAr;
	sz = m_stateAr.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		m_stateAr[i].Write(ar, version);
	}

	// Write m_languageCodeAr;
	sz = m_languageCodeAr.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		m_languageCodeAr[i].Write(ar, version);
	}
}



///////////////////////////////////////////////////
// CStation
CStation::CStation()
{
	Zero();
}
CStation::~CStation()
{
}
CStation::CStation(const CStation & station)
{
	Copy(station);
}
void CStation::operator=(const CStation & station)
{
	Copy(station);
}
BOOL CStation::operator==(const CStation & station)
{
	if(	m_name		== station.m_name	  &&
		m_url		== station.m_url	  &&
		m_country	== station.m_country  &&
		m_state		== station.m_state	  &&
		m_language	== station.m_language &&
		m_tags		== station.m_tags	  &&
		m_nVotes	== station.m_nVotes   &&
		m_nClicks	== station.m_nClicks  )
		return TRUE;
	else
		return FALSE;
}
void CStation::Copy(const CStation & station)
{
	m_name		= station.m_name;
	m_url		= station.m_url;
	m_country	= station.m_country;
	m_state		= station.m_state;
	m_language	= station.m_language;
	m_tags		= station.m_tags;
	m_nVotes	= station.m_nVotes;
	m_nClicks	= station.m_nClicks;
}
void CStation::Zero()
{
	m_name		= _T("");
	m_url		= _T("");
	m_country	= _T("");
	m_state		= _T("");
	m_language	= _T("");
	m_tags		= _T("");
	m_nVotes	= 0;
	m_nClicks	= 0;
}

// Serialize a CStation.
void CStation::Read(CArchive & ar, int & version)
{
	ar >> m_name;
	ar >> m_url;
	ar >> m_country;
	ar >> m_state;
	ar >> m_language;
	ar >> m_tags;
	ar >> m_nVotes;
	ar >> m_nClicks;
}
void CStation::Write(CArchive & ar, int & version)
{
	ar << m_name;
	ar << m_url;
	ar << m_country;
	ar << m_state;
	ar << m_language;
	ar << m_tags;
	ar << m_nVotes;
	ar << m_nClicks;
}
///////////////////////////////////////////////////
// CStationArray

/*	// Sort Stations.
	void SortStation(BOOL name, BOOL votes, BOOL clicks);
protected:
	// All ascending.
	static int CompareStationName(const void *, const void *);
	static int CompareStationVotes(const void *, const void *);
	static int CompareStationClicks(const void *, const void *);*/

// Sort Stations, all ascending.
//
// Sort station array by either name, votes, or clicks.
void CStationAr::SortStationsAr(BOOL ascend, BOOL name, BOOL votes, BOOL clicks)
{
	if(ascend)
	{
		if(name)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationName);
		else if(votes)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationVotes);
		else if(clicks)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationClicks);
	}
	else
	{
		if(name)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationNameDescend);
		else if(votes)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationVotesDescend);
		else if(clicks)
			qsort(GetData(), GetSize(), sizeof(CStation), CompareStationClicksDescend);
	}
}
// Qsort Names.
int CStationAr::CompareStationName(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	return item1->m_name.CompareNoCase(item2->m_name);
}
// Qsort Names descend.
int CStationAr::CompareStationNameDescend(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	return item2->m_name.CompareNoCase(item1->m_name);
}

// Qsort Votes.
int CStationAr::CompareStationVotes(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	int ret = 0;
	// Sort Votes.
	if(item1->m_nVotes > item2->m_nVotes)
		ret = 1;
	else if(item1->m_nVotes < item2->m_nVotes)
		ret = -1;
	// If equal, sort the name (may as well).
	else
		ret = item1->m_name.CompareNoCase(item2->m_name);
	return ret;
}
// Qsort Votes descend.
int CStationAr::CompareStationVotesDescend(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	int ret = 0;
	// Sort Votes.
	if(item1->m_nVotes > item2->m_nVotes)
		ret = -1;
	else if(item1->m_nVotes < item2->m_nVotes)
		ret = 1;
	// If equal, sort the name (may as well).
	else
		ret = item2->m_name.CompareNoCase(item1->m_name);
	return ret;
}

// Qsort Clicks.
int CStationAr::CompareStationClicks(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	int ret = 0;
	// Sort Clicks.
	if(item1->m_nClicks > item2->m_nClicks)
		ret = 1;
	else if(item1->m_nClicks < item2->m_nClicks)
		ret = -1;
	// If equal, sort the name (may as well).
	else
		ret = item1->m_name.CompareNoCase(item2->m_name);
	return ret;
}
// Qsort Clicks descend.
int CStationAr::CompareStationClicksDescend(const void * arg1, const void * arg2)
{
	// Get the CStation items.
	CStation * item1 = (CStation *)arg1;
	CStation * item2 = (CStation *)arg2;
	int ret = 0;
	// Sort Clicks.
	if(item1->m_nClicks > item2->m_nClicks)
		ret = -1;
	else if(item1->m_nClicks < item2->m_nClicks)
		ret = 1;
	// If equal, sort the name (may as well).
	else
		ret = item2->m_name.CompareNoCase(item1->m_name);
	return ret;
}
// Serialize array
void CStationAr::Read(CArchive & ar, int & version)
{
	// Read from archive.
	CStation station;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		// Read into temp CStation.
		station.Read(ar, version);

		// Add to array.
		Add(station);
	}
}
void CStationAr::Write(CArchive & ar, int & version)
{
	// Write to archive.
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		CStation & station = ElementAt(i);
		station.Write(ar, version);
	}
}

///////////////////////////////////////////////////
// CCountryCode
CCountryCode::CCountryCode()
{
	Zero();
}
CCountryCode::~CCountryCode()
{
}
CCountryCode::CCountryCode(const CCountryCode & cc)
{
	Copy(cc);
}
void CCountryCode::operator=(const CCountryCode & cc)
{
	Copy(cc);
}
BOOL CCountryCode::operator==(const CCountryCode & cc)
{
	if(m_name == cc.m_name && m_code == cc.m_code) // no m_isUsed
		return TRUE;
	else
		return FALSE;
}
void CCountryCode::Copy(const CCountryCode & cc)
{
	m_name = cc.m_name;
	m_code = cc.m_code;
	m_isUsed = cc.m_isUsed;
}
void CCountryCode::Zero()
{
	m_name = _T("");
	m_code = _T("");
	m_isUsed = FALSE;
}
// Serialize
void CCountryCode::Read(CArchive & ar, int & version)
{
	ar >> m_name;
	ar >> m_code;
	ar >> m_isUsed;
}
void CCountryCode::Write(CArchive & ar, int & version)
{
	ar << m_name;
	ar << m_code;
	ar << m_isUsed;
}
///////////////////////////////////////////////
// CCountryCodeArray - an array of CCountries.

// Set a CCountryCode's m_isUsed per param.
// Return TRUE if setting to used, FALSE otherwise.
BOOL CCountryCodeArray::SetUsed(CString & countryCode)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CCountryCode & cc = ElementAt(i);

		// If code exists.
		if(cc.m_code == countryCode)
		{
			// If not set.
			if(cc.m_isUsed == FALSE)
			{
				cc.m_isUsed = TRUE;
				return TRUE;
			}
			// Already set.
			else
				return FALSE;
		}
	}
	// No match.
	return FALSE;
}
// Set all CCountryCode's m_isUsed members to FALSE.
void CCountryCodeArray::SetAllNotUsed()
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CCountryCode & cc = ElementAt(i);
		cc.m_isUsed = FALSE;
	}
}
// Count all CCountryCodes in use.
int CCountryCodeArray::CountUsed()
{
	int count = 0;
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CCountryCode & cc = ElementAt(i);
		if(cc.m_isUsed)
			count ++;
	}
	return count;
}

// Serialize array
void CCountryCodeArray::Read(CArchive & ar, int & version)
{
	// Read from archive.
	CCountryCode cc;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		// Read into temp CCountryCode.
		cc.Read(ar, version);

		// Add to array.
		Add(cc);
	}
}
void CCountryCodeArray::Write(CArchive & ar, int & version)
{
	// Write to archive.
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		CCountryCode & cc = ElementAt(i);
		cc.Write(ar, version);
	}
}

///////////////////////////////////////////////////
// CLanguageCode
CLanguageCode::CLanguageCode()
{
	Zero();
}
CLanguageCode::~CLanguageCode()
{
}
CLanguageCode::CLanguageCode(const CLanguageCode & lc)
{
	Copy(lc);
}
void CLanguageCode::operator=(const CLanguageCode & lc)
{
	Copy(lc);
}
BOOL CLanguageCode::operator==(const CLanguageCode & lc)
{
	if(m_name == lc.m_name && m_code == lc.m_code) // no m_isUsed
		return TRUE;
	else
		return FALSE;
}
void CLanguageCode::Copy(const CLanguageCode & lc)
{
	m_name = lc.m_name;
	m_code = lc.m_code;
	m_isUsed = lc.m_isUsed;
}
void CLanguageCode::Zero()
{
	m_name = _T("");
	m_code = _T("");
	m_isUsed = FALSE;
}
// Serialize
void CLanguageCode::Read(CArchive & ar, int & version)
{
	ar >> m_name;
	ar >> m_code;
	ar >> m_isUsed;
}
void CLanguageCode::Write(CArchive & ar, int & version)
{
	ar << m_name;
	ar << m_code;
	ar << m_isUsed;
}
///////////////////////////////////////////////
// CLanguageCodeArray - an array of CLanguageCode.

// Set a CLanguageCode's m_isUsed per param.
// Return TRUE if setting to used, FALSE otherwise.
BOOL CLanguageCodeArray::SetUsed(CString & languageCode)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CLanguageCode & lc = ElementAt(i);

		// If code exists.
		if(lc.m_code == languageCode)
		{
			// If not set.
			if(lc.m_isUsed == FALSE)
			{
				lc.m_isUsed = TRUE;
				return TRUE;
			}
			// Already set.
			else
				return FALSE;
		}
	}
	// No match.
	return FALSE;
}
// Set all CLanguageCode's m_isUsed members to FALSE.
void CLanguageCodeArray::SetAllNotUsed()
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CLanguageCode & lc = ElementAt(i);
		lc.m_isUsed = FALSE;
	}
}
// Count all CLanguageCodes in use.
int CLanguageCodeArray::CountUsed()
{
	int count = 0;
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref.
		CLanguageCode & lc = ElementAt(i);
		if(lc.m_isUsed)
			count ++;
	}
	return count;
}

// Get 2 letter code for a language name.
BOOL CLanguageCodeArray::GetLanguageCodeCode(const CString & name, CString & code)
{
	// Search Ar.
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		CLanguageCode & lc = GetAt(i);
		if(lc.m_name == name)
		{
			code = lc.m_code;
			return TRUE;
		}
	}
	return FALSE;
}
/*
// Currently RB xml data does not use the 2 letter iso 639 language code.
// Instead, apparently user entered strings ar given.
// This attempts to get the 2 letter code.
// Returns true if valid code exists or is gotten. 
BOOL CLanguageCodeArray::GetRepairedLanguageCode(const CString & name, CString & code)
{
	// Search Ar.
	int sz = GetSize();
	int nameLen = name.GetLength();
	for(int i = 0; i < sz; i++)
	{
		// Get ref.
		CLanguageCode & lc = GetAt(i);

		// Check if valid.
		if(nameLen == 2)
		{ 
			if(name == lc.m_code)
				return TRUE;
		}
		// Else try to find.
		else
		{
			if(FindNoCase(name, lc.m_name)
			{
				code = lc.m_code;
				return TRUE;
			}
		}
	}
	return FALSE;
}*/



// Serialize array
void CLanguageCodeArray::Read(CArchive & ar, int & version)
{
	// Read from archive.
	CLanguageCode lc;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		// Read into temp CLanguageCode.
		lc.Read(ar, version);

		// Add to array.
		Add(lc);
	}
}
void CLanguageCodeArray::Write(CArchive & ar, int & version)
{
	// Write to archive.
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		CLanguageCode & lc = ElementAt(i);
		lc.Write(ar, version);
	}
}


///////////////////////////////////////////////////
// CTag
CTag::CTag()
{
	Zero();
}
CTag::~CTag()
{
}
CTag::CTag(const CTag & tag)
{
	Copy(tag);
}
void CTag::operator=(const CTag & tag)
{
	Copy(tag);
}
BOOL CTag::operator==(const CTag & tag)
{
	if(m_name == tag.m_name && m_nStations == tag.m_nStations)
		return TRUE;
	else
		return FALSE;
}
void CTag::Copy(const CTag & tag)
{
	m_name = tag.m_name;
	m_nStations = tag.m_nStations;
}
void CTag::Zero()
{
	m_name = _T("");
	m_nStations = 0;
}
// Serialize
void CTag::Read(CArchive & ar, int & version)
{
	ar >> m_name;
	ar >> m_nStations;
}
void CTag::Write(CArchive & ar, int & version)
{
	ar << m_name;
	ar << m_nStations;
}

//////////////////////////////////////////////////////
// CTagArray

// Serialize array
void CTagArray::Read(CArchive & ar, int & version)
{
	// Read from archive.
	CTag tag;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		// Read into temp CCountryCode.
		tag.Read(ar, version);

		// Add to array.
		Add(tag);
	}
}
void CTagArray::Write(CArchive & ar, int & version)
{
	// Write to archive.
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		CTag & tag = ElementAt(i);
		tag.Write(ar, version);
	}
}

// Sort tag array by m_name per ascend param.
void CTagArray::SortTagName(BOOL ascend)
{
	if(ascend)
		qsort(GetData(), GetSize(), sizeof(CTag), CompareTagNameAscend);
	else
		qsort(GetData(), GetSize(), sizeof(CTag), CompareTagNameDescend);
}
// Qsort ascending.
int CTagArray::CompareTagNameAscend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CTag * item1 = (CTag *)arg1;
	CTag * item2 = (CTag *)arg2;
	return item1->m_name.CompareNoCase(item2->m_name);
}
// Qsort descending.
int CTagArray::CompareTagNameDescend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CTag * item1 = (CTag *)arg1;
	CTag * item2 = (CTag *)arg2;
	return item2->m_name.CompareNoCase(item1->m_name);
}

///////////////////////////////////////////////////
// CState
CState::CState()
{
	Zero();
}
CState::~CState()
{
}
CState::CState(const CState & state)
{
	Copy(state);
}
void CState::operator=(const CState & state)
{
	Copy(state);
}
BOOL CState::operator==(const CState & state)
{
	if(m_name == state.m_name			&& 
	   m_country == state.m_country		&&
	   m_nStations == state.m_nStations )
		return TRUE;
	else
		return FALSE;
}
void CState::Copy(const CState & state)
{
	m_name = state.m_name;
	m_country = state.m_country;
	m_nStations = state.m_nStations;
}
void CState::Zero()
{
	m_name = _T("");
	m_country = _T("");
	m_nStations = 0;
}
// Serialize
void CState::Read(CArchive & ar, int & version)
{
	ar >> m_name;
	ar >> m_country;
	ar >> m_nStations;
}
void CState::Write(CArchive & ar, int & version)
{
	ar << m_name;
	ar << m_country;
	ar << m_nStations;
}
//////////////////////////////////////////////////////
// CStateArray

// Serialize array
void CStateArray::Read(CArchive & ar, int & version)
{
	// Read from archive.
	CState state;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		// Read into temp CCountryCode.
		state.Read(ar, version);

		// Add to array.
		Add(state);
	}
}
void CStateArray::Write(CArchive & ar, int & version)
{
	// Write to archive.
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		CState & state = ElementAt(i);
		state.Write(ar, version);
	}
}

// Sort state array by m_name per ascend param.
void CStateArray::SortStateName(BOOL ascend)
{
	if(ascend)
		qsort(GetData(), GetSize(), sizeof(CState), CompareStateNameAscend);
	else
		qsort(GetData(), GetSize(), sizeof(CState), CompareStateNameDescend);
}
// Qsort ascending.
int CStateArray::CompareStateNameAscend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CState * item1 = (CState *)arg1;
	CState * item2 = (CState *)arg2;
//	return item1->m_name.CompareNoCase(item2->m_name);

	// Sort country first.
	int ret = item1->m_country.CompareNoCase(item2->m_country);
	// Same country - sort state name.
	if(ret == 0)
		return item1->m_name.CompareNoCase(item2->m_name);
	// Sort country.
	else
		return ret;
}
// Qsort descending.
int CStateArray::CompareStateNameDescend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CState * item1 = (CState *)arg1;
	CState * item2 = (CState *)arg2;
	return item2->m_name.CompareNoCase(item1->m_name);
}








