#include "StdAfx.h"

extern bool g_bAutoUpdate;
extern HANDLE g_hEventWorkThreadStop;

struct CQuotesProviderBase::CXMLFileInfo
{
	CXMLFileInfo() : m_qs(L"Unknown") {}
	IQuotesProvider::CProviderInfo m_pi;
	CQuotesProviderBase::CQuoteSection m_qs;
	tstring m_sURL;
};

inline tstring get_ini_file_name(LPCTSTR pszFileName)
{
	return CreateFilePath(pszFileName);
}

bool parse_quote(const IXMLNode::TXMLNodePtr& pTop, CQuotesProviderBase::CQuote& q)
{
	tstring sSymbol;
	tstring sDescription;
	tstring sID;

	size_t cChild = pTop->GetChildCount();
	for (size_t i = 0; i < cChild; ++i) {
		IXMLNode::TXMLNodePtr pNode = pTop->GetChildNode(i);
		tstring sName = pNode->GetName();
		if (0 == mir_wstrcmpi(L"symbol", sName.c_str())) {
			sSymbol = pNode->GetText();
			if (true == sSymbol.empty())
				return false;
		}
		else if (0 == mir_wstrcmpi(L"description", sName.c_str())) {
			sDescription = pNode->GetText();
		}
		else if (0 == mir_wstrcmpi(L"id", sName.c_str())) {
			sID = pNode->GetText();
			if (true == sID.empty())
				return false;
		}
	}

	q = CQuotesProviderBase::CQuote(sID, TranslateW(sSymbol.c_str()), TranslateW(sDescription.c_str()));
	return true;
}

bool parse_section(const IXMLNode::TXMLNodePtr& pTop, CQuotesProviderBase::CQuoteSection& qs)
{
	CQuotesProviderBase::CQuoteSection::TSections aSections;
	CQuotesProviderBase::CQuoteSection::TQuotes aQuotes;
	tstring sSectionName;

	size_t cChild = pTop->GetChildCount();
	for (size_t i = 0; i < cChild; ++i) {
		IXMLNode::TXMLNodePtr pNode = pTop->GetChildNode(i);
		tstring sName = pNode->GetName();
		if (0 == mir_wstrcmpi(L"section", sName.c_str())) {
			CQuotesProviderBase::CQuoteSection qs1;
			if (true == parse_section(pNode, qs1))
				aSections.push_back(qs1);
		}
		else if (0 == mir_wstrcmpi(L"quote", sName.c_str())) {
			CQuotesProviderBase::CQuote q;
			if (true == parse_quote(pNode, q))
				aQuotes.push_back(q);
		}
		else if (0 == mir_wstrcmpi(L"name", sName.c_str())) {
			sSectionName = pNode->GetText();
			if (true == sSectionName.empty())
				return false;
		}
	}

	qs = CQuotesProviderBase::CQuoteSection(TranslateW(sSectionName.c_str()), aSections, aQuotes);
	return true;
}

IXMLNode::TXMLNodePtr find_provider(const IXMLNode::TXMLNodePtr& pRoot)
{
	IXMLNode::TXMLNodePtr pProvider;
	size_t cChild = pRoot->GetChildCount();
	for (size_t i = 0; i < cChild; ++i) {
		IXMLNode::TXMLNodePtr pNode = pRoot->GetChildNode(i);
		tstring sName = pNode->GetName();
		if (0 == mir_wstrcmpi(L"Provider", sName.c_str())) {
			pProvider = pNode;
			break;
		}

		pProvider = find_provider(pNode);
		if (pProvider)
			break;
	}

	return pProvider;
}

CQuotesProviderBase::CXMLFileInfo parse_ini_file(const tstring& rsXMLFile, bool& rbSucceded)
{
	CQuotesProviderBase::CXMLFileInfo res;
	CQuotesProviderBase::CQuoteSection::TSections aSections;

	const CModuleInfo::TXMLEnginePtr& pXMLEngine = CModuleInfo::GetXMLEnginePtr();
	IXMLNode::TXMLNodePtr pRoot = pXMLEngine->LoadFile(rsXMLFile);
	if (pRoot) {
		IXMLNode::TXMLNodePtr pProvider = find_provider(pRoot);
		if (pProvider) {
			rbSucceded = true;
			size_t cChild = pProvider->GetChildCount();
			for (size_t i = 0; i < cChild; ++i) {
				IXMLNode::TXMLNodePtr pNode = pProvider->GetChildNode(i);
				tstring sName = pNode->GetName();
				if (0 == mir_wstrcmpi(L"section", sName.c_str())) {
					CQuotesProviderBase::CQuoteSection qs;
					if (true == parse_section(pNode, qs))
						aSections.push_back(qs);
				}
				else if (0 == mir_wstrcmpi(L"Name", sName.c_str()))
					res.m_pi.m_sName = pNode->GetText();
				else if (0 == mir_wstrcmpi(L"ref", sName.c_str()))
					res.m_pi.m_sURL = pNode->GetText();
				else if (0 == mir_wstrcmpi(L"url", sName.c_str()))
					res.m_sURL = pNode->GetText();
			}
		}
	}

	res.m_qs = CQuotesProviderBase::CQuoteSection(res.m_pi.m_sName, aSections);
	return res;
}

CQuotesProviderBase::CXMLFileInfo init_xml_info(LPCTSTR pszFileName, bool& rbSucceded)
{
	rbSucceded = false;
	tstring sIniFile = get_ini_file_name(pszFileName);
	return parse_ini_file(sIniFile, rbSucceded);
}

CQuotesProviderBase::CQuotesProviderBase()
	: m_hEventSettingsChanged(::CreateEvent(NULL, FALSE, FALSE, NULL)),
	m_hEventRefreshContact(::CreateEvent(NULL, FALSE, FALSE, NULL)),
	m_bRefreshInProgress(false)
{
}

CQuotesProviderBase::~CQuotesProviderBase()
{
	::CloseHandle(m_hEventSettingsChanged);
	::CloseHandle(m_hEventRefreshContact);
}

bool CQuotesProviderBase::Init()
{
	bool bSucceded = m_pXMLInfo != NULL;
	if (!m_pXMLInfo) {
		CQuotesProviderVisitorDbSettings visitor;
		Accept(visitor);
		assert(visitor.m_pszXMLIniFileName);

		m_pXMLInfo.reset(new CXMLFileInfo(init_xml_info(visitor.m_pszXMLIniFileName, bSucceded)));
	}

	return bSucceded;
}

CQuotesProviderBase::CXMLFileInfo* CQuotesProviderBase::GetXMLFileInfo()const
{
	// 	if(!m_pXMLInfo)
	// 	{
	// 		CQuotesProviderVisitorDbSettings visitor;
	// 		Accept(visitor);
	// 		assert(visitor.m_pszXMLIniFileName);
	// 		m_pXMLInfo.reset(new CXMLFileInfo(init_xml_info(visitor.m_pszXMLIniFileName)));
	// 	}

	return m_pXMLInfo.get();
}

const CQuotesProviderBase::CProviderInfo& CQuotesProviderBase::GetInfo()const
{
	return GetXMLFileInfo()->m_pi;
}

const CQuotesProviderBase::CQuoteSection& CQuotesProviderBase::GetQuotes()const
{
	return GetXMLFileInfo()->m_qs;
}

const tstring& CQuotesProviderBase::GetURL()const
{
	return GetXMLFileInfo()->m_sURL;
}

bool CQuotesProviderBase::IsOnline()
{
	return /*g_bAutoUpdate*/true;
}

void CQuotesProviderBase::AddContact(MCONTACT hContact)
{
	// 	CCritSection cs(m_cs);
	assert(m_aContacts.end() == std::find(m_aContacts.begin(), m_aContacts.end(), hContact));

	m_aContacts.push_back(hContact);
}

void CQuotesProviderBase::DeleteContact(MCONTACT hContact)
{
	mir_cslock lck(m_cs);

	TContracts::iterator i = std::find(m_aContacts.begin(), m_aContacts.end(), hContact);
	if (i != m_aContacts.end())
		m_aContacts.erase(i);
}

void CQuotesProviderBase::SetContactStatus(MCONTACT hContact, int nNewStatus)
{
	int nStatus = db_get_w(hContact, QUOTES_PROTOCOL_NAME, DB_STR_STATUS, ID_STATUS_OFFLINE);
	if (nNewStatus != nStatus) {
		db_set_w(hContact, QUOTES_PROTOCOL_NAME, DB_STR_STATUS, nNewStatus);

		if (ID_STATUS_ONLINE != nNewStatus) {
			db_unset(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME);
			tstring sSymbol = Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_SYMBOL);
			if (false == sSymbol.empty())
				db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, sSymbol.c_str());

			SetContactExtraImage(hContact, eiEmpty);
		}
	}
}

class CTendency
{
	enum { NumValues = 2 };
	enum EComparison
	{
		NonValid,
		Greater,
		Less,
		Equal,
		GreaterOrEqual,
		LessOrEqual
	};

public:
	enum EResult
	{
		NotChanged,
		Up,
		Down
	};

public:
	CTendency() : m_nComparison(NonValid) {}

	bool Parse(const IQuotesProvider* pProvider, const tstring& rsFrmt, MCONTACT hContact)
	{
		m_abValueFlags[0] = false;
		m_abValueFlags[1] = false;
		m_nComparison = NonValid;
		bool bValid = true;
		int nCurValue = 0;
		for (tstring::const_iterator i = rsFrmt.begin(); i != rsFrmt.end() && bValid && nCurValue < NumValues;) {
			wchar_t chr = *i;
			switch (chr) {
			default:
				if (false == std::isspace(chr))
					bValid = false;
				else 
					++i;
				break;

			case '%':
				++i;
				if (i != rsFrmt.end()) {
					wchar_t t = *i;
					++i;
					CQuotesProviderVisitorTendency visitor(hContact, t);
					pProvider->Accept(visitor);
					if (false == visitor.IsValid()) {
						bValid = false;
					}
					else {
						double d = visitor.GetResult();
						m_adValues[nCurValue] = d;
						m_abValueFlags[nCurValue] = true;
						++nCurValue;
					}
				}
				else bValid = false;
				break;
			case '>':
				m_nComparison = Greater;
				++i;
				break;
			case '<':
				m_nComparison = Less;
				++i;
				break;
			case '=':
				switch (m_nComparison) {
				default:
					bValid = false;
					break;
				case NonValid:
					m_nComparison = Equal;
					break;
				case Greater:
					m_nComparison = GreaterOrEqual;
					break;
				case Less:
					m_nComparison = LessOrEqual;
					break;
				}
				++i;
				break;
			}
		}

		return (bValid && IsValid());
	}

	bool IsValid()const { return (m_abValueFlags[0] && m_abValueFlags[1] && (m_nComparison != NonValid)); }

	EResult Compare()const
	{
		switch (m_nComparison) {
		case Greater:
			if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
				return NotChanged;

			if (m_adValues[0] > m_adValues[1])
				return Up;
			return Down;

		case GreaterOrEqual:
			if ((true == IsWithinAccuracy(m_adValues[0], m_adValues[1])) || (m_adValues[0] > m_adValues[1]))
				return Up;
			return Down;

		case Less:
			if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
				return NotChanged;

			if (m_adValues[0] < m_adValues[1])
				return Up;
			return Down;

		case LessOrEqual:
			if ((true == IsWithinAccuracy(m_adValues[0], m_adValues[1])) || (m_adValues[0] < m_adValues[1]))
				return Up;
			return Down;

		case Equal:
			if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
				return Up;
			return Down;
		}
		return NotChanged;
	}

private:
	double m_adValues[NumValues];
	bool m_abValueFlags[NumValues];
	EComparison m_nComparison;
};

tstring format_rate(const IQuotesProvider *pProvider, MCONTACT hContact, const tstring &rsFrmt)
{
	tstring sResult;

	for (tstring::const_iterator i = rsFrmt.begin(); i != rsFrmt.end();) {
		wchar_t chr = *i;
		switch (chr) {
		default:
			sResult += chr;
			++i;
			break;
		
		case '\\':
			++i;
			if (i != rsFrmt.end()) {
				wchar_t t = *i;
				switch (t) {
					case '%':  sResult += L"%"; break;
					case 't':  sResult += L"\t"; break;
					case 'n':  sResult += L"\n"; break;
					case '\\': sResult += L"\\"; break;
					default:       sResult += chr; sResult += t; break;
				}
				++i;
			}
			else sResult += chr;
			break;

		case '%':
			++i;
			if (i != rsFrmt.end()) {
				chr = *i;

				byte nWidth = 0;
				if (::isdigit(chr)) {
					nWidth = chr - 0x30;
					++i;
					if (i == rsFrmt.end()) {
						sResult += chr;
						break;
					}
					else chr = *i;
				}

				CQuotesProviderVisitorFormater visitor(hContact, chr, nWidth);
				pProvider->Accept(visitor);
				const tstring& s = visitor.GetResult();
				sResult += s;
				++i;
			}
			else sResult += chr;
			break;
		}
	}

	return sResult;
}

void log_to_file(const IQuotesProvider* pProvider,
	MCONTACT hContact,
	const tstring& rsLogFileName,
	const tstring& rsFormat)
{
	std::string sPath = quotes_t2a(rsLogFileName.c_str());

	std::string::size_type n = sPath.find_last_of("\\/");
	if (std::string::npos != n)
		sPath.erase(n);

	DWORD dwAttributes = ::GetFileAttributesA(sPath.c_str());
	if ((0xffffffff == dwAttributes) || (0 == (dwAttributes&FILE_ATTRIBUTE_DIRECTORY)))
		CreateDirectoryTree(sPath.c_str());

	tofstream file(rsLogFileName.c_str(), std::ios::app | std::ios::out);
	file.imbue(GetSystemLocale());
	if (file.good()) {
		tstring s = format_rate(pProvider, hContact, rsFormat);
		file << s;
	}
}

void log_to_history(const IQuotesProvider* pProvider,
	MCONTACT hContact,
	time_t nTime,
	const tstring& rsFormat)
{
	tstring s = format_rate(pProvider, hContact, rsFormat);
	T2Utf psz(s.c_str());

	DBEVENTINFO dbei = { sizeof(dbei) };
	dbei.szModule = QUOTES_PROTOCOL_NAME;
	dbei.timestamp = static_cast<DWORD>(nTime);
	dbei.flags = DBEF_READ | DBEF_UTF;
	dbei.eventType = EVENTTYPE_MESSAGE;
	dbei.cbBlob = (int)::mir_strlen(psz) + 1;
	dbei.pBlob = (PBYTE)(char*)psz;
	db_event_add(hContact, &dbei);
}

bool do_set_contact_extra_icon(MCONTACT hContact, const CTendency& tendency)
{
	CTendency::EResult nComparison = tendency.Compare();

	if (CTendency::NotChanged == nComparison)
		return SetContactExtraImage(hContact, eiNotChanged);

	if (CTendency::Up == nComparison)
		return SetContactExtraImage(hContact, eiUp);

	if (CTendency::Down == nComparison)
		return SetContactExtraImage(hContact, eiDown);

	return false;
}

bool show_popup(const IQuotesProvider* pProvider,
	MCONTACT hContact,
	const CTendency& tendency,
	const tstring& rsFormat,
	const CPopupSettings& ps)
{
	if (!ServiceExists(MS_POPUP_ADDPOPUPT))
		return false;

	POPUPDATAT ppd;
	memset(&ppd, 0, sizeof(ppd));
	ppd.lchContact = hContact;

	if (tendency.IsValid()) {
		CTendency::EResult nComparison = tendency.Compare();
		if (CTendency::NotChanged == nComparison)
			ppd.lchIcon = Quotes_LoadIconEx(IDI_ICON_NOTCHANGED);
		else if (CTendency::Up == nComparison)
			ppd.lchIcon = Quotes_LoadIconEx(IDI_ICON_UP);
		else if (CTendency::Down == nComparison)
			ppd.lchIcon = Quotes_LoadIconEx(IDI_ICON_DOWN);
	}

	CQuotesProviderVisitorFormater visitor(hContact, 's', 0);
	pProvider->Accept(visitor);
	const tstring& sTitle = visitor.GetResult();
	mir_wstrncpy(ppd.lptzContactName, sTitle.c_str(), MAX_CONTACTNAME);
	{
		ptrW ss(variables_parsedup((wchar_t*)rsFormat.c_str(), 0, hContact));
		tstring sText = format_rate(pProvider, hContact, tstring(ss));
		mir_wstrncpy(ppd.lptzText, sText.c_str(), MAX_SECONDLINE);
	}

	if (CPopupSettings::colourDefault == ps.GetColourMode()) {
		ppd.colorText = CPopupSettings::GetDefColourText();
		ppd.colorBack = CPopupSettings::GetDefColourBk();
	}
	else {
		ppd.colorText = ps.GetColourText();
		ppd.colorBack = ps.GetColourBk();
	}

	switch (ps.GetDelayMode()) {
	default:
		assert(!"Unknown popup delay mode");
	case CPopupSettings::delayFromPopup:
		ppd.iSeconds = 0;
		break;
	case CPopupSettings::delayPermanent:
		ppd.iSeconds = -1;
		break;
	case CPopupSettings::delayCustom:
		ppd.iSeconds = ps.GetDelayTimeout();
		break;
	}

	LPARAM lp = 0;
	if (false == ps.GetHistoryFlag())
		lp |= 0x08;

	return (0 == CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast<WPARAM>(&ppd), lp));
}

void CQuotesProviderBase::WriteContactRate(MCONTACT hContact, double dRate, const tstring& rsSymbol/* = ""*/)
{
	time_t nTime = ::time(NULL);

	if (false == rsSymbol.empty())
		db_set_ws(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_SYMBOL, rsSymbol.c_str());

	double dPrev = 0.0;
	bool bValidPrev = Quotes_DBReadDouble(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_CURR_VALUE, dPrev);
	if (true == bValidPrev)
		Quotes_DBWriteDouble(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_PREV_VALUE, dPrev);

	Quotes_DBWriteDouble(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_CURR_VALUE, dRate);
	db_set_dw(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_FETCH_TIME, nTime);

	tstring sSymbol = rsSymbol;

	tostringstream oNick;
	oNick.imbue(GetSystemLocale());
	if (false == m_sContactListFormat.empty()) {
		tstring s = format_rate(this, hContact, m_sContactListFormat);
		oNick << s;
	}
	else {
		if (true == sSymbol.empty())
			sSymbol = Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_SYMBOL);

		oNick << std::setfill(L' ') << std::setw(10) << std::left << sSymbol << std::setw(6) << std::right << dRate;
	}
	CTendency tendency;

	if (true == tendency.Parse(this, m_sTendencyFormat, hContact))
		do_set_contact_extra_icon(hContact, tendency);

	db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, oNick.str().c_str());

	tstring sStatusMsg = format_rate(this, hContact, m_sStatusMsgFormat);
	if (false == sStatusMsg.empty())
		db_set_ws(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME, sStatusMsg.c_str());
	else
		db_unset(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME);

	bool bUseContactSpecific = (db_get_b(hContact, QUOTES_PROTOCOL_NAME, DB_STR_CONTACT_SPEC_SETTINGS, 0) > 0);

	CAdvProviderSettings global_settings(this);

	WORD dwMode = (bUseContactSpecific)
		? db_get_w(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_LOG, static_cast<WORD>(lmDisabled))
		: global_settings.GetLogMode();
	if (dwMode&lmExternalFile) {
		bool bAdd = true;
		bool bOnlyIfChanged = (bUseContactSpecific)
			? (db_get_w(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_LOG_FILE_CONDITION, 1) > 0)
			: global_settings.GetLogOnlyChangedFlag();
		if (true == bOnlyIfChanged) {
			bAdd = ((false == bValidPrev) || (false == IsWithinAccuracy(dRate, dPrev)));
		}
		if (true == bAdd) {
			tstring sLogFileName = (bUseContactSpecific)
				? Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_LOG_FILE, global_settings.GetLogFileName().c_str())
				: global_settings.GetLogFileName();

			if (true == sSymbol.empty()) {
				sSymbol = Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_SYMBOL);
			}

			sLogFileName = GenerateLogFileName(sLogFileName, sSymbol);

			tstring sFormat = global_settings.GetLogFormat();
			if (bUseContactSpecific) {
				CQuotesProviderVisitorDbSettings visitor;
				Accept(visitor);
				sFormat = Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_FORMAT_LOG_FILE, visitor.m_pszDefLogFileFormat);
			}

			log_to_file(this, hContact, sLogFileName, sFormat);
		}
	}
	if (dwMode&lmInternalHistory) {
		bool bAdd = true;
		bool bOnlyIfChanged = (bUseContactSpecific)
			? (db_get_w(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_HISTORY_CONDITION, 1) > 0)
			: global_settings.GetHistoryOnlyChangedFlag();

		if (true == bOnlyIfChanged) {
			bAdd = ((false == bValidPrev) || (false == IsWithinAccuracy(dRate, dPrev)));
		}
		if (true == bAdd) {
			tstring sFormat = (bUseContactSpecific)
				? Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_FORMAT_HISTORY, global_settings.GetHistoryFormat().c_str())
				: global_settings.GetHistoryFormat();

			log_to_history(this, hContact, nTime, sFormat);
		}
	}

	if (dwMode&lmPopup) {
		bool bOnlyIfChanged = (bUseContactSpecific)
			? (1 == db_get_b(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_POPUP_CONDITION, 1) > 0)
			: global_settings.GetShowPopupIfValueChangedFlag();
		if ((false == bOnlyIfChanged)
			|| ((true == bOnlyIfChanged) && (true == bValidPrev) && (false == IsWithinAccuracy(dRate, dPrev)))) {
			tstring sFormat = (bUseContactSpecific)
				? Quotes_DBGetStringT(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_FORMAT_POPUP, global_settings.GetPopupFormat().c_str())
				: global_settings.GetPopupFormat();

			CPopupSettings ps = *(global_settings.GetPopupSettingsPtr());
			ps.InitForContact(hContact);
			show_popup(this, hContact, tendency, sFormat, ps);
		}
	}

	SetContactStatus(hContact, ID_STATUS_ONLINE);
}

MCONTACT CQuotesProviderBase::CreateNewContact(const tstring& rsName)
{
	MCONTACT hContact = MCONTACT(CallService(MS_DB_CONTACT_ADD, 0, 0));
	if (hContact) {
		if (0 == Proto_AddToContact(hContact, QUOTES_PROTOCOL_NAME)) {
			tstring sProvName = GetInfo().m_sName;
			db_set_ws(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_PROVIDER, sProvName.c_str());
			db_set_ws(hContact, QUOTES_PROTOCOL_NAME, DB_STR_QUOTE_SYMBOL, rsName.c_str());
			db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, rsName.c_str());

			mir_cslock lck(m_cs);
			m_aContacts.push_back(hContact);
		}
		else {
			CallService(MS_DB_CONTACT_DELETE, WPARAM(hContact), 0);
			hContact = NULL;
		}
	}

	return hContact;
}

DWORD get_refresh_timeout_miliseconds(const CQuotesProviderVisitorDbSettings& visitor)
{
	if (!g_bAutoUpdate)
		return INFINITE;

	assert(visitor.m_pszDbRefreshRateType);
	assert(visitor.m_pszDbRefreshRateValue);

	int nRefreshRateType = db_get_w(NULL, QUOTES_MODULE_NAME, visitor.m_pszDbRefreshRateType, RRT_MINUTES);
	if (nRefreshRateType < RRT_SECONDS || nRefreshRateType > RRT_HOURS)
		nRefreshRateType = RRT_MINUTES;

	DWORD nTimeout = db_get_w(NULL, QUOTES_MODULE_NAME, visitor.m_pszDbRefreshRateValue, 1);
	switch (nRefreshRateType) {
	default:
	case RRT_SECONDS:
		if (nTimeout < 1 || nTimeout > 60)
			nTimeout = 1;

		nTimeout *= 1000;
		break;
	case RRT_MINUTES:
		if (nTimeout < 1 || nTimeout > 60)
			nTimeout = 1;

		nTimeout *= 1000 * 60;
		break;
	case RRT_HOURS:
		if (nTimeout < 1 || nTimeout > 24)
			nTimeout = 1;

		nTimeout *= 1000 * 60 * 60;
		break;
	}

	return nTimeout;
}

class CBoolGuard
{
public:
	CBoolGuard(bool& rb) : m_b(rb) { m_b = true; }
	~CBoolGuard() { m_b = false; }

private:
	bool m_b;
};

void CQuotesProviderBase::Run()
{
	CQuotesProviderVisitorDbSettings visitor;
	Accept(visitor);

	DWORD nTimeout = get_refresh_timeout_miliseconds(visitor);
	m_sContactListFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbDisplayNameFormat, visitor.m_pszDefDisplayFormat);
	m_sStatusMsgFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbStatusMsgFormat, visitor.m_pszDefStatusMsgFormat);
	m_sTendencyFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbTendencyFormat, visitor.m_pszDefTendencyFormat);

	enum
	{
		STOP_THREAD = 0,
		SETTINGS_CHANGED = 1,
		REFRESH_CONTACT = 2,
		COUNT_SYNC_OBJECTS = 3
	};

	HANDLE anEvents[COUNT_SYNC_OBJECTS];
	anEvents[STOP_THREAD] = g_hEventWorkThreadStop;
	anEvents[SETTINGS_CHANGED] = m_hEventSettingsChanged;
	anEvents[REFRESH_CONTACT] = m_hEventRefreshContact;

	TContracts anContacts;
	{
		mir_cslock lck(m_cs);
		anContacts = m_aContacts;
	}

	bool bGoToBed = false;

	if (g_bAutoUpdate) {
		CBoolGuard bg(m_bRefreshInProgress);
		RefreshQuotes(anContacts);
	}

	while (false == bGoToBed) {
		anContacts.clear();

		DWORD dwBegin = ::GetTickCount();
		DWORD dwResult = ::WaitForMultipleObjects(COUNT_SYNC_OBJECTS, anEvents, FALSE, nTimeout);
		switch (dwResult) {
		case WAIT_FAILED:
			assert(!"WaitForMultipleObjects failed");
			bGoToBed = true;
			break;

		case WAIT_ABANDONED_0 + STOP_THREAD:
		case WAIT_ABANDONED_0 + SETTINGS_CHANGED:
		case WAIT_ABANDONED_0 + REFRESH_CONTACT:
			assert(!"WaitForMultipleObjects abandoned");

		case WAIT_OBJECT_0 + STOP_THREAD:
			bGoToBed = true;
			break;

		case WAIT_OBJECT_0 + SETTINGS_CHANGED:
			nTimeout = get_refresh_timeout_miliseconds(visitor);
			m_sContactListFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbDisplayNameFormat, visitor.m_pszDefDisplayFormat);
			m_sStatusMsgFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbStatusMsgFormat, visitor.m_pszDefStatusMsgFormat);
			m_sTendencyFormat = Quotes_DBGetStringT(NULL, QUOTES_PROTOCOL_NAME, visitor.m_pszDbTendencyFormat, visitor.m_pszDefTendencyFormat);
			{
				mir_cslock lck(m_cs);
				anContacts = m_aContacts;
			}
			break;
		case WAIT_OBJECT_0 + REFRESH_CONTACT:
			{
				DWORD dwTimeRest = ::GetTickCount() - dwBegin;
				if (INFINITE != nTimeout && dwTimeRest < nTimeout) {
					nTimeout -= dwTimeRest;
				}

				{
					mir_cslock lck(m_cs);
					anContacts = m_aRefreshingContacts;
					m_aRefreshingContacts.clear();
				}

				{
					CBoolGuard bg(m_bRefreshInProgress);
					RefreshQuotes(anContacts);
				}
			}
			break;
		case WAIT_TIMEOUT:
			nTimeout = get_refresh_timeout_miliseconds(visitor);
			{
				mir_cslock lck(m_cs);
				anContacts = m_aContacts;
			}
			{
				CBoolGuard bg(m_bRefreshInProgress);
				RefreshQuotes(anContacts);
			}
			break;

		default:
			assert(!"What is the hell?");
		}
	}

	OnEndRun();
}

void CQuotesProviderBase::OnEndRun()
{
	TContracts anContacts;
	{
		mir_cslock lck(m_cs);
		anContacts = m_aContacts;
		m_aRefreshingContacts.clear();
	}

	CBoolGuard bg(m_bRefreshInProgress);
	std::for_each(anContacts.begin(), anContacts.end(), boost::bind(&SetContactStatus, _1, ID_STATUS_OFFLINE));
}

void CQuotesProviderBase::Accept(CQuotesProviderVisitor &visitor)const
{
	visitor.Visit(*this);
}

void CQuotesProviderBase::RefreshSettings()
{
	BOOL b = ::SetEvent(m_hEventSettingsChanged);
	assert(b && "Failed to set event");
}

void CQuotesProviderBase::RefreshAllContacts()
{
	{// for CCritSection
		mir_cslock lck(m_cs);
		m_aRefreshingContacts.clear();
		std::for_each(std::begin(m_aContacts), std::end(m_aContacts), [&](MCONTACT hContact) { m_aRefreshingContacts.push_back(hContact); });
	}

	BOOL b = ::SetEvent(m_hEventRefreshContact);
	assert(b && "Failed to set event");
}

void CQuotesProviderBase::RefreshContact(MCONTACT hContact)
{
	{// for CCritSection
		mir_cslock lck(m_cs);
		m_aRefreshingContacts.push_back(hContact);
	}

	BOOL b = ::SetEvent(m_hEventRefreshContact);
	assert(b && "Failed to set event");
}