/*
	This file is part of Hikari, a library that allows developers
	to use Flash in their Ogre3D applications.

	Copyright (C) 2008 Adam J. Simmons

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef __FlashHandler_H__
#define __FlashHandler_H__

#import "PROGID:ShockwaveFlash.ShockwaveFlash" named_guids
#include "FlashValue.h"
#include <sstream>

namespace Hikari {
namespace Impl {

inline void replaceAll(std::wstring &sourceStr, const std::wstring &replaceWhat, const std::wstring &replaceWith)
{
	for(size_t i = sourceStr.find(replaceWhat); i != std::wstring::npos; i = sourceStr.find(replaceWhat, i + replaceWith.length()))
	{
		sourceStr.erase(i, replaceWhat.length());
		sourceStr.insert(i, replaceWith);
	}
}

std::wstring serializeValue(const FlashValue& value)
{
	switch(value.getType())
	{
	case FT_NULL:
		return L"<null/>";
	case FT_BOOLEAN:
		return value.getBool()? L"<true/>" : L"<false/>";
	case FT_NUMBER:
		{
			static std::wstringstream converter;
			converter.clear();
			converter.str(L"");
			converter << value.getNumber();

			return L"<number>" + converter.str() + L"</number>";
		}
	case FT_STRING:
		{
			std::wstring stringVal = value.getString();
			replaceAll(stringVal, L"&", L"&amp;");
			replaceAll(stringVal, L"\"", L"&quot;");
			replaceAll(stringVal, L"'", L"&apos;");
			replaceAll(stringVal, L"<", L"&lt;");
			replaceAll(stringVal, L">", L"&gt;");

			return L"<string>" + stringVal + L"</string>";
		}
	}

	return L"<null/>";
}

FlashValue deserializeValue(const std::wstring& valueStr)
{
	if(valueStr == L"<null/>")
		return FLASH_VOID;
	else if(valueStr == L"<true/>")
		return FlashValue(true);
	else if(valueStr == L"<false/>")
		return FlashValue(false);

	if(valueStr.substr(0, 8) == L"<string>")
	{
		std::wstring stringVal = valueStr.substr(8, valueStr.find(L"</string>", 8) - 8);
		replaceAll(stringVal, L"&quot;", L"\"");
		replaceAll(stringVal, L"&apos;", L"'");
		replaceAll(stringVal, L"&lt;", L"<");
		replaceAll(stringVal, L"&gt;", L">");
		replaceAll(stringVal, L"&amp;", L"&");
		return FlashValue(stringVal);
	}
	else if(valueStr.substr(0, 8) == L"<number>")
	{
		static std::wstringstream converter;
		converter.clear();
		Ogre::Real numValue = 0;

		converter.str(valueStr.substr(8, valueStr.find(L"</number>", 8) - 8));
		converter >> numValue;

		return FlashValue(numValue);
	}

	return FLASH_VOID;
}

std::wstring serializeInvocation(const std::wstring& funcName, const Arguments& args)
{
	std::wstring result;

	result += L"<invoke name=\"" + funcName + L"\" returntype=\"xml\">";

	if(args.size())
	{
		result += L"<arguments>";
		for(Arguments::const_iterator i = args.begin(); i != args.end(); i++)
			result += serializeValue(*i);
		result += L"</arguments>";
	}

	result += L"</invoke>";

	return result;
}

bool deserializeInvocation(const std::wstring& xmlString, std::wstring& funcName, Arguments& args)
{
	size_t indexA = 0;
	size_t indexB = 0;

	if((indexA = xmlString.find(L"<invoke name=\"")) == std::wstring::npos)
		return false;

	if((indexB = xmlString.find(L"\"", indexA + 14)) == std::wstring::npos)
		return false;

	funcName = xmlString.substr(indexA + 14, indexB - (indexA + 14));
	args.clear();

	if((indexA = xmlString.find(L"<arguments>", indexB)) == std::wstring::npos)
		return true;

	indexA += 11;
	std::wstring argString(xmlString.substr(indexA, xmlString.find(L"</arguments>", indexA) - indexA));

	for(indexA = 0, indexB = 0; true;)
	{
		if((indexA = argString.find(L"<", indexB)) == std::wstring::npos)
			break;

		if((indexB = argString.find(L">", indexA)) == std::wstring::npos)
			break;

		if(argString[indexB-1] != L'/')
		{
			if((indexB = argString.find(L">", indexB + 1)) == std::wstring::npos)
				break;
		}

		args.push_back(deserializeValue(argString.substr(indexA, indexB + 1 - indexA)));
	}

	return true;
}

class FlashHandler : public ShockwaveFlashObjects::_IShockwaveFlashEvents
{
public:	
	LPCONNECTIONPOINT connectionPoint;	
	DWORD cookie;
	int refCount;
	FlashControl* owner;

public:
	FlashHandler() : cookie(0), connectionPoint(0), refCount(0), owner(0)
	{		
	}

	virtual ~FlashHandler()
	{
		owner->comCount--;
	}

	HRESULT Init(FlashControl* owner)
	{
		this->owner = owner;
		owner->comCount++;

		HRESULT result = NOERROR;
		LPCONNECTIONPOINTCONTAINER cPointContainer = 0;
						
		if ((owner->flashInterface->QueryInterface(IID_IConnectionPointContainer, (void**)&cPointContainer) == S_OK) &&
			(cPointContainer->FindConnectionPoint(__uuidof(ShockwaveFlashObjects::_IShockwaveFlashEvents), &connectionPoint) == S_OK))			
		{
			IDispatch* dispatch = 0;
			QueryInterface(__uuidof(IDispatch), (void**)&dispatch);

			if(dispatch)
			{
				result = connectionPoint->Advise((LPUNKNOWN)dispatch, &cookie);
				dispatch->Release();
			}
		}
				
		if(cPointContainer)
			cPointContainer->Release();

		return result;
	}

	HRESULT Shutdown()
	{
		HRESULT result = S_OK;

		if(connectionPoint)
		{
			if(cookie)
			{
				result = connectionPoint->Unadvise(cookie);
				cookie = 0;
			}

			connectionPoint->Release();
			connectionPoint = 0;
		}

		return result;
	}
 
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv)
	{
		*ppv = 0;

		if (riid == IID_IUnknown)
		{
			*ppv = (LPUNKNOWN)this;
			AddRef();
			return S_OK;
		}
		else if (riid == IID_IDispatch)
		{
			*ppv = (IDispatch*)this;
			AddRef();
			return S_OK;
		}
		else if (riid == __uuidof(ShockwaveFlashObjects::_IShockwaveFlashEvents))
		{
			*ppv = (ShockwaveFlashObjects::_IShockwaveFlashEvents*) this;
			AddRef();
			return S_OK;
		}
		else
		{   
			return E_NOTIMPL;
		}
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{  
		return ++refCount;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		--refCount;

		if(!refCount)
			delete this;		

		return refCount;
	}

	virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo)
	{
		return E_NOTIMPL;
	}

	virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
	{
		return E_NOTIMPL; 
	}

	virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid,DISPID* rgDispId)
	{
		return E_NOTIMPL; 
	}

	virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
		WORD wFlags, ::DISPPARAMS __RPC_FAR *pDispParams, VARIANT __RPC_FAR *pVarResult,
		::EXCEPINFO __RPC_FAR *pExcepInfo, UINT __RPC_FAR *puArgErr)
	{
		switch(dispIdMember)
		{
		case 0x7a6:			
			break;
		case 0x96:			
			if((pDispParams->cArgs == 2) && (pDispParams->rgvarg[0].vt == VT_BSTR) && (pDispParams->rgvarg[1].vt == VT_BSTR))
				FSCommand(pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].bstrVal);
			break;
		case 0xC5:
			if((pDispParams->cArgs == 1) && (pDispParams->rgvarg[0].vt == VT_BSTR))
				FlashCall(pDispParams->rgvarg[0].bstrVal);
			break;
		case DISPID_READYSTATECHANGE:					
			break;
		default:			
			return DISP_E_MEMBERNOTFOUND;
		} 
		
		return NOERROR;
	}

	HRESULT OnReadyStateChange (long newState)
	{	
		return S_OK;
	}
    
	HRESULT OnProgress(long percentDone )
	{		
		return S_OK;
	}

	HRESULT FSCommand (_bstr_t command, _bstr_t args)
	{
		// TODO: Handle FSCommand

		return S_OK;
	}	

    HRESULT FlashCall (_bstr_t request)
	{
		owner->handleFlashCall((wchar_t*)request);

		return S_OK;
	}
};

}
}

#endif