#define INITGUID
#include <windows.h>
#include <stdio.h>
#include "kbqcp_decoder.h"
#if PUREVOICE_SDK_VER == 2

#include "purevoicesdk/Qpl.h"
typedef HRESULT (WINAPI *pfnQPLCreateInstance)(REFCLSID, REFIID, void**);
static pfnQPLCreateInstance qcpCreateInstance = NULL;

#else
#define QSCLGUID
#include "purevoice_windows_sdk-3.5/qscl.h"
typedef void* (_cdecl *pfnQsclCreateInstance) (const QsclGuid &classId,
                                              const QsclGuid &ifId);
static pfnQsclCreateInstance qcpCreateInstance = NULL;
#endif

extern HINSTANCE g_hKpi;
static HINSTANCE g_hQcpDll = NULL;

static BOOL __fastcall LoadQplDll(void)
{
#if PUREVOICE_SDK_VER == 2
    static const wchar_t cszDllName[] = L"qpl.dll";
#else
    static const wchar_t cszDllName[] = L"qscl.dll";
#endif
    static LONG lInitializeStarted = -1;
    static LONG lInitializeDone = 0;
    if(g_hQcpDll)return TRUE;
    if(InterlockedIncrement(&lInitializeStarted) > 0){
        while(InterlockedCompareExchange(&lInitializeDone, 1, 1) == 0){
            Sleep(0);
        }
        return g_hQcpDll != NULL;
    }
    HINSTANCE hDll = NULL;
    do{
        wchar_t szDllPath[MAX_PATH];
        GetModuleFileName(g_hKpi, szDllPath, _countof(szDllPath));
        wchar_t *path_delimiter = wcsrchr(szDllPath, L'\\');
        if(!path_delimiter){
            break;
        }
        path_delimiter[1] = 0;
        wcsncat_s(szDllPath, cszDllName, _TRUNCATE);
        hDll = LoadLibrary(szDllPath);
        if(!hDll)break;
#if PUREVOICE_SDK_VER == 2
        qcpCreateInstance = (pfnQPLCreateInstance)GetProcAddress(hDll, "QPLCreateInstance");
#else
        qcpCreateInstance = (pfnQsclCreateInstance)GetProcAddress(hDll, "QsclCreateInstance");
#endif
        if(!qcpCreateInstance){
            break;
        }
        g_hQcpDll = hDll;
        InterlockedIncrement(&lInitializeDone);
        return TRUE;
    }while(0);
    if(hDll){
        FreeLibrary(hDll);
    }
    InterlockedIncrement(&lInitializeDone);
    return FALSE;
}
//////////////////////////////////////////////////////////////////////////
static void __fastcall UnloadQplDll(void)
{
    if(!g_hQcpDll)return;
    FreeLibrary(g_hQcpDll);
    g_hQcpDll = NULL;
}
//////////////////////////////////////////////////////////////////////////
KbQcpDecoder::KbQcpDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_pQcpFile = NULL;
#if PUREVOICE_SDK_VER == 2
    m_cszFileNameA = NULL;
    m_nPos = 0;
#else
    m_nSamplesPerBlock = 0;
    m_nTotalBlocks = 0;
#endif
}
//////////////////////////////////////////////////////////////////////////
KbQcpDecoder::~KbQcpDecoder(void)
{
    if(m_pQcpFile){
#if PUREVOICE_SDK_VER == 2
        m_pQcpFile->Release();
#else
        m_pQcpFile->release();
#endif
    }
    if(m_pFile){
        m_pFile->Release();
    }
}
//////////////////////////////////////////////////////////////////////////
#if PUREVOICE_SDK_VER == 2
DWORD __fastcall KbQcpDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile,
                                        IKpiFolder *pFolder)
{
    if(!cpRequest){//タグ取得用
        m_MediaInfo.dwCount = 1;
        m_pFile = pFile;
        pFile->AddRef();
        return 1;
    }
    if(!LoadQplDll()){
        return NULL;
    }
    HRESULT hr = qcpCreateInstance(CLSID_QPLPvQcpFile1,
                                   IID_IQPLQcpFile1, 
                                   (void**)&m_pQcpFile);
    if(!m_pQcpFile){
        return FALSE;
    }
    const char *cszFileNameA;
    if(!pFile->GetRealFileA(&cszFileNameA)){
        return FALSE;
    }
    if(!m_pQcpFile->Open((const unsigned char*)cszFileNameA, QPL_READ)){
        m_pQcpFile->Release();
        m_pQcpFile = NULL;
        return FALSE;
    }
    m_pFile = pFile;
    pFile->AddRef();
    m_MediaInfo.dwSampleRate = 8000;
    m_MediaInfo.dwChannels = 1;
    m_MediaInfo.nBitsPerSample = 16;
    int bs = m_pQcpFile->GetBlockSize();
    int ln = m_pQcpFile->GetLength();
    m_MediaInfo.qwLength = bs*ln*10000ui64;
    m_MediaInfo.dwSeekableFlags = 0;
    m_MediaInfo.dwUnitSample = bs * (8000/1000);//pInfo->dwUnitRender = bs*2*(8000/1000);
    m_MediaInfo.dwCount = 1;
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbQcpDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(qwPosSample == 0 && m_nPos == 0){
        return 0;
    }
    if(!m_pQcpFile){
        return 0;
    }
    m_nPos = 0;
    m_pQcpFile->Close();
    if(!m_pQcpFile->Open((const unsigned char*)m_cszFileNameA, QPL_READ)){
        m_pQcpFile->Release();
        m_pQcpFile = NULL;
    }
    return 0;
}
DWORD WINAPI KbQcpDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    if(!m_pQcpFile){
        return 0;
    }
    int nRet = m_pQcpFile->Read(1, pBuffer);
    if(nRet <= 0)nRet = 0;
    DWORD dwRet;
    if(nRet){
        dwRet = nRet - m_nPos;
        m_nPos = nRet;
    }
    else{
        dwRet = 0;
    }
    return dwRet*dwSizeSample;
}
#else
static unsigned long fnQsclClientCallback (QsclClientOp op,
                                           unsigned char *buffer,
                                           unsigned long offset,
                                           unsigned long amount,
                                           void *context)
{
    switch(op){
    case QSCL_CLIENT_READ:{
        IKpiFile *pFile = (IKpiFile*)context;
        return pFile->Read(buffer, amount);
    }
    case QSCL_CLIENT_SEEK:{
        IKpiFile *pFile = (IKpiFile*)context;
        UINT64 qwPos = pFile->Seek(offset, FILE_BEGIN);
        if(qwPos == KPI_FILE_EOF){
            return 0;
        }
        return (unsigned long)qwPos;
    }
    case QSCL_CLIENT_FILE_SIZE:{
        IKpiFile *pFile = (IKpiFile*)context;
        UINT64 qwSize = pFile->GetSize();
        if(qwSize > 0x7FFFFFFF){
            return 0;
        }
        return (unsigned long)qwSize;
    }
    }
    return 0;
}
DWORD __fastcall KbQcpDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile,
                                        IKpiFolder *pFolder)
{
    if(!cpRequest){//タグ取得用
        m_MediaInfo.dwCount = 1;
        m_pFile = pFile;
        pFile->AddRef();
        return 1;
    }
    if(!LoadQplDll()){
        return 0;
    }
    m_pQcpFile = (IQsclFile*)qcpCreateInstance(ID_QsclFile,
                                               ID_IQsclFile);
    if(!m_pQcpFile){
        return 0;
    }
    QsclHandle qh;
    qh.client.type = QSCL_HANDLE_CLIENT;
    qh.client.cb = fnQsclClientCallback;
    qh.client.context = pFile;
    QsclSpeechInfo fileFormat = {0};
    QsclSpeechInfo clientFormat = {0};
    fileFormat.format = ID_FormatQcp;
    fileFormat.codec = ID_QsclCodecQcelp;
    clientFormat.format = ID_FormatRaw;
    clientFormat.codec = ID_QsclCodecPcm;
    clientFormat.samplesPerSecond = 8000;
    clientFormat.bitsPerSample = 16;

    if(!m_pQcpFile->open(qh, false, fileFormat, clientFormat)){
        m_pQcpFile->release();
        m_pQcpFile = NULL;
        return 0;
    }
    QsclSpeechInfo info = {0};
    m_pQcpFile->getInfo(info);
    m_pFile = pFile;
    pFile->AddRef();
    m_nSamplesPerBlock = info.samplesPerBlock;
    m_nTotalBlocks = m_pQcpFile->getFileSize();
    m_MediaInfo.dwSampleRate = info.samplesPerSecond;
    m_MediaInfo.dwChannels = 1;
    m_MediaInfo.nBitsPerSample = info.bitsPerSample;
    m_MediaInfo.qwLength = kpi_SampleTo100ns(m_nTotalBlocks * m_nSamplesPerBlock, 8000);
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = m_nSamplesPerBlock;
    m_MediaInfo.dwCount = 1;
    return m_MediaInfo.dwCount;
}
UINT64 WINAPI KbQcpDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(m_nSamplesPerBlock == 0){
        return 0;
    }
    int nSeekBlock = qwPosSample / m_nSamplesPerBlock;
    if(nSeekBlock > m_nTotalBlocks){
        nSeekBlock = m_nTotalBlocks;
    }
    int ret = m_pQcpFile->seek(nSeekBlock);
    int retSample = ret * m_nSamplesPerBlock;
    return retSample;
}
DWORD WINAPI KbQcpDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    unsigned long bytes = 0;
    int nRet = m_pQcpFile->read(1, pBuffer, &bytes);
    return nRet;
}
#endif
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbQcpDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo;
    }
    if(dwNumber > m_MediaInfo.dwCount){
    //番号指定が不正
        return 0;
    }
    m_MediaInfo.dwNumber = dwNumber;
    GetTagInfo(pTagInfo, dwTagGetFlags);
    if (!ppMediaInfo) {
    //タイトル/アーティスト取得のみ
        return dwNumber;
    }
    *ppMediaInfo = &m_MediaInfo;
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbQcpDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){
        return;
    }
    pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_RIFF, dwTagGetFlags);
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbQcpDecoder::Uninitialize(void)
{
    UnloadQplDll();
}
//////////////////////////////////////////////////////////////////////////
