#include <windows.h>
#include <stdio.h>
#include "kbasio_enum.h"
#include "kpi_device.h"

#define ASIODRV_DESC  L"description"
#define ASIO_PATH     L"software\\asio"
#define COM_CLSID     L"clsid"

///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioEnumerator::FindDriverFromClsIdStr(const wchar_t *cszClsId)
{
    HKEY hKeyInprocServer = NULL;//HKEY_CLASSES_ROOT\CLSID\szClsId({xxxxxxxx-xxxx-xxxx--xxxx-xxxxxxxxxxxx})\InprocServer32
    BOOL bFound = FALSE;
    wchar_t szClsIdInprocServer[128];//"CLSID\\{xxxxxxxx-xxxx-xxxx--xxxx-xxxxxxxxxxxx}\\InprocServer32"

    wcsncpy_s(szClsIdInprocServer, L"CLSID\\", _TRUNCATE);
    wcsncat_s(szClsIdInprocServer, cszClsId, _TRUNCATE);
    wcsncat_s(szClsIdInprocServer, L"\\InprocServer32", _TRUNCATE);
    do{
        if(RegOpenKeyEx(HKEY_CLASSES_ROOT, szClsIdInprocServer, 0, KEY_READ, &hKeyInprocServer) != ERROR_SUCCESS){
            break;
        }
        wchar_t szDllPath[MAX_PATH];
        szDllPath[0] = 0;
        DWORD datatype = REG_SZ;
        DWORD datasize = (DWORD)sizeof(szDllPath);//nDllPathSize;
        if(RegQueryValueEx(hKeyInprocServer, 0, 0, &datatype, (LPBYTE)szDllPath, &datasize) != ERROR_SUCCESS){
            break;
        }
        //DLL の存在チェック
        //DLL のパスに環境変数を使用している場合がある(Realtek ASIO)
        //
        #if 0 //存在チェックしない場合(レジストリに DLL のパスが登録されていればドライバが存在するものとして扱う)
        bFound = szDllPath[0] != 0;
        #else //存在チェックする場合
        static const wchar_t cszEnvSysRoot[] = L"%SystemRoot%";//パスに環境変数を使用している場合に対応(Realtek ASIO 対策)
        if(wcslen(szDllPath) > _countof(cszEnvSysRoot) &&
           _wcsnicmp(szDllPath, cszEnvSysRoot, _countof(cszEnvSysRoot)-1) == 0){
           //環境変数%SystemRoot%を使用している
            wchar_t szTmp[MAX_PATH];
            if(GetEnvironmentVariable(L"systemroot", szTmp, _countof(szTmp)) == 0){//失敗
                break;
            }
            wcsncat_s(szTmp, szDllPath + (_countof(cszEnvSysRoot)-1), _TRUNCATE);
            wcsncpy_s(szDllPath, szTmp, _TRUNCATE);
        }
        DWORD dwCode = GetFileAttributes(szDllPath);
        if(dwCode != 0xffffffff){
            if(!(FILE_ATTRIBUTE_DIRECTORY & dwCode)){
                bFound = TRUE;
            }
        }
        #endif
    }while(0);
    if(hKeyInprocServer)RegCloseKey(hKeyInprocServer);
    return bFound;
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioEnumerator::GetAsioDeviceFromKeyName(HKEY hKeyAsio, //=HKEY_LOCAL_MACHINE\software\asio
                                                           const wchar_t *cszDriverKeyName,
                                                           ASIO_DEVICE *pAsioDevice)
{
    HKEY    hKeyDriver;//HKEY_LOCAL_MACHINE\software\asio\cszDriverKeyName
    wchar_t szClsId[MAX_ID_LENGTH];
    wchar_t szDriverName[MAX_DRIVERNAME];
    CLSID   clsid;
    DWORD   datatype,datasize;

    ZeroMemory(pAsioDevice, sizeof(ASIO_DEVICE));

    if(RegOpenKeyEx(hKeyAsio, cszDriverKeyName, 0, KEY_READ, &hKeyDriver) != ERROR_SUCCESS){
        return FALSE;
    }

    datatype = REG_SZ; datasize = sizeof(szClsId);
    if(RegQueryValueEx(hKeyDriver, COM_CLSID, 0, &datatype, (BYTE*)szClsId, &datasize) != ERROR_SUCCESS){
        RegCloseKey(hKeyDriver);
        return FALSE;
    }
    if(!FindDriverFromClsIdStr(szClsId)){
        RegCloseKey(hKeyDriver);
        return FALSE;
    }
    wcsncpy_s(pAsioDevice->szClsId, szClsId, _TRUNCATE);
    if(CLSIDFromString(pAsioDevice->szClsId, &clsid) == S_OK) {
        pAsioDevice->ClsId = clsid;
    }
    datatype = REG_SZ; datasize = sizeof(szDriverName);
    if(RegQueryValueEx(hKeyDriver, ASIODRV_DESC, 0, &datatype, (BYTE*)szDriverName, &datasize) == ERROR_SUCCESS){
        wcsncpy_s(pAsioDevice->szDriverName, szDriverName, _TRUNCATE);
    }
    else{
        wcsncpy_s(pAsioDevice->szDriverName, cszDriverKeyName, _TRUNCATE);
    }
    RegCloseKey(hKeyDriver);
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
KbAsioEnumerator::KbAsioEnumerator(void)
{
    m_pDevices = NULL;
    m_nCount = 0;

    wchar_t szDriverName[128];
    HKEY    hKeyAsio = NULL;//HKEY_LOCAL_MACHINE\software\asio
    int     nIndex = 0;
    if(RegOpenKey(HKEY_LOCAL_MACHINE, ASIO_PATH, &hKeyAsio) != ERROR_SUCCESS){
        //ASIO ドライバなし
        return;
    }
    while(1){
        if(RegEnumKey(hKeyAsio, nIndex++, szDriverName, _countof(szDriverName)) != ERROR_SUCCESS){
            break;
        }
        ASIO_DEVICE asioDriver;
        if(GetAsioDeviceFromKeyName(hKeyAsio, szDriverName, &asioDriver)){
            m_pDevices = (ASIO_DEVICE*)realloc(m_pDevices, sizeof(ASIO_DEVICE)*(m_nCount+1));
            m_pDevices[m_nCount] = asioDriver;
            m_nCount++;
        }
    }
    RegCloseKey(hKeyAsio);
}
///////////////////////////////////////////////////////////////////////////////
KbAsioEnumerator::~KbAsioEnumerator(void)
{
    if(m_pDevices){
        free(m_pDevices);
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbAsioEnumerator::Enumerate(IKpiDeviceEnumerator *pDeviceEnumerator)
{
    int i;
    for(i = 0; i < m_nCount; i++){
        pDeviceEnumerator->Enumerate(m_pDevices[i].szDriverName, m_pDevices[i].szClsId);
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioEnumerator::OpenDriver(const wchar_t *cszDriverName, 
                                             const wchar_t *cszId,
                                             IASIO **ppAsio)
{
    *ppAsio = NULL;
    int i;
    for(i = 0; i < m_nCount; i++){
        if(wcscmp(m_pDevices[i].szDriverName, cszDriverName) == 0 &&
           wcscmp(m_pDevices[i].szClsId, cszId) == 0){
            CoCreateInstance(m_pDevices[i].ClsId, 0, CLSCTX_INPROC_SERVER, m_pDevices[i].ClsId, (void**)ppAsio);
            break;
        }
    }
    return *ppAsio != NULL;
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbAsioEnumerator::GetDeviceName(int nIndex, 
                                                wchar_t *pszDeviceName, int nSizeDevice,
                                                wchar_t *pszId, int nSizeId)
{
    pszDeviceName[0] = pszId[0] = 0;
    if(nIndex < 0 || nIndex >= m_nCount){
        return;
    }
    wcsncpy_s(pszDeviceName, nSizeDevice, m_pDevices[nIndex].szDriverName, _TRUNCATE);
    wcsncpy_s(pszId, nSizeId, m_pDevices[nIndex].szClsId, _TRUNCATE);
}
///////////////////////////////////////////////////////////////////////////////
