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

#if !defined RADIO_BROWSEDB_H
#define RADIO_BROWSEDB_H

#if _MSC_VER > 1000
#pragma once
#endif				// _MSC_VER > 1000

/*
	CRadioBrowseDb is a flat file database of the Radio-Browser
	data available at http://nl1.api.radio-browser.info.
	As of 2021, it's a 28 MB download which gets serialized into
	a 7 MB file with 28363 CStations.
	The DB can be updated with one Get request to Radio-Browser 
	for the xml/stations file.

	The user can search for:
	 A word in a station's name, stations within a country or a state, 
	 language, tags, votes, clicks.
	Searches are brute-force, and take under one second.

	It's quite a pile of code when including an Update Thread, 
	a Search Dialog, and Serialization.
*/

#include "Stdafx.h"

///////////////////////////////////////////////////
// CState
class CState
{
public:
	CState();
	~CState();
	CState(const CState & state);
	void	operator=(const CState & state);
	BOOL	operator==(const CState & state);
	void	Copy(const CState & state);
	void	Zero();
	// Serialize
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	CString m_name;
	CString m_country;
	int		m_nStations;
};
// CStateArray - an array of CStates.
class CStateArray : public CArray <CState, CState&> 
{
public:
	// Serialize Array
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	// Sort States
	void SortStateName(BOOL ascend);
protected:
	static int CompareStateNameAscend(const void *, const void *);
	static int CompareStateNameDescend(const void *, const void *);
};

///////////////////////////////////////////////////
// CTag
class CTag
{
public:
	CTag();
	~CTag();
	CTag(const CTag & tag);
	void	operator=(const CTag & tag);
	BOOL	operator==(const CTag & tag);
	void	Copy(const CTag & tag);
	void	Zero();
	// Serialize
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	CString m_name;
	int		m_nStations;
};
// CTagArray - an array of CTags.
class CTagArray : public CArray <CTag, CTag&> 
{
public:
	// Serialize Array
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	// Sort Tags
	void SortTagName(BOOL ascend);
protected:
	static int CompareTagNameAscend(const void *, const void *);
	static int CompareTagNameDescend(const void *, const void *);
};

///////////////////////////////////////////////////
// CCountryCode
class CCountryCode
{
public:
	CCountryCode();
	~CCountryCode();
	CCountryCode(const CCountryCode & cc);
	void	operator=(const CCountryCode & cc);
	BOOL	operator==(const CCountryCode & cc);
	void	Copy(const CCountryCode & cc);
	void	Zero();
	// Serialize
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	CString m_name;
	CString m_code;
	BOOL	m_isUsed;
};
// CCountryCodeArray - an array of CCountries.
class CCountryCodeArray : public CArray <CCountryCode, CCountryCode&> 
{
public:
	BOOL SetUsed(CString & countryCode);
	void SetAllNotUsed();
	int  CountUsed();
	// Serialize Array
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);
};

///////////////////////////////////////////////////
// CLanguageCode
class CLanguageCode
{
public:
	CLanguageCode();
	~CLanguageCode();
	CLanguageCode(const CLanguageCode & lc);
	void	operator=(const CLanguageCode & lc);
	BOOL	operator==(const CLanguageCode & lc);
	void	Copy(const CLanguageCode & lc);
	void	Zero();
	// Serialize
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	CString m_name;
	CString m_code;
	BOOL	m_isUsed;
};
// CLanguageCodeArray - an array of CCountries.
class CLanguageCodeArray : public CArray <CLanguageCode, CLanguageCode&> 
{
public:
	BOOL SetUsed(CString & languageCode);
	void SetAllNotUsed();
	int  CountUsed();
	BOOL GetLanguageCodeCode(const CString & name, CString & code);
//	BOOL GetRepairedLanguageCode(const CString & name, CString & code);

	// Serialize Array
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);
};

///////////////////////////////////////////////////
// CStation
class CStation
{
public:
	CStation();
	~CStation();
	CStation(const CStation & station);
	void	operator=(const CStation & station);
	BOOL	operator==(const CStation & station);
	void	Copy(const CStation & station);
	void	Zero();

	// Serialize
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	// Members.
	CString m_name;
	CString m_url;
	CString m_country;
	CString m_state;
	CString m_language;	// comma seperated
	CString m_tags;		// comma seperated
	int		m_nVotes;
	int		m_nClicks;
};
// CStationAr - an array of CStations.
class CStationAr : public CArray <CStation, CStation&> 
{
public:
	// Sort Stations.
	void SortStationsAr(BOOL ascend, BOOL name, BOOL votes, BOOL clicks);
protected:
	// Ascending.
	static int CompareStationName(const void *, const void *);
	static int CompareStationVotes(const void *, const void *);
	static int CompareStationClicks(const void *, const void *);
	// Descending.
	static int CompareStationNameDescend(const void *, const void *);
	static int CompareStationVotesDescend(const void *, const void *);
	static int CompareStationClicksDescend(const void *, const void *);

	// Serialize Array
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);
};

///////////////////////////////////////////////////
// Class CRadioBrowseDb
class CRadioBrowseDb
{
public:
	CRadioBrowseDb();
	~CRadioBrowseDb();
	void ZeroStorage();
	void CopyStorage(CRadioBrowseDb & db);

	// Build CRadioBrowseDb array data.
	//
	// Build RB database in worker thread - for user updating.
	int BuildRadioBrowserThread(CString rbStationsUrl, CString tempFilePath, CString & errStr, 
								HWND hwndMain, HWND hwndDlg, CEvent * pEvent);
	// Build helpers.
	void SaveNameValue(CString & name, CString & value);
	int  SetCountryCodesUsed();
	int  SetLanguageCodesUsed();
	void AddTagNoDupSetCount(CString & tagStr);
	void AddStateNoDupSetCount(CString & stateStr, CString & countryStr);
	void RepaireLanguageCodes();
	int  GetRepairedLanguageCode(const CString & name, CString & code);

	// Get RB Servers.
	int	 GetServers(CString filePath, CString & errStr, CStringArray & serversAr);

	// Serialize RB data.
	void Read(CArchive & ar, int & version);
	void Write(CArchive & ar, int & version);

	// Array size access.
	int GetStationArSize() { return m_stationAr.GetSize();  };
	int GetCountryCodeArSize() { return m_countryCodeAr.GetSize();  };
	int GetTagArSize() { return m_tagAr.GetSize();  };
	int GetStateArSize() { return m_stateAr.GetSize();  };
	int GetResultsArSize() { return m_searchResultsAr.GetSize();  };
	int GetLanguageCodesArSize() { return m_languageCodeAr.GetSize();  };

	// Array access - no bounds check.
	CTag & GetTagAt(int elmt) { return m_tagAr[elmt]; };
	CCountryCode & GetCountryCodeAt(int elmt) { return m_countryCodeAr[elmt]; };
	CState & GetStateAt(int elmt) { return m_stateAr[elmt]; };
	CStation & GetSearchResultsAt(int elmt) { return m_searchResultsAr[elmt]; };
	CStation & GetStationAt(int elmt) { return m_stationAr[elmt]; };
	CLanguageCode & GetLanguageCodeAt(int elmt)  { return m_languageCodeAr[elmt]; };

	// Search.
	int SearchDb();
	void SortResultsArray(BOOL ascend, BOOL name, BOOL votes, BOOL clicks);
	void ZeroSearchResultsAndCritera();

	// Set search criteria
	void ZeroSearchCritera();
	void SetSearchName(CString name) { m_bName = TRUE; m_searchName = name; };
	void SetSearchCountryCode(CString country) { m_bCountry = TRUE; m_searchCountry = country; };
	void SetSearchState(CString state) { m_bState = TRUE; m_searchState = state; };
	void SetSearchLanguage(CString language) { m_bLanguage = TRUE; m_searchLanguage = language; };
	void SetSearchTags(CStringArray & tags) { m_bTags = TRUE; m_searchTags.Copy(tags); };
	void SetSearchClicks(int minClicks) { m_bClicks = TRUE; m_nClicksMinimum = minClicks; };
	void SetSearchVotes(int minVotes) { m_bVotes = TRUE; m_nVotesMinimum = minVotes; };

private:
	// Build radio browser arrays.
	//  Functions called only from BuildRadioBrouserDb().
	//
	// Build m_stationAr - from http://nl1.api.radio-browser.info.
	// Threaded version.
	BOOL BuildStationArrayThread(CString rbUrl, CString tempFilePath, CString & errStr);

	// No thread version.
	int	 BuildStationArray(CString filePath, CString server, CString & errStr);

	// Build m_countryCodeAr - from RadioBrowse_CC.txt.
	int  BuildCountryCodesArray();

	// Build m_languageCodeAr - from RadioBrowse_LC.txt.
	int  BuildLanguageCodesArray();

	// Build Tags - from m_stationAr
	int  BuildTagArray(int minStationCount);

	// Build States - from m_stationAr
	int  BuildStatesArray(int minStationCount);

	// Thread message to dialog.
	void ShowDialogStatus(CString strStatus);

	// Thread progress ctrl message to dlg.
	void SetProgressPos(int pos);

	// Thread canceled by user.
	BOOL UpdateCanceled(CString dbgMsg);

	// Threaded build updtate.
	HWND m_hwndMain;
	HWND m_hwndDlg;
    CEvent * m_pEvent;
	BOOL m_inThread;
	int m_nProccessed;

	// Storage arrays.
	CStationAr m_stationAr;
	CCountryCodeArray m_countryCodeAr;
	CLanguageCodeArray m_languageCodeAr;
	CTagArray m_tagAr;
	CStateArray m_stateAr;

	// Build - temp.
	CStation m_tempStation;

	// Search helpers.
	int FindNoCase(const CString & str, CString & sub);
	int GetCriteriaCount();

	// Search Criteria
	BOOL	m_bName;
	BOOL	m_bCountry;
	BOOL	m_bState;
	BOOL	m_bLanguage;
	BOOL	m_bTags;
	BOOL	m_bClicks;
	BOOL	m_bVotes;
	CString m_searchName;
	CString m_searchCountry;
	CString m_searchState;
	CString m_searchLanguage;
	CStringArray m_searchTags;
	int		m_nClicksMinimum;
	int		m_nVotesMinimum;

	// Search storage
	CStationAr m_searchResultsAr;
	CString m_strFull, m_strSub;
};

#endif //RADIO_BROWSEDB_H