#include "oyagame.h"

// The call back function for timer is just defined here.
// Two parameters will be passed into user event.
static Uint32 NewTimerCallback(Uint32 interval, void *param){
	SDL_Event ev;
	ev.type=SDL_USEREVENT;
	ev.user.type=SDL_USEREVENT;
	ev.user.code=interval;
	ev.user.data1=param;
	int Stack[1]={(int)&ev};
	return CallSDL(L"PushEvent", 1, Stack );
}

//Method definition
DWORD COM_GetIDsOfNames(LPOLESTR* rgszNames, UINT cNames, DISPID* rgDispId,COMDATA *data)
{
	for (UINT i = 0; i < cNames; i++)
	{
		rgDispId[i] = DISPID_UNKNOWN;
		data->MethodName=rgszNames[i];
		if (data->pStructure())                                    rgDispId[i]=15;
		else if (!lstrcmpiW(rgszNames[i],L"test"))                 rgDispId[i]=1;
		else if (!lstrcmpiW(rgszNames[i],L"Include"))              rgDispId[i]=2;
		else if (!lstrcmpiW(rgszNames[i],L"ConstVBS"))             rgDispId[i]=3;
		else if (!lstrcmpiW(rgszNames[i],L"ConstJS"))              rgDispId[i]=4;
		else if (!lstrcmpiW(rgszNames[i],L"CreateStructure"))      rgDispId[i]=10;
		else if (!lstrcmpiW(rgszNames[i],L"int8"))                 rgDispId[i]=11;
		else if (!lstrcmpiW(rgszNames[i],L"int16"))                rgDispId[i]=12;
		else if (!lstrcmpiW(rgszNames[i],L"int24"))                rgDispId[i]=13;
		else if (!lstrcmpiW(rgszNames[i],L"int32"))                rgDispId[i]=14;
		else if (!lstrcmpiW(rgszNames[i],L"GetHWND"))              rgDispId[i]=20;
		else if (!lstrcmpiW(rgszNames[i],L"NewTimerCallback"))     rgDispId[i]=30;
		else if (!lstrcmpiW(rgszNames[i],L"edx"))                  rgDispId[i]=31;
		else if (!lstrcmpiW(rgszNames[i],L"LoadLibrary"))          rgDispId[i]=40;
		else if (!lstrcmpiW(rgszNames[i],L"LoadLibraryA"))         rgDispId[i]=40;
		else if (!lstrcmpiW(rgszNames[i],L"LoadLibraryW"))         rgDispId[i]=41;
		else rgDispId[i]=99;
	}
	return S_OK;
}

//Method Invoke
DWORD COM_Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
            DISPPARAMS* pDispParams, VARIANT* pVarResult,
            EXCEPINFO* pExcepInfo, UINT *puArgErr,COMDATA *data)
{
	try {
		int cArgs = pDispParams->cArgs;
		VARIANTARG* rgvarg = pDispParams->rgvarg;
		DWORD ret;
		TempMemory temp;
		LPSTR ANSI;
		LPWSTR UNI;
		int i,j,eax;
		HANDLE fHandle;
		unsigned char* pByte;
		switch (dispIdMember)
		{
		case 0: // surface() etc is used. Let's return pointer to structure.
			i=(int)data->pStructure();
			if (wFlags!=DISPATCH_METHOD || !i) return E_NOINTERFACE;
			if (pVarResult) {
				pVarResult->vt=VT_I4;
				pVarResult->lVal=i;
			}
			return S_OK;
		case 1: //test
			//pVarResult->vt=VT_BSTR;
			//pVarResult->bstrVal=ConstScript(""/*"On Error Resume Next '\r\n"*/,""/*"On Error Goto 0 '\r\n"*/,"Const ", " '\r\n");
			return S_OK; //if everything is all right.
			return DISP_E_BADPARAMCOUNT;  //if wrong parameter(s)

		case 2: //Include
			if (!pVarResult) return S_OK;
			if (cArgs!=1) return DISP_E_BADPARAMCOUNT;
			// Try to open the file in current directory
			UNI=ResolveSTR(&rgvarg[cArgs-1]);
			if (!UNI) throw DISP_E_TYPEMISMATCH;
			fHandle=CreateFileW(UNI,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
			if (fHandle==INVALID_HANDLE_VALUE) {// Try to open the file in oyagame directory
				ANSI=temp.TempANSI(ModuleDir(),temp.TempANSI(UNI));
				fHandle=CreateFileA(ANSI,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
				if (fHandle==INVALID_HANDLE_VALUE) throw COMADMIN_E_APP_FILE_READFAIL;
			}
			// Read the file
			i=GetFileSize(fHandle,NULL);
			ANSI=temp.TempANSI(i+1);
			ANSI[i]=0;
			if (!ReadFile(fHandle,ANSI,i,(DWORD*)&j,NULL)) throw COMADMIN_E_APP_FILE_READFAIL;
			if (j!=(DWORD)j) throw COMADMIN_E_APP_FILE_READFAIL;
			//return as Unicode
			pVarResult->vt=VT_BSTR;
			pVarResult->bstrVal=SysAllocString(temp.TempUNI(ANSI));
			return S_OK;

		case 3: //ConstVBS
			if (!pVarResult) return S_OK;
			pVarResult->vt=VT_BSTR;               //return as Unicode
			pVarResult->bstrVal=ConstScript("","","Const ", " '\r\n");
			return S_OK;
		case 4: //ConstJS
			if (!pVarResult) return S_OK;
			pVarResult->vt=VT_BSTR;               //return as Unicode
			pVarResult->bstrVal=ConstScript("","","var ", "; //\r\n");
			return S_OK;

		case 10: //CreateStructure
			if (!pVarResult) return S_OK;
			STRUCTURE_TYPE sType;
			IDispatch* obj;
			void* pStructure;
			// Resolve pointer to exsisting structure
			if (cArgs==1) {
				pStructure=NULL;
			} else if (cArgs==2) {
				pStructure=(void*)CopyINT(&rgvarg[0]);
			} else return DISP_E_BADPARAMCOUNT;
			// Resolve structure type
			sType=ResolveStructureTypeFromName(ResolveSTR(&rgvarg[cArgs-1]));
			if (sType==Struct_NULL) return TYPE_E_UNDEFINEDTYPE;
			// Create object with structure and return it
			obj=NewStructureObject(sType, pStructure);
			if (!obj) return E_UNEXPECTED;
			pVarResult->vt=VT_DISPATCH;
			pVarResult->pdispVal=obj;
			return S_OK;

		case 11://int8
		case 12://int16
		case 13://int24
		case 14://int32
			if (cArgs==1 && (wFlags & DISPATCH_PROPERTYGET)) {
				if (!pVarResult) return S_OK;
				pByte=(unsigned char*)CopyINT(&rgvarg[0]);
				for (i=eax=0;i<dispIdMember-10;i++) eax|=pByte[i]<<(i*8);
				pVarResult->vt=VT_I4;
				pVarResult->lVal=eax;
				return S_OK;
			} else if (cArgs==2 && (wFlags & DISPATCH_PROPERTYPUT)) {
				pByte=(unsigned char*)CopyINT(&rgvarg[1]);
				eax=CopyINT(&rgvarg[0]);
				for (i=0;i<dispIdMember-10;i++) {
					pByte[i]=(unsigned char)(eax & 0xFF);
					eax>>=8;
				}
				return S_OK;
			} else return DISP_E_BADPARAMCOUNT;

		case 15: //Structure: put or get
			int key;
			switch(wFlags){
				case DISPATCH_PROPERTYGET:
				case DISPATCH_METHOD:
				case DISPATCH_METHOD | DISPATCH_PROPERTYGET:
					if (cArgs==0) key=0;
					else if (cArgs==1) key=CopyINT(&rgvarg[0]);
					else return DISP_E_BADPARAMCOUNT;
					ret=data->GetStructureValue(data->MethodName, pVarResult, key);
					if (ret!=S_OK) return ret;
					return S_OK;
				case DISPATCH_PROPERTYPUT:
				case DISPATCH_METHOD | DISPATCH_PROPERTYPUT:
					if (cArgs==1) key=0;
					else if (cArgs==2) key=CopyINT(&rgvarg[1]);
					else return DISP_E_BADPARAMCOUNT;
					return data->SetStructureValue(data->MethodName, &rgvarg[0], key);
				default:
					return TYPE_E_UNDEFINEDTYPE;
			}

		case 20:// GetHWND
			if (!pVarResult) return S_OK;
			pVarResult->vt=VT_I4;
			pVarResult->lVal=(int)GetHWND();
			return S_OK;

		case 30:// NewTimerCallback
			if (!pVarResult) return S_OK;
			pVarResult->vt=VT_I4;
			pVarResult->lVal=(int)NewTimerCallback;
			return S_OK;

		case 31://edx
			if (!pVarResult) return S_OK;
			pVarResult->vt=VT_I4;
			pVarResult->lVal=data->edx;
			return S_OK;

		case 40:// LoadLibrary or LoadLibraryA
		case 41:// LoadLibraryW
			if (cArgs==1) UNI=NULL;
			else UNI=ResolveSTR(&rgvarg[0]);
			return data->LoadDLL(ResolveSTR(&rgvarg[cArgs-1]), UNI, dispIdMember==41);

		case 99:// Dynamic call of functions in SDL.h
			// Convert function name (due to the macro definition in header files)
			if      (!lstrcmpiW(data->MethodName,L"BlitSurface"))  data->MethodName=L"UpperBlit";
			else if (!lstrcmpiW(data->MethodName,L"LockMutex"))    data->MethodName=L"mutexP";
			else if (!lstrcmpiW(data->MethodName,L"UnlockMutex"))  data->MethodName=L"mutexV";
			else if (!lstrcmpiW(data->MethodName,L"AllocSurface")) data->MethodName=L"CreateRGBSurface";
			// Call the DLL function dynamically.
			if (lstrcmpiW(data->FuncPrefix,L"SDL_")) {
				// Library may not be SDL.dll
				eax=0;
				data->DynaCall(data->MethodName, rgvarg, cArgs, &eax, &data->edx);
			} else if (!lstrcmpiW(data->MethodName,L"VERSION")) {
				//#define SDL_VERSION(X) {(X)->major = SDL_MAJOR_VERSION;(X)->minor = SDL_MINOR_VERSION;(X)->patch = SDL_PATCHLEVEL;}
				if (cArgs!=1) return DISP_E_BADPARAMCOUNT;
				SDL_version* version;
				version=(SDL_version*)CopyINT(rgvarg);
				version->major = SDL_MAJOR_VERSION;
				version->minor = SDL_MINOR_VERSION;
				version->patch = SDL_PATCHLEVEL;
				return S_OK;
			} else if (!lstrcmpiW(data->MethodName,L"getenv")) {
				//#define SDL_getenv	getenv
				if (cArgs!=1) return DISP_E_BADPARAMCOUNT;
				if (!pVarResult) return S_OK;
				_wgetenv_s((size_t *) &i, NULL, 0, ResolveSTR(rgvarg));
				if (!i) return STG_E_INVALIDPARAMETER;
				UNI=temp.TempUNI(i);
				if (_wgetenv_s((size_t *) &j, UNI, i, ResolveSTR(rgvarg))) return STG_E_INVALIDPARAMETER;
				pVarResult->vt=VT_BSTR;
				pVarResult->bstrVal=SysAllocString(UNI);
				return S_OK;
			} else if (!lstrcmpiW(data->MethodName,L"putenv")) {
				//#define SDL_putenv	putenv
				if (cArgs!=2) return DISP_E_BADPARAMCOUNT;
				if (_wputenv_s(ResolveSTR(&rgvarg[1]),ResolveSTR(&rgvarg[0]))) return STG_E_INVALIDPARAMETER;
				return S_OK;
			} else if (!lstrcmpiW(data->MethodName,L"LoadWAV")) {
				//#define SDL_LoadWAV(file, spec, audio_buf, audio_len) 	SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len)
				if (cArgs!=4) return DISP_E_BADPARAMCOUNT;
				eax=CallSDL(L"RWFromFile", CopyINT(&rgvarg[3]), (int)"rb" );
				eax=CallSDL(L"LoadWAV_RW", eax, 1, CopyINT(&rgvarg[2]),CopyINT(&rgvarg[1]),CopyINT(&rgvarg[0]) );
			} else if (!lstrcmpiW(data->MethodName,L"LoadBMP")) {
				//#define SDL_LoadBMP(file)	SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1)
				if (cArgs!=1) return DISP_E_BADPARAMCOUNT;
				eax=CallSDL(L"RWFromFile", (int)temp.TempANSI(&rgvarg[0]), (int)"rb" );
				eax=CallSDL(L"LoadBMP_RW", eax, 1);
			} else if (!lstrcmpiW(data->MethodName,L"SaveBMP")) {
				//#define SDL_SaveBMP(surface, file) 		SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1)
				if (cArgs!=2) return DISP_E_BADPARAMCOUNT;
				eax=CallSDL(L"RWFromFile", (int)temp.TempANSI(&rgvarg[0]), (int)"wb" );
				eax=CallSDL(L"SaveBMP_RW", CopyINT(&rgvarg[1]), eax, 1);
			} else {
				// Let's call functions in SDL dynamically!
				eax=0;
				data->DynaCall(data->MethodName, rgvarg, cArgs, &eax, &data->edx);
			}
			if (pVarResult) {
				UNI=temp.TempUNI(data->FuncPrefix,data->MethodName);
				if (false==ConverToStructureObjectOrString(UNI, (void*) eax, pVarResult)){
					pVarResult->vt=VT_I4;
					pVarResult->lVal=eax;
				}
			}
			return S_OK;

		default:return (DWORD)DISPID_UNKNOWN;
	  }
	// Error handling.
	} catch(DWORD e) {
		return e;
	} catch(long e) {
		return e;
	} catch(LPWSTR s) {
		pExcepInfo->wCode=0x0201;
		pExcepInfo->bstrDescription=SysAllocString(s);
		return DISP_E_EXCEPTION;
	} catch(...) {
		return SPAPI_E_UNKNOWN_EXCEPTION;
	}
}
