#include <windows.h>
#include "kbwav_decoder.h"
#include <mmreg.h>

static LRESULT CALLBACK mmioProcInternal(LPSTR lpmmioinfo, UINT uMsg,
                                LPARAM lParam1, LPARAM lParam2)
{
    MMIOINFO *pInfo= (MMIOINFO*)lpmmioinfo;
#ifdef _WIN64
    IKpiFile *pFile = (IKpiFile*)(((UINT64)pInfo->adwInfo[1] << 32) + pInfo->adwInfo[0]);
#else
    IKpiFile *pFile = (IKpiFile*)pInfo->adwInfo[0];
#endif
    switch(uMsg){
    case MMIOM_READ:{
        DWORD dwRet = pFile->Read((BYTE*)lParam1, lParam2);
        pInfo->lDiskOffset += dwRet;
        return dwRet;
    }
    case MMIOM_SEEK:{
        UINT64 pos = pFile->Seek(lParam1, lParam2);
        if(pos != KPI_FILE_EOF){
            pInfo->lDiskOffset = pos;
            return pos;
        }
        return -1;
    }
    case MMIOM_OPEN:break;
    case MMIOM_CLOSE: break;
    default :break;
    }
    return 0;
}
/////////////////////////////////////////////////////////////////////////
KbWavDecoder::KbWavDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_hmmio = NULL;
    m_pFile = NULL;
    m_dwBlockAlign = 0;
    m_dwStartFileOffset = 0;
    m_dwCurSample = 0;
    m_dwEndSample = 0;
}
/////////////////////////////////////////////////////////////////////////
KbWavDecoder::~KbWavDecoder(void)
{
    if(m_hmmio){
        mmioClose(m_hmmio, 0);
    }
    if(m_pFile){
        m_pFile->Release();
    }
}
/////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbWavDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                     IKpiFile   *pFile, 
                                     IKpiFolder *pFolder)
{
    if(pFile->GetSize() > 0xFFFFFFFFui64){
        return FALSE;
    }
    MMIOINFO mmioInfo = {0};
    mmioInfo.adwInfo[0] = ((DWORD_PTR)pFile) & 0xFFFFFFFF;
#ifdef _WIN64
    mmioInfo.adwInfo[1] = ((DWORD_PTR)pFile>>32) & 0xFFFFFFFF;
#endif
    mmioInfo.pIOProc = mmioProcInternal;
    HMMIO hmmio = mmioOpen(NULL, &mmioInfo, MMIO_READ);
    if(!hmmio){
        return FALSE;
    }
    do{
        MMRESULT mmr;
        MMCKINFO riffChunk = {0};
        riffChunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');
        mmr = mmioDescend(hmmio, &riffChunk, NULL, MMIO_FINDRIFF);
        if(mmr != MMSYSERR_NOERROR ) {
            break;
        }
        MMCKINFO formatChunk = {0};
        formatChunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
        mmr = mmioDescend(hmmio, &formatChunk, &riffChunk, MMIO_FINDCHUNK);
        if(mmr != MMSYSERR_NOERROR ) {
            break;
        }
        WAVEFORMATEX wfx = {0};
        DWORD wfx_size = formatChunk.cksize;
        if(wfx_size < offsetof(WAVEFORMATEX, cbSize)){
            break;
        }
        if(wfx_size > sizeof(wfx)){
            wfx_size = sizeof(wfx);
        }
        DWORD size = mmioRead(hmmio, (HPSTR)&wfx, wfx_size);
        if(size != wfx_size ) {
            break;
        }
        if(wfx.wFormatTag != WAVE_FORMAT_PCM &&
           wfx.wFormatTag != WAVE_FORMAT_IEEE_FLOAT){
            break;
        }
        mmioAscend(hmmio, &formatChunk, 0);
        MMCKINFO dataChunk = {0};
        dataChunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
        mmr = mmioDescend(hmmio, &dataChunk, &riffChunk, MMIO_FINDCHUNK);
        if(mmr != MMSYSERR_NOERROR ) {
            break;
        }
        m_MediaInfo.dwSampleRate = wfx.nSamplesPerSec;
        m_MediaInfo.dwChannels = wfx.nChannels;
        m_MediaInfo.nBitsPerSample = wfx.wBitsPerSample;
        if(wfx.wFormatTag == WAVE_FORMAT_IEEE_FLOAT){
            m_MediaInfo.nBitsPerSample *= -1;
        }
        DWORD dwBlockAlign = (wfx.wBitsPerSample / 8) * wfx.nChannels;
        m_dwStartFileOffset = mmioSeek(hmmio, 0, SEEK_CUR);
        m_hmmio = hmmio;
        m_dwBlockAlign = dwBlockAlign;
        m_dwEndSample = dataChunk.cksize / dwBlockAlign;
        m_pFile = pFile;
        pFile->AddRef();
        m_MediaInfo.qwLength = kpi_SampleTo100ns(m_dwEndSample, wfx.nSamplesPerSec); 
        m_MediaInfo.dwNumber = 1;
        m_MediaInfo.dwCount = 1;
        m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
        return 1;
    }while(0);
    mmioClose(hmmio, 0);
    return 0;
}
/////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbWavDecoder::Select(DWORD dwNumber, 
                                     const KPI_MEDIAINFO **ppMediaInfo,
                                     IKpiTagInfo *pTagInfo,
                                     DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(!ppMediaInfo){
        return 1;
    }
    *ppMediaInfo = &m_MediaInfo;
    return 1;
}
/////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbWavDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    if(m_dwCurSample + dwSizeSample > m_dwEndSample){
        dwSizeSample = m_dwEndSample - m_dwCurSample;
    }
    DWORD dwRetSample = mmioRead(m_hmmio, (HPSTR)pBuffer, dwSizeSample*m_dwBlockAlign) / m_dwBlockAlign;
    m_dwCurSample += dwRetSample;
    return dwRetSample;
}
/////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbWavDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    UINT64 qwPosFileOffset = qwPosSample * m_dwBlockAlign + m_dwStartFileOffset;
    UINT64 qwEndFileOffset = m_dwStartFileOffset + m_dwEndSample * m_dwBlockAlign;
    if(qwPosFileOffset > qwEndFileOffset){
        qwPosFileOffset = qwEndFileOffset;
    }
    INT64 retBytes = mmioSeek(m_hmmio, (DWORD)qwPosFileOffset, SEEK_SET) - m_dwStartFileOffset;
    m_dwCurSample = retBytes / m_dwBlockAlign;
    return m_dwCurSample;
}
/////////////////////////////////////////////////////////////////////////
