#include <windows.h>
#include "iasiodrv.h"
#include "asiolist.h"

#define ASIODRV_DESC		"description"
#define INPROC_SERVER		"InprocServer32"
#define ASIO_PATH			"software\\asio"
#define COM_CLSID			"clsid"

// ******************************************************************
// Local Functions 
// ******************************************************************
static LONG findDrvPath (char *clsidstr,char *dllpath,int dllpathsize)
{
	HKEY			hkEnum,hksub,hkpath;
	char			databuf[512];
	LONG 			cr,rc = -1;
	DWORD			datatype,datasize;
	DWORD			index;
	OFSTRUCT		ofs;
	HFILE			hfile;
	BOOL			found = FALSE;

#ifdef UNICODE
	CharLowerBuffA(clsidstr,strlen(clsidstr));
	if ((cr = RegOpenKeyA(HKEY_CLASSES_ROOT,COM_CLSID,&hkEnum)) == ERROR_SUCCESS) {

		index = 0;
		while (cr == ERROR_SUCCESS && !found) {
			cr = RegEnumKeyA(hkEnum,index++,databuf,512);
			if (cr == ERROR_SUCCESS) {
				CharLowerBuffA(databuf,strlen(databuf));
				if (!(strcmp(databuf,clsidstr))) {
					if ((cr = RegOpenKeyExA(hkEnum,databuf,0,KEY_READ,&hksub)) == ERROR_SUCCESS) {
						if ((cr = RegOpenKeyExA(hksub,INPROC_SERVER,0,KEY_READ,&hkpath)) == ERROR_SUCCESS) {
							datatype = REG_SZ; datasize = (DWORD)dllpathsize;
							cr = RegQueryValueEx(hkpath,0,0,&datatype,(LPBYTE)dllpath,&datasize);
							if (cr == ERROR_SUCCESS) {
								memset(&ofs,0,sizeof(OFSTRUCT));
								ofs.cBytes = sizeof(OFSTRUCT); 
								hfile = OpenFile(dllpath,&ofs,OF_EXIST);
								if (hfile) rc = 0; 
							}
							RegCloseKey(hkpath);
						}
						RegCloseKey(hksub);
					}
					found = TRUE;	// break out 
				}
			}
		}				
		RegCloseKey(hkEnum);
	}
#else
	CharLowerBuff(clsidstr,strlen(clsidstr));
	if ((cr = RegOpenKey(HKEY_CLASSES_ROOT,COM_CLSID,&hkEnum)) == ERROR_SUCCESS) {

		index = 0;
		while (cr == ERROR_SUCCESS && !found) {
			cr = RegEnumKey(hkEnum,index++,databuf,512);
			if (cr == ERROR_SUCCESS) {
				CharLowerBuff(databuf,strlen(databuf));
				if (!(strcmp(databuf,clsidstr))) {
					if ((cr = RegOpenKeyEx(hkEnum,databuf,0,KEY_READ,&hksub)) == ERROR_SUCCESS) {
						if ((cr = RegOpenKeyEx(hksub,INPROC_SERVER,0,KEY_READ,&hkpath)) == ERROR_SUCCESS) {
							datatype = REG_SZ; datasize = (DWORD)dllpathsize;
							cr = RegQueryValueEx(hkpath,0,0,&datatype,(LPBYTE)dllpath,&datasize);
							if (cr == ERROR_SUCCESS) {
								memset(&ofs,0,sizeof(OFSTRUCT));
								ofs.cBytes = sizeof(OFSTRUCT); 
								hfile = OpenFile(dllpath,&ofs,OF_EXIST);
								if (hfile) rc = 0; 
							}
							RegCloseKey(hkpath);
						}
						RegCloseKey(hksub);
					}
					found = TRUE;	// break out 
				}
			}
		}				
		RegCloseKey(hkEnum);
	}
#endif
	return rc;
}


static LPASIODRVSTRUCT newDrvStruct (HKEY hkey,char *keyname,int drvID,LPASIODRVSTRUCT lpdrv)
{
	HKEY	hksub;
	char	databuf[256];
	char	dllpath[MAXPATHLEN];
	WORD	wData[100];
	CLSID	clsid;
	DWORD	datatype,datasize;
	LONG	cr,rc;

	if (!lpdrv) {
		if ((cr = RegOpenKeyExA(hkey,keyname,0,KEY_READ,&hksub)) == ERROR_SUCCESS) {

			datatype = REG_SZ; datasize = 256;
			cr = RegQueryValueExA(hksub,COM_CLSID,0,&datatype,(LPBYTE)databuf,&datasize);
			if (cr == ERROR_SUCCESS) {
				rc = findDrvPath (databuf,dllpath,MAXPATHLEN);
				if (rc == 0) {
					lpdrv = new ASIODRVSTRUCT[1];
					if (lpdrv) {
						memset(lpdrv,0,sizeof(ASIODRVSTRUCT));
						lpdrv->drvID = drvID;
						MultiByteToWideChar(CP_ACP,0,(LPCSTR)databuf,-1,(LPWSTR)wData,100);
						if ((cr = CLSIDFromString((LPOLESTR)wData,(LPCLSID)&clsid)) == S_OK) {
							memcpy(&lpdrv->clsid,&clsid,sizeof(CLSID));
						}

						datatype = REG_SZ; datasize = 256;
						cr = RegQueryValueExA(hksub,ASIODRV_DESC,0,&datatype,(LPBYTE)databuf,&datasize);
						if (cr == ERROR_SUCCESS) {
							strcpy(lpdrv->drvname,databuf);
						}
						else strcpy(lpdrv->drvname,keyname);
					}
				}
			}
			RegCloseKey(hksub);
		}
	}	
	else lpdrv->next = newDrvStruct(hkey,keyname,drvID+1,lpdrv->next);

	return lpdrv;
}

static void deleteDrvStruct (LPASIODRVSTRUCT lpdrv)
{
	IASIO	*iasio;

	if (lpdrv != 0) {
		deleteDrvStruct(lpdrv->next);
		if (lpdrv->asiodrv) {
			iasio = (IASIO *)lpdrv->asiodrv;
			iasio->Release();
		}
		delete lpdrv;
	}
}


static LPASIODRVSTRUCT getDrvStruct (int drvID,LPASIODRVSTRUCT lpdrv)
{
	while (lpdrv) {
		if (lpdrv->drvID == drvID) return lpdrv;
		lpdrv = lpdrv->next;
	}
	return 0;
}
// ******************************************************************


// ******************************************************************
//	AsioDriverList
// ******************************************************************
AsioDriverList::AsioDriverList ()
{
	HKEY			hkEnum = 0;
	char			keyname[MAXDRVNAMELEN];
	LPASIODRVSTRUCT	pdl;
	LONG 			cr;
	DWORD			index = 0;

	numdrv		= 0;
	lpdrvlist	= 0;

#ifdef UNICODE
	cr = RegOpenKeyA(HKEY_LOCAL_MACHINE,ASIO_PATH,&hkEnum);
#else
	cr = RegOpenKey(HKEY_LOCAL_MACHINE,ASIO_PATH,&hkEnum);
#endif
	while (cr == ERROR_SUCCESS) {
#ifdef UNICODE
		if ((cr = RegEnumKeyA(hkEnum,index++,keyname,MAXDRVNAMELEN))== ERROR_SUCCESS) {
#else
		if ((cr = RegEnumKey(hkEnum,index++,keyname,MAXDRVNAMELEN))== ERROR_SUCCESS) {
#endif
			lpdrvlist = newDrvStruct (hkEnum,keyname,0,lpdrvlist);
		}
	}
	if (hkEnum) RegCloseKey(hkEnum);

	pdl = lpdrvlist;
	while (pdl) {
		numdrv++;
		pdl = pdl->next;
	}

	if (numdrv) CoInitialize(0);	// initialize COM
}

AsioDriverList::~AsioDriverList ()
{
	if (numdrv) {
		deleteDrvStruct(lpdrvlist);
		CoUninitialize();
	}
}


LONG AsioDriverList::asioGetNumDev (VOID)
{
	return (LONG)numdrv;
}


LONG AsioDriverList::asioOpenDriver (int drvID,LPVOID *asiodrv)
{
	LPASIODRVSTRUCT	lpdrv = 0;
	long			rc;

	if (!asiodrv) return DRVERR_INVALID_PARAM;

	if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
		if (!lpdrv->asiodrv) {
			rc = CoCreateInstance(lpdrv->clsid,0,CLSCTX_INPROC_SERVER,lpdrv->clsid,asiodrv);
			if (rc == S_OK) {
				lpdrv->asiodrv = *asiodrv;
				return 0;
			}
			// else if (rc == REGDB_E_CLASSNOTREG)
			//	strcpy (info->messageText, "Driver not registered in the Registration Database!");
		}
		else rc = DRVERR_DEVICE_ALREADY_OPEN;
	}
	else rc = DRVERR_DEVICE_NOT_FOUND;
	
	return rc;
}


LONG AsioDriverList::asioCloseDriver (int drvID)
{
	LPASIODRVSTRUCT	lpdrv = 0;
	IASIO			*iasio;

	if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
		if (lpdrv->asiodrv) {
			iasio = (IASIO *)lpdrv->asiodrv;
			iasio->Release();
			lpdrv->asiodrv = 0;
		}
	}

	return 0;
}

LONG AsioDriverList::asioGetDriverName (int drvID,char *drvname,int drvnamesize)
{	
	LPASIODRVSTRUCT			lpdrv = 0;

	if (!drvname) return DRVERR_INVALID_PARAM;

	if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
		if (strlen(lpdrv->drvname) < (unsigned int)drvnamesize) {
			strcpy(drvname,lpdrv->drvname);
		}
		else {
			memcpy(drvname,lpdrv->drvname,drvnamesize-4);
			drvname[drvnamesize-4] = '.';
			drvname[drvnamesize-3] = '.';
			drvname[drvnamesize-2] = '.';
			drvname[drvnamesize-1] = 0;
		}
		return 0;
	}
	return DRVERR_DEVICE_NOT_FOUND;
}

LONG AsioDriverList::asioGetDriverPath (int drvID,char *dllpath,int dllpathsize)
{
	LPASIODRVSTRUCT			lpdrv = 0;

	if (!dllpath) return DRVERR_INVALID_PARAM;

	if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
		if (strlen(lpdrv->dllpath) < (unsigned int)dllpathsize) {
			strcpy(dllpath,lpdrv->dllpath);
			return 0;
		}
		dllpath[0] = 0;
		return DRVERR_INVALID_PARAM;
	}
	return DRVERR_DEVICE_NOT_FOUND;
}

LONG AsioDriverList::asioGetDriverCLSID (int drvID,CLSID *clsid)
{
	LPASIODRVSTRUCT			lpdrv = 0;

	if (!clsid) return DRVERR_INVALID_PARAM;

	if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
		memcpy(clsid,&lpdrv->clsid,sizeof(CLSID));
		return 0;
	}
	return DRVERR_DEVICE_NOT_FOUND;
}