#include "asio.h"

#ifdef SHR3D_AUDIO_ASIO

static i32 findDrvPath(char* clsidstr, char* dllpath, i32 dllpathsize)
{
  i32 cr;
  i32 rc = -1;

  HKEY hkEnum;
  CharLowerBuffA(clsidstr, DWORD(strlen(clsidstr)));
  if ((cr = RegOpenKeyA(HKEY_CLASSES_ROOT, "clsid", &hkEnum)) == ERROR_SUCCESS)
  {
    DWORD index = 0;
    BOOL found = FALSE;
    while (cr == ERROR_SUCCESS && !found) {
      char databuf[512];
      cr = RegEnumKeyA(hkEnum, index++, databuf, 512);
      if (cr == ERROR_SUCCESS) {
        CharLowerBuffA(databuf, DWORD(strlen(databuf)));
        if (!(strcmp(databuf, clsidstr))) {
          HKEY hksub;
          if ((cr = RegOpenKeyExA(hkEnum, databuf, 0, KEY_READ, &hksub)) == ERROR_SUCCESS) {
            HKEY hkpath;
            if ((cr = RegOpenKeyExA(hksub, "InprocServer32", 0, KEY_READ, &hkpath)) == ERROR_SUCCESS) {
              DWORD datatype = REG_SZ;
              DWORD datasize = (DWORD)dllpathsize;
              cr = RegQueryValueExA(hkpath, 0, 0, &datatype, (LPBYTE)dllpath, &datasize);
              if (cr == ERROR_SUCCESS) {
                OFSTRUCT ofs{};
                ofs.cBytes = sizeof(OFSTRUCT);
                HFILE hfile = OpenFile(dllpath, &ofs, OF_EXIST);
                if (hfile)
                  rc = 0;
              }
              RegCloseKey(hkpath);
            }
            RegCloseKey(hksub);
          }
          found = TRUE;	// break out 
        }
      }
    }
    RegCloseKey(hkEnum);
  }
  return rc;
}


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

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

      datatype = REG_SZ; datasize = 256;
      cr = RegQueryValueExA(hksub, "clsid", 0, &datatype, (LPBYTE)databuf, &datasize);
      if (cr == ERROR_SUCCESS) {
        rc = findDrvPath(databuf, dllpath, MAXPATHLEN);
        if (rc == 0) {
          lpdrv = new ASIO::drvstruct[1];
          if (lpdrv) {
            memset(lpdrv, 0, sizeof(ASIO::drvstruct));
            lpdrv->drvID = drvID;
            MultiByteToWideChar(CP_ACP, 0, 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, "description", 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 ASIO::drvstruct* getDrvStruct(i32 drvID, ASIO::drvstruct* lpdrv)
{
  while (lpdrv) {
    if (lpdrv->drvID == drvID)
      return lpdrv;
    lpdrv = lpdrv->next;
  }
  return 0;
}

ASIO::Manager::Manager()
{
  HKEY hkEnum = 0;
  i32 cr = RegOpenKeyA(HKEY_LOCAL_MACHINE, "software\\asio", &hkEnum);
  DWORD index = 0;
  while (cr == ERROR_SUCCESS) {
    char keyname[MAXDRVNAMELEN];
    if ((cr = RegEnumKeyA(hkEnum, index++, keyname, MAXDRVNAMELEN)) == ERROR_SUCCESS) {
      lpdrvlist = newDrvStruct(hkEnum, keyname, 0, lpdrvlist);
    }
  }
  if (hkEnum)
    RegCloseKey(hkEnum);

  ASIO::drvstruct* pdl = lpdrvlist;
  while (pdl != nullptr) {
    ++numDrivers;
    pdl = pdl->next;
  }

  if (numDrivers != 0)
    CoInitialize(nullptr);	// initialize COM
}

i32 ASIO::Manager::OpenDriver(i32 drvID, void** asiodrv)
{
  ASIO::drvstruct* lpdrv = 0;
  i32 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
      rc = DRVERR_DEVICE_ALREADY_OPEN;
  }
  else
    rc = DRVERR_DEVICE_NOT_FOUND;

  return rc;
}


i32 ASIO::Manager::CloseDriver(i32 drvID)
{
  ASIO::drvstruct* lpdrv = 0;
  IASIO* iasio;

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

  return 0;
}

i32 ASIO::Manager::GetDriverName(i32 drvID, char* drvname, i32 drvnamesize)
{
  const ASIO::drvstruct* lpdrv = nullptr;

  if (!drvname)
    return DRVERR_INVALID_PARAM;

  if ((lpdrv = getDrvStruct(drvID, lpdrvlist)) != 0) {
    if (strlen(lpdrv->drvname) < (u32)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;
}

ASIO::IASIO* ASIO::Manager::loadDriver(const char* name)
{
  ASIO::IASIO* theAsioDriver = nullptr;

  char dname[64];

  for (i32 i = 0; i < numDrivers; i++)
  {
    if (!GetDriverName(i, dname, 32) && !strcmp(name, dname))
    {
      if (!OpenDriver(i, (void**)&theAsioDriver))
        curIndex = i;
      break;
    }
  }
  return theAsioDriver;
}

#endif // SHR3D_AUDIO_ASIO
