/*
    TiMidity++ -- MIDI to WAVE converter and player
    Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
    Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>

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

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
//#define DEBUG
#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

#if __DMC__
unsigned long _beginthreadex( void *security, unsigned stack_size,
		unsigned ( __stdcall *start_address )( void * ), void *arglist,
		unsigned initflag, unsigned *thrdaddr );
void _endthreadex( unsigned retval );
#endif

#ifdef DEBUG
#include <stdio.h>
#endif

#include <windows.h>
#include "mmddk.h"
#include <mmsystem.h>
#if defined(__DMC__)
//following codes are from wine's mmsystem.h
typedef struct tagMIDIOUTCAPS2A {
    WORD	wMid;
    WORD	wPid;
    MMVERSION	vDriverVersion;
    CHAR	szPname[MAXPNAMELEN];
    WORD	wTechnology;
    WORD	wVoices;
    WORD	wNotes;
    WORD	wChannelMask;
    DWORD	dwSupport;
    GUID	ManufacturerGuid;
    GUID	ProductGuid;
    GUID	NameGuid;
} MIDIOUTCAPS2A, *LPMIDIOUTCAPS2A;

typedef struct tagMIDIOUTCAPS2W {
    WORD	wMid;
    WORD	wPid;
    MMVERSION	vDriverVersion;
    WCHAR	szPname[MAXPNAMELEN];
    WORD	wTechnology;
    WORD	wVoices;
    WORD	wNotes;
    WORD	wChannelMask;
    DWORD	dwSupport;
    GUID	ManufacturerGuid;
    GUID	ProductGuid;
    GUID	NameGuid;
} MIDIOUTCAPS2W, *LPMIDIOUTCAPS2W;

//DECL_WINELIB_TYPE_AW(MIDIOUTCAPS2)
//DECL_WINELIB_TYPE_AW(LPMIDIOUTCAPS2)
#endif

#include <process.h>

// GUID definitions from wine's guiddef.h
#ifndef GUID_DEFINED
#define GUID_DEFINED
typedef struct _GUID
{
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[ 8 ];
} GUID;

#endif
//GUID numbers are generated by timiditydrv.idl
const GUID CLSID_tim_synth = {0x0FEC4C35,0xA705,0x41d7,{0xA3,0xBB,0xD5,0x87,0xA2,0x31,0x04,0x5A}};

#include "config.h"
#include "sysdep.h"
#include "output.h"

#include "timiwp_timidity.h"

static volatile int OpenCount = 0;
static volatile int modm_closed = 1;

static HDRVR hdrvrInstance;
static MIDIOPENDESC mid_desc;
static DWORD dwOpenFlags;

static CRITICAL_SECTION mim_section;
static volatile int stop_thread = 0;
static HANDLE hRtsynThread = NULL;
static DWORD processPriority;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ){
	if (fdwReason == DLL_PROCESS_ATTACH){
		DisableThreadLibraryCalls(hinstDLL);
	}else if(fdwReason == DLL_PROCESS_DETACH){
		;
	}
	return TRUE;
}

STDAPI DllCanUnloadNow(void){
	return S_OK;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID * ppv){
	return S_OK;
}

STDAPI DllRegisterServer(void){
	return S_OK;
}

STDAPI DllUnregisterServer(void)
{
	return S_OK;
}

STDAPI_(LRESULT) DriverProc(DWORD_PTR dwDriverId, HDRVR hdrvr, UINT msg, LPARAM lParam1, LPARAM lParam2){
	switch(msg) {
	case DRV_OPEN:
		hdrvrInstance = hdrvr;
		return 1;
	case DRV_REMOVE:
		if (modm_closed == 0){
			int maxloop=500;

			stop_thread = 1;    //why thread can't stop by this????
			while( stop_thread != 0 && maxloop-- > 0) Sleep(10);
			if(stop_thread != 0) TerminateThread(hRtsynThread, FALSE);
			CloseHandle(hRtsynThread);
			SetPriorityClass(GetCurrentProcess(), processPriority);
		}
		DeleteCriticalSection(&mim_section);
		return 1;
	case DRV_LOAD:
		processPriority = GetPriorityClass(GetCurrentProcess());
		InitializeCriticalSection(&mim_section);
		return 1;
	case DRV_CLOSE:
	case DRV_CONFIGURE:
	case DRV_DISABLE:
	case DRV_ENABLE:
	case DRV_EXITSESSION:
	case DRV_FREE:
	case DRV_INSTALL:
	case DRV_POWER:
	case DRV_QUERYCONFIGURE:
	default:
		return 1;
	}
	return DefDriverProc(dwDriverId, hdrvr, msg, lParam1, lParam2);
}

HRESULT modGetCaps(PVOID capsPtr, DWORD capsSize) {
	MIDIOUTCAPSA * myCapsA;
	MIDIOUTCAPSW * myCapsW;
	MIDIOUTCAPS2A * myCaps2A;
	MIDIOUTCAPS2W * myCaps2W;

	CHAR synthName[] = "Timidity++ Driver\0";
	WCHAR synthNameW[] = L"Timidity++ Driver\0";

	switch (capsSize) {
	case (sizeof(MIDIOUTCAPSA)):
		myCapsA = (MIDIOUTCAPSA *)capsPtr;
		myCapsA->wMid = 0xffff; //MM_UNMAPPED
		myCapsA->wPid = 0xffff; //MM_PID_UNMAPPED
		memcpy(myCapsA->szPname, synthName, sizeof(synthName));
		myCapsA->wTechnology = MOD_MIDIPORT;
		myCapsA->vDriverVersion = 0x0090;
		myCapsA->wVoices = 0;
		myCapsA->wNotes = 0;
		myCapsA->wChannelMask = 0xffff;
		myCapsA->dwSupport = 0;
		return MMSYSERR_NOERROR;
	case (sizeof(MIDIOUTCAPSW)):
		myCapsW = (MIDIOUTCAPSW *)capsPtr;
		myCapsW->wMid = 0xffff;
		myCapsW->wPid = 0xffff;
		memcpy(myCapsW->szPname, synthNameW, sizeof(synthNameW));
		myCapsW->wTechnology = MOD_MIDIPORT;
		myCapsW->vDriverVersion = 0x0090;
		myCapsW->wVoices = 0;
		myCapsW->wNotes = 0;
		myCapsW->wChannelMask = 0xffff;
		myCapsW->dwSupport = 0;
		return MMSYSERR_NOERROR;
	case (sizeof(MIDIOUTCAPS2A)):
		myCaps2A = (MIDIOUTCAPS2A *)capsPtr;
		myCaps2A->wMid = 0xffff;
		myCaps2A->wPid = 0xffff;
		memcpy(myCaps2A->szPname, synthName, sizeof(synthName));
		myCaps2A->wTechnology = MOD_MIDIPORT;
		myCaps2A->vDriverVersion = 0x0090;
		myCaps2A->wVoices = 0;
		myCaps2A->wNotes = 0;
		myCaps2A->wChannelMask = 0xffff;
		myCaps2A->dwSupport = 0;
		myCaps2A->ManufacturerGuid = CLSID_tim_synth;
		myCaps2A->ProductGuid = CLSID_tim_synth;
		myCaps2A->NameGuid = CLSID_tim_synth;
		return MMSYSERR_NOERROR;
	case (sizeof(MIDIOUTCAPS2W)):
		myCaps2W = (MIDIOUTCAPS2W *)capsPtr;
		myCaps2W->wMid = 0xffff;
		myCaps2W->wPid = 0xffff;
		memcpy(myCaps2W->szPname, synthNameW, sizeof(synthNameW));
		myCaps2W->wTechnology = MOD_MIDIPORT;
		myCaps2W->vDriverVersion = 0x0090;
		myCaps2W->wVoices = 0;
		myCaps2W->wNotes = 0;
		myCaps2W->wChannelMask = 0xffff;
		myCaps2W->dwSupport = 0;
		myCaps2W->ManufacturerGuid = CLSID_tim_synth;
		myCaps2W->ProductGuid = CLSID_tim_synth;
		myCaps2W->NameGuid = CLSID_tim_synth;
		return MMSYSERR_NOERROR;
	default:
		return MMSYSERR_ERROR;
	}

}

#include <stdio.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#ifdef _WIN32
#define alloca _alloca
#endif

unsigned __stdcall threadfunc(LPVOID lpV){
	while(stop_thread == 0){
		Sleep(1);
		//timsyn_play_some_data();
		EnterCriticalSection(&mim_section);
		rtsyn_play_calculate();
		LeaveCriticalSection(&mim_section);
	}
	rtsyn_stop_playing();
	rtsyn_close();
	timiwp_main_close();
	stop_thread=0;
	_endthreadex(0);
	return 0;
}

static void modCallback(UINT uMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
//	DWORD_PTR dwUser, dwSend1, dwSend2;
	DWORD_PTR dwCallback = mid_desc.dwCallback;
	DWORD_PTR dwInstance = mid_desc.dwInstance;

	if (!dwCallback) {
	    return;
	}
/*
	switch (uMsg) {
	case MM_MOM_DONE:
	    dwUser = mid_desc.dwInstance;
	    dwSend1 = (DWORD_PTR)(mid_desc.hMidi);
	    dwSend2 = dwParam1;
	    break;
	default:
	    dwUser = mid_desc.dwInstance;
	    dwSend1 = (DWORD_PTR)(mid_desc.hMidi);
	    dwSend2 = 0;
	    break;
	}
*/

	switch (dwOpenFlags & CALLBACK_TYPEMASK) {
	case CALLBACK_NULL:
	    break;
	case CALLBACK_WINDOW:
	    {
//		HWND hWnd = (HWND)(dwCallback);
//		PostMessage(hWnd, uMsg, (WPARAM)(dwSend1), (LPARAM)(dwSend2));
		DriverCallback(dwCallback, DCB_WINDOW, hdrvrInstance, uMsg, dwInstance, dwParam1, dwParam2);
	    }
	    break;
	case CALLBACK_FUNCTION:
	    {
//		typedef void (CALLBACK *MIDIOUTPROC)(HMIDIOUT, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
//		MIDIOUTPROC MidiOutProc = (MIDIOUTPROC)(dwCallback);
//		(*MidiOutProc)((HMIDIOUT)mid_desc.hMidi, uMsg, dwUser, dwSend1, dwSend2);
		DriverCallback(dwCallback, DCB_FUNCTION, hdrvrInstance, uMsg, dwInstance, dwParam1, dwParam2);
	    }
	    break;
	case CALLBACK_THREAD:
	    {
//		DWORD dwThread = (DWORD)(dwCallback);
//		PostThreadMessage(dwThread, uMsg, (WPARAM)(dwSend1), (LPARAM)(dwSend2));
		DriverCallback(dwCallback, DCB_TASK, hdrvrInstance, uMsg, dwInstance, dwParam1, dwParam2);
	    }
	    break;
	case CALLBACK_EVENT:
	    {
//		HANDLE hEvent = (HANDLE)(dwCallback);
//		SetEvent(hEvent);
		DriverCallback(dwCallback, DCB_EVENT, hdrvrInstance, uMsg, dwInstance, dwParam1, dwParam2);
	    }
	    break;
	default:
	    {
		/* not implemented */
	    }
	    break;
	}

	return;
}

STDAPI_(DWORD) modMessage(UINT uDeviceID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2){
	MIDIHDR *IIMidiHdr;

	switch (uMsg) {
	case MODM_OPEN:
		OpenCount++;
		if ( OpenCount == 1 && modm_closed  == 1 ){
			unsigned thrdaddr;
			const char *argv_readOnly[] = {"timidity", "iW", "-B3"};
			int argc = ARRAY_SIZE(argv_readOnly),i;
			int opend=0;
			char **argv = (char **)alloca(argc * sizeof(char *));

			ZeroMemory(&mid_desc, sizeof(MIDIOPENDESC));
			if (dwParam1) {
				CopyMemory(&mid_desc, (LPMIDIOPENDESC)dwParam1, sizeof(MIDIOPENDESC));
			}
			dwOpenFlags = (DWORD)dwParam2;

			processPriority = GetPriorityClass(GetCurrentProcess());
			SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
			//AllocConsole();
			//freopen("CONOUT$", "wb", stdout);
			for (i = 0; i < argc; i++)
				argv[i] = strdup(argv_readOnly[i]);
			while(TRUE) {
				if (timiwp_main_ini(argc, argv) == 0){
					rtsyn_init();
					opend = 1;
					break;
				}
				Sleep(100);
			}
			for(i = 0 ; i < argc; i++) free(argv[i]);
			hRtsynThread=(HANDLE)_beginthreadex(NULL,0,threadfunc,0,0,&thrdaddr);
			SetPriorityClass(hRtsynThread, REALTIME_PRIORITY_CLASS);
			SetThreadPriority(hRtsynThread, THREAD_PRIORITY_TIME_CRITICAL);
			modCallback(MM_MOM_OPEN, dwParam1, dwParam2);
			modm_closed = 0;
		}
		SetPriorityClass(GetCurrentProcess(), processPriority);
		return MMSYSERR_NOERROR;
	case MODM_PREPARE:
		return MMSYSERR_NOTSUPPORTED;
	case MODM_UNPREPARE:
		return MMSYSERR_NOTSUPPORTED;
	case MODM_GETDEVCAPS:
		return modGetCaps((PVOID)dwParam1, dwParam2);
	case MODM_LONGDATA:
		{
		double event_time = get_current_calender_time();
		DWORD dwLength;
		IIMidiHdr = (MIDIHDR *)dwParam1;
		if( !(IIMidiHdr->dwFlags & MHDR_PREPARED) ) return MIDIERR_UNPREPARED;
		IIMidiHdr->dwFlags &= ~MHDR_INQUEUE;
		IIMidiHdr->dwFlags |= MHDR_DONE;
		EnterCriticalSection(&mim_section);
		dwLength = IIMidiHdr->dwBufferLength;
		if (IIMidiHdr->dwBytesRecorded > 0 && IIMidiHdr->dwBytesRecorded <= IIMidiHdr->dwBufferLength) {
			dwLength = IIMidiHdr->dwBytesRecorded;
		}
		rtsyn_play_one_sysex(IIMidiHdr->lpData, dwLength, event_time);
		LeaveCriticalSection(&mim_section);
		modCallback(MM_MOM_DONE, dwParam1, dwParam2);
		return MMSYSERR_NOERROR;
		}
	case MODM_DATA:
		{
		double event_time = get_current_calender_time();
		EnterCriticalSection(&mim_section);
		rtsyn_play_one_data (0, dwParam1, event_time);
		LeaveCriticalSection(&mim_section);
		return MMSYSERR_NOERROR;
		}
	case MODM_GETNUMDEVS:
		return 0x1;
	case MODM_CLOSE:
		if ( stop_thread != 0 ) return MIDIERR_STILLPLAYING;
		--OpenCount;
		if(OpenCount < 0){
			OpenCount = 0;
			return MMSYSERR_NOTENABLED;
		}
		modCallback(MM_MOM_CLOSE, dwParam1, dwParam2);
		return MMSYSERR_NOERROR;
		break;
	default:
		return MMSYSERR_NOERROR;
		break;
	}
}

