#include <windows.h>
#include <stdio.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys.h>

#include "kpi_device.h"
#include "kbwasapi_enum.h"
#include "kbwasapi_config.h"

static const wchar_t g_cszSuffix_Event[] = L"(event)";
static const wchar_t g_cszSuffix_Push[] = L"(push)";
extern wchar_t g_szCfgFileName[MAX_PATH];
////////////////////////////////////////////////////////////////
KbWasapiEnumerator::KbWasapiEnumerator(IKpiConfig *pConfig)
{
    m_nCount = 0;
    m_pDevices = NULL;
    m_pConfig = NULL;
    m_pEnumerator = NULL;
    IMMDeviceEnumerator* pEnumerator = NULL;
    IMMDeviceCollection* pCollection = NULL;
    UINT i;
    do{
        HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                                      NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pEnumerator));
        if(FAILED(hr)){
            break;
        }
        hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pCollection);
        if(FAILED(hr)){
            break;
        }
        UINT nCount;
        if(FAILED(pCollection->GetCount(&nCount)) || nCount == 0){
            break;
        }
        WASAPI_DEVICE *pDevices = (WASAPI_DEVICE*)malloc(sizeof(WASAPI_DEVICE)*nCount);
        ZeroMemory(pDevices, sizeof(WASAPI_DEVICE)*nCount);
        for(i = 0; i < nCount; i++){
            IMMDevice *pMmDevice = NULL;
            hr = pCollection->Item(i, &pMmDevice);
            if(FAILED(hr)){
                break;
            }
            BOOL bOk = FALSE;
            IPropertyStore *pProps = NULL;
            if(SUCCEEDED(pMmDevice->OpenPropertyStore( STGM_READ, &pProps))){
                PROPVARIANT varName;
                PropVariantInit(&varName);
                LPWSTR pszID = NULL;
                pMmDevice->GetId(&pszID);
                if(pszID){
                    wcsncpy_s(pDevices[i].szId, pszID, _TRUNCATE);
                    CoTaskMemFree(pszID);
                }
                if(SUCCEEDED(pProps->GetValue(PKEY_Device_FriendlyName, &varName))){
                    wcsncpy_s(pDevices[i].szDeviceName, varName.pwszVal, _TRUNCATE);
                    bOk = TRUE;
                }
                PropVariantClear(&varName);
            }
            if(pProps)pProps->Release();
            if(pMmDevice)pMmDevice->Release();
            if(!bOk){
                break;
            }
        }
        m_pDevices = pDevices;
        m_nCount = i;
    }while(0);
    if(pCollection)pCollection->Release();
    m_pEnumerator = pEnumerator;
    m_pConfig = pConfig;
    pConfig->AddRef();
}
////////////////////////////////////////////////////////////////
KbWasapiEnumerator::~KbWasapiEnumerator(void)
{
    if(m_pDevices)free(m_pDevices);
    if(m_pEnumerator)m_pEnumerator->Release();
    if(m_pConfig)m_pConfig->Release();
}
////////////////////////////////////////////////////////////////
void __fastcall KbWasapiEnumerator::EnumerateDevices(IKpiDeviceEnumerator *pDeviceEnumerator)
{//デバイス名の列挙
    int i;
    wchar_t szDeviceName[MAX_DEVICE_LENGTH+32];
    //event モードで一覧(末尾に (event) を付加)
    for(i = 0; i < m_nCount; i++){
        wcsncpy_s(szDeviceName, m_pDevices[i].szDeviceName, _TRUNCATE);
        wcsncat_s(szDeviceName, g_cszSuffix_Event, _TRUNCATE);
        KbWasapiConfig cfg(m_pConfig, szDeviceName, m_pDevices[i].szId);
        if(cfg.GetEnumerate()){
            if(pDeviceEnumerator){
                pDeviceEnumerator->Enumerate(szDeviceName, m_pDevices[i].szId);
            }
        }
    }
    //push モードで一覧(末尾に (push) を付加)
    for(i = 0; i < m_nCount; i++){
        wcsncpy_s(szDeviceName, m_pDevices[i].szDeviceName, _TRUNCATE);
        wcsncat_s(szDeviceName, g_cszSuffix_Push, _TRUNCATE);
        KbWasapiConfig cfg(m_pConfig, szDeviceName, m_pDevices[i].szId);
        if(cfg.GetEnumerate()){
            if(pDeviceEnumerator){
                pDeviceEnumerator->Enumerate(szDeviceName, m_pDevices[i].szId);
            }
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbWasapiEnumerator::GetMmDevice(const wchar_t *cszDeviceName, 
                                                const wchar_t *cszId, 
                                                IMMDevice **ppMmDevice,
                                                int *pMode)
{
    *ppMmDevice = NULL;
    *pMode = MODE_UNDEFINED;
    if(!m_pEnumerator || m_nCount == 0){
        return;
    }
    if(cszDeviceName[0] == 0){
        m_pEnumerator->GetDevice(m_pDevices[0].szId, ppMmDevice);
        return;
    }
    //デバイス名の末尾に (event) か (push) がある筈なので探す
    wchar_t szDeviceName[MAX_DEVICE_LENGTH+32];
    wcsncpy_s(szDeviceName, cszDeviceName, _TRUNCATE);
    int i = 0;
    int nMode = MODE_UNDEFINED;

    while(szDeviceName[i]){
        while(szDeviceName[i] != g_cszSuffix_Event[0]){
            if(!szDeviceName[i]){
                return;
            }
            i++;
        }
        if(wcscmp(&szDeviceName[i], g_cszSuffix_Event) == 0){
            nMode = MODE_EVENT;//event
            szDeviceName[i] = 0;
            break;
        }
        else if(wcscmp(&szDeviceName[i], g_cszSuffix_Push) == 0){
            nMode = MODE_PUSH;//push
            szDeviceName[i] = 0;
            break;
        }
        i++;
    }
    if(nMode == MODE_UNDEFINED){
        return;
    }
    for(i = 0; i < m_nCount; i++){
        if(wcscmp(m_pDevices[i].szDeviceName, szDeviceName) == 0 &&
           wcscmp(m_pDevices[i].szId, cszId) == 0){
            m_pEnumerator->GetDevice(cszId, ppMmDevice);
            *pMode = nMode;
            return;
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbWasapiEnumerator::GetDeviceName(int nIndex, 
                                                  wchar_t *pszDeviceName, int nSizeDevice,
                                                  wchar_t *pszId, int nSizeId)
{
    pszDeviceName[0] = pszId[0] = 0;
    if(nIndex < 0 || nIndex >= m_nCount*2){
        return;
    }
    bool bIsEvent = nIndex < m_nCount;
    nIndex %= m_nCount;
    wcsncpy_s(pszDeviceName, nSizeDevice, m_pDevices[nIndex].szDeviceName, _TRUNCATE);
    wcsncat_s(pszDeviceName, nSizeDevice, bIsEvent ? g_cszSuffix_Event : g_cszSuffix_Push, _TRUNCATE);
    wcsncpy_s(pszId, nSizeId, m_pDevices[nIndex].szId, _TRUNCATE);
}
///////////////////////////////////////////////////////////////////////////////
