/// <summary>
///  Snarl C++ interface implementation
///
///  Written and maintained by Toke Noer Nøttrup (toke@noer.it)
///
///  Please note the following changes compared to the VB6 (official API) dokumentation:
///  - Function names doesn't have the prefix "sn". Naming of constants and variables are
///    generally changed to follow Microsoft C# standard. This naming convention is kept for
///    the C++ version, to keep them alike.
///  - Grouped variables like SNARL_LAUNCHED, SNARL_QUIT is enums in SnarlEnums namespace.
///  - Message events like SNARL_NOTIFICATION_CLICKED, is found in SnarlEnums::MessageEvent.
///  - Please note that string functions return NULL when they fail and not an empty string.
///  - Some functions in the VB API takes an appToken as first parameter. This token is a
///    member variable in C++ version, so it is omitted from the functions.
///    (Always call RegisterApp as first function!)
///  - Functions manipulating messages (Update, Hide etc.) still takes a message token as
///    parameter, but you can get the last message token calling GetLastMsgToken();
///    Example: snarl.Hide(snarl.GetLastMsgToken());
///
///  The functions in SnarlInterface both have ANSI(UTF8) and UNICODE versions.
///  If the LPCWSTR (unicode) version of the functions are called, the strings
///  are converted to UTF8 by SnarlInterface before sent to Snarl. So using the
///  ANSI/UTF8/LPCSTR versions of the functions are faster!
///
///  Funtions special to C++ V41 API compared to VB version:
///    GetLastMsgToken()
///    GetAppPath()
///    GetIconsPath()
/// </summary>
///----------------------------------------------------------------------------
/// <example>
/// SnarlInterface snarl;
/// snarl.RegisterApp(_T("CppTest"), _T("C++ test app"), NULL);
/// snarl.AddClass(_T("Class1"), _T("Class 1"));
/// snarl.EZNotify(_T("Class1"), _T("C++ example 1"), _T("Some text"), 10);
/// snarl.UnregisterApp();
///
/// Please see the SimpleTest.cpp and SnarlV41Test.cpp for more example code.
/// </example>
///----------------------------------------------------------------------------
/// <VersionHistory>
///  2010-08-13 : First release of V41 Snarl API implementation
/// </VersionHistory>

#define _CRT_SECURE_NO_WARNINGS

#include "SnarlInterface.h"


namespace Snarl {
namespace V41 {

//-----------------------------------------------------------------------------
// Constructor/Destructor
//-----------------------------------------------------------------------------
SnarlInterface::SnarlInterface()
	: appToken(0), lastMsgToken(0), localError(SnarlEnums::Success)
{
}

SnarlInterface::~SnarlInterface()
{
}

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

LONG32 SnarlInterface::RegisterApp(LPCSTR signature, LPCSTR title, LPCSTR icon, HWND hWndReply /* = NULL */, LONG32 msgReply /* = 0 */, SnarlEnums::AppFlags flags /* = SnarlEnums::AppDefault */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::RegisterApp;
	msg.Token = 0;
	PackData(msg.PacketData, 
		"id::%s#?title::%s#?icon::%s#?hwnd::%d#?umsg::%d#?flags::%d", 
		signature, title, icon, hWndReply, msgReply, flags);

	appToken = Send(msg);
	lastMsgToken = 0;

	return appToken;
}

LONG32 SnarlInterface::RegisterApp(LPCWSTR signature, LPCWSTR title, LPCWSTR icon, HWND hWndReply /* = NULL */, LONG32 msgReply /* = 0 */, SnarlEnums::AppFlags flags /* = SnarlEnums::AppDefault */)
{
	LPCSTR szParam1 = WideToUTF8(signature);
	LPCSTR szParam2 = WideToUTF8(title);
	LPCSTR szParam3 = WideToUTF8(icon);
	
	LONG32 result = RegisterApp(szParam1, szParam2, szParam3, hWndReply, msgReply, flags);
	
	delete [] szParam1;
	delete [] szParam2;
	delete [] szParam3;

	return result;
}

LONG32 SnarlInterface::UnregisterApp()
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::UnregisterApp;
	msg.Token = appToken;
	PackData(msg.PacketData, NULL);

	appToken = 0;
	lastMsgToken = 0;

	return Send(msg);
}

LONG32 SnarlInterface::UpdateApp(LPCSTR title /* = NULL */, LPCSTR icon /* = NULL */)
{
	if (title == NULL && icon == NULL)
		return 0;

	SnarlMessage msg;
	msg.Command = SnarlEnums::UpdateApp;
	msg.Token = appToken;
	
	// TODO: Uckly code ahead
	if (title != NULL && title[0] != 0 && icon != NULL && icon[0] != 0)
		PackData(msg.PacketData, "title::%s#?icon::%s", title, icon);
	else if (title != NULL && title[0] != 0)
		PackData(msg.PacketData, "title::%s", title);
	else if (icon != NULL && icon[0] != 0)
		PackData(msg.PacketData, "icon::%s", icon);
	
	return Send(msg);
}

LONG32 SnarlInterface::UpdateApp(LPCWSTR title /* = NULL */, LPCWSTR icon /* = NULL */)
{
	LPCSTR szParam1 = WideToUTF8(title);
	LPCSTR szParam2 = WideToUTF8(icon);
	
	LONG32 result = UpdateApp(szParam1, szParam2);
	
	delete [] szParam1;
	delete [] szParam2;

	return result;
}

LONG32 SnarlInterface::AddClass(LPCSTR className, LPCSTR description, bool enabled /* = true */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::AddClass;
	msg.Token = appToken;
	PackData(msg.PacketData, "id::%s#?name::%s#?enabled::%d", className, description, (enabled ? 1 : 0));

	return Send(msg);
}

LONG32 SnarlInterface::AddClass(LPCWSTR className, LPCWSTR description, bool enabled /* = true */)
{
	LPCSTR szParam1 = WideToUTF8(className);
	LPCSTR szParam2 = WideToUTF8(description);
	
	LONG32 result = AddClass(szParam1, szParam2, enabled);
	
	delete [] szParam1;
	delete [] szParam2;

	return result;
}

LONG32 SnarlInterface::RemoveClass(LPCSTR className, bool forgetSettings /* = false */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::RemoveClass;
	msg.Token = appToken;
	PackData(msg.PacketData, "id::%s#?forget::%d", className, (forgetSettings ? 1 : 0));

	return Send(msg);
}

LONG32 SnarlInterface::RemoveClass(LPCWSTR className, bool forgetSettings /* = false */)
{
	LPCSTR szParam1 = WideToUTF8(className);
	
	LONG32 result = RemoveClass(szParam1, forgetSettings);
	
	delete [] szParam1;

	return result;
}

LONG32 SnarlInterface::RemoveAllClasses(bool forgetSettings /* = false */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::RemoveClass;
	msg.Token = appToken;
	PackData(msg.PacketData, "all::1#?forget::%d", (forgetSettings ? 1 : 0));

	return Send(msg);
}

LONG32 SnarlInterface::EZNotify(LPCSTR className, LPCSTR title, LPCSTR text, LONG32 timeout /* = -1 */, LPCSTR icon /* = NULL */, LONG32 priority /* = 0 */, LPCSTR acknowledge /* = NULL */, LPCSTR value /* = NULL */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::Notify;
	msg.Token = appToken;
	PackData(msg.PacketData,
		"id::%s#?title::%s#?text::%s#?timeout::%d#?icon::%s#?priority::%d#?ack::%s#?value::%s",
		className, title, text, timeout, (icon ? icon : ""), priority, (acknowledge ? acknowledge : ""), (value ? value : ""));

	lastMsgToken = Send(msg);
	return lastMsgToken;
}

LONG32 SnarlInterface::EZNotify(LPCWSTR className, LPCWSTR title, LPCWSTR text, LONG32 timeout /* = -1 */, LPCWSTR icon /* = NULL */, LONG32 priority /* = 0 */, LPCWSTR acknowledge /* = NULL */, LPCWSTR value /* = NULL */)
{
	LPCSTR szParam1 = WideToUTF8(className);
	LPCSTR szParam2 = WideToUTF8(title);
	LPCSTR szParam3 = WideToUTF8(text);
	LPCSTR szParam4 = WideToUTF8(icon);
	LPCSTR szParam5 = WideToUTF8(acknowledge);
	LPCSTR szParam6 = WideToUTF8(value);
	
	LONG32 result = EZNotify(szParam1, szParam2, szParam3, timeout, szParam4, priority, szParam5, szParam6);
	
	delete [] szParam1; delete [] szParam2; delete [] szParam3;
	delete [] szParam4; delete [] szParam5; delete [] szParam6;

	return result;
}

LONG32 SnarlInterface::Notify(LPCSTR className, LPCSTR packetData)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::Notify;
	msg.Token = appToken;
	PackData(msg.PacketData, "id::%s#?%s", className, packetData);

	lastMsgToken = Send(msg);
	return lastMsgToken;
}

LONG32 SnarlInterface::Notify(LPCWSTR className, LPCWSTR packetData)
{
	LPCSTR szParam1 = WideToUTF8(className);
	LPCSTR szParam2 = WideToUTF8(packetData);

	LONG32 result = Notify(szParam1, szParam2);
	
	delete [] szParam1; delete [] szParam2;
	
	return result;
}

LONG32 SnarlInterface::EZUpdate(LONG32 msgToken, LPCSTR title /* = NULL */, LPCSTR text /* = NULL */, LONG32 timeout /* = -1 */, LPCSTR icon /* = NULL */)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::UpdateNotification;
	msg.Token = msgToken;
	
	// Create packed data
	errno_t err = 0;
	ZeroMemory(msg.PacketData, sizeof(msg.PacketData));
	char* pData = reinterpret_cast<char*>(msg.PacketData);

	if (title != NULL) {
		err |= strncat_s(pData, SnarlPacketDataSize, (pData[0] != NULL) ? "#?title::" : "title::", _TRUNCATE); //StringCbCat(tmp, SnarlPacketDataSize, "title::%s");
		err |= strncat_s(pData, SnarlPacketDataSize, title, _TRUNCATE);
	}
	if (text != NULL) {
		err |= strncat_s(pData, SnarlPacketDataSize, (pData[0] != NULL) ? "#?text::" : "text::", _TRUNCATE);
		err |= strncat_s(pData, SnarlPacketDataSize, text, _TRUNCATE);
	}
	if (icon != NULL) {
		err |= strncat_s(pData, SnarlPacketDataSize, (pData[0] != NULL) ? "#?icon::" : "icon::", _TRUNCATE);
		err |= strncat_s(pData, SnarlPacketDataSize, icon, _TRUNCATE);
	}
	if (timeout != -1) {
		char tmp[32];
		_itoa_s(timeout, tmp, 10);
		
		err |= strncat_s(pData, SnarlPacketDataSize, (pData[0] != NULL) ? "#?timeout::" : "timeout::", _TRUNCATE);
		err |= strncat_s(pData, SnarlPacketDataSize, tmp, _TRUNCATE);
	}
	
	// Check for strcat errors and exit on error
	if (err != 0) {
		localError = SnarlEnums::ErrorFailed;
		return 0;
	}

	return Send(msg);
}

LONG32 SnarlInterface::EZUpdate(LONG32 msgToken, LPCWSTR title /* = NULL */, LPCWSTR text /* = NULL */, LONG32 timeout /* = -1 */, LPCWSTR icon /* = NULL */)
{
	LPCSTR szParam1 = WideToUTF8(title);
	LPCSTR szParam2 = WideToUTF8(text);
	LPCSTR szParam3 = WideToUTF8(icon);

	LONG32 result = EZUpdate(msgToken, szParam1, szParam2, timeout, szParam3);
	
	delete [] szParam1; delete [] szParam2; delete [] szParam3;
	
	return result;
}

LONG32 SnarlInterface::Update(LONG32 msgToken, LPCSTR packetData)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::UpdateNotification;
	msg.Token = msgToken;
	PackData(msg.PacketData, packetData);

	return Send(msg);
}

LONG32 SnarlInterface::Update(LONG32 msgToken, LPCWSTR packetData)
{
	LPCSTR szParam1 = WideToUTF8(packetData);

	LONG32 result = Update(msgToken, szParam1);
	
	delete [] szParam1;
	
	return result;
}

LONG32 SnarlInterface::Hide(LONG32 msgToken)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::HideNotification;
	msg.Token = msgToken;
	PackData(msg.PacketData, NULL);

	return Send(msg);
}

LONG32 SnarlInterface::IsVisible(LONG32 msgToken)
{
	SnarlMessage msg;
	msg.Command = SnarlEnums::IsNotificationVisible;
	msg.Token = msgToken;
	PackData(msg.PacketData, NULL);

	return Send(msg);
}

SnarlEnums::SnarlStatus SnarlInterface::GetLastError()
{
	return localError;
}

// static
BOOL SnarlInterface::IsSnarlRunning()
{
	return IsWindow(GetSnarlWindow());
}

LONG32 SnarlInterface::GetVersion()
{
	localError = SnarlEnums::Success;

	HWND hWnd = GetSnarlWindow();
	if (!IsWindow(hWnd))
	{
		localError = SnarlEnums::ErrorNotRunning;
		return 0;
	}

	HANDLE hProp = GetProp(hWnd, _T("_version"));
	return reinterpret_cast<int>(hProp);
}

// static
UINT SnarlInterface::Broadcast()
{
	return RegisterWindowMessage(SnarlGlobalMsg);
}

// static
UINT SnarlInterface::AppMsg()
{
	return RegisterWindowMessage(SnarlAppMsg);
}

// static
HWND SnarlInterface::GetSnarlWindow()
{
	return FindWindow(SnarlWindowClass, SnarlWindowTitle);;
}

LPCTSTR SnarlInterface::GetAppPath()
{
	HWND hWnd = GetSnarlWindow();
	if (hWnd)
	{
		HWND hWndPath = FindWindowEx(hWnd, NULL, _T("static"), NULL);
		if (hWndPath)
		{
			TCHAR strTmp[MAX_PATH] = {0};
			int nReturn = GetWindowText(hWndPath, strTmp, MAX_PATH-1);
			if (nReturn > 0) {
				TCHAR* strReturn = AllocateString(nReturn + 1);
				_tcsncpy(strReturn, strTmp, nReturn + 1);
				strReturn[nReturn] = 0;
				return strReturn;
			}
		}
	}

	return NULL;
}

LPCTSTR SnarlInterface::GetIconsPath()
{
	TCHAR* szIconPath = NULL;
	LPCTSTR szPath = GetAppPath();
	if (!szPath)
		return NULL;

	size_t nLen = 0;
	// TODO: _tcsnlen MAX_PATH
	if (nLen = _tcsnlen(szPath, MAX_PATH))
	{
		nLen += 10 + 1; // etc\\icons\\ + NULL
		szIconPath = AllocateString(nLen);

		_tcsncpy(szIconPath, szPath, nLen);
		_tcsncat(szIconPath, _T("etc\\icons\\"), nLen);
	}
	
	FreeString(szPath);

	return szIconPath;
}

LONG32 SnarlInterface::GetLastMsgToken() const
{
	return lastMsgToken;
}


//-----------------------------------------------------------------------------
// Private functions 
//-----------------------------------------------------------------------------

LONG32 SnarlInterface::Send(SnarlMessage msg)
{
	DWORD_PTR nReturn = 0; // Failure

	HWND hWnd = GetSnarlWindow();
	if (!IsWindow(hWnd))
	{
		localError = SnarlEnums::ErrorNotRunning;
		return 0;
	}

	COPYDATASTRUCT cds;
	cds.dwData = 0x534E4C02; // "SNL",2;
	cds.cbData = sizeof(SnarlMessage);
	cds.lpData = &msg;

	if (SendMessageTimeout(hWnd, WM_COPYDATA, (WPARAM)GetCurrentProcessId(), (LPARAM)&cds, SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 500, &nReturn) == 0)
	{
		// return zero on failure
		if (GetLastError() == ERROR_TIMEOUT)
			localError = SnarlEnums::ErrorTimedOut;
		else
			localError = SnarlEnums::ErrorFailed;
		
		return 0;
	}

	// return result and cache LastError
	HANDLE hProp = GetProp(hWnd, _T("last_error"));
	localError = static_cast<SnarlEnums::SnarlStatus>(reinterpret_cast<int>(hProp));

	return nReturn;
}

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

// Remember to delete [] returned string
inline
LPSTR SnarlInterface::WideToUTF8(LPCWSTR szWideStr)
{
	if (szWideStr == NULL)
		return NULL;

	int nSize = WideCharToMultiByte(CP_UTF8, 0, szWideStr, -1, NULL, 0, NULL, NULL);
	LPSTR szUTF8 = new char[nSize];
	WideCharToMultiByte(CP_UTF8, 0, szWideStr, -1, szUTF8, nSize, NULL, NULL);
	
	return szUTF8;
}

void SnarlInterface::PackData(BYTE* data, LPCSTR format, ...)
{
	// Always zero array - Used to clear the array in member functions
	ZeroMemory(data, SnarlPacketDataSize);

	// Return if format string is empty
	if (format == NULL || format[0] == 0)
		return;

	int cchStrTextLen = 0;
	va_list args;
	va_start(args, format);
	
	// Get size of buffer
	cchStrTextLen = _vscprintf(format, args) + 1; // + NULL
	if (cchStrTextLen <= 1)
		return;

	// Create formated string - _TRUNCATE will ensure zero terminated
	_vsnprintf_s((char*)data, SnarlPacketDataSize, _TRUNCATE, format, args);

	va_end(args);
}

}} // namespace Snarl::V41