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

#include "kbwv_decoder.h"
/*
typedef struct {
    int32_t (*read_bytes)(void *id, void *data, int32_t bcount);
    int32_t (*write_bytes)(void *id, void *data, int32_t bcount);
    int64_t (*get_pos)(void *id);                               // new signature for large files
    int (*set_pos_abs)(void *id, int64_t pos);                  // new signature for large files
    int (*set_pos_rel)(void *id, int64_t delta, int mode);      // new signature for large files
    int (*push_back_byte)(void *id, int c);
    int64_t (*get_length)(void *id);                            // new signature for large files
    int (*can_seek)(void *id);
    int (*truncate_here)(void *id);                             // new function to truncate file at current position
    int (*close)(void *id);                                     // new function to close file
} WavpackStreamReader64;
*/
static int32_t wavpack_read_bytes (void *id, void *data, int32_t bcount)
{
    IKpiFile *pFile = (IKpiFile*)id;
    return pFile->Read(data, bcount);
}
/*static int32_t wavpack_write_bytes(void *id, void *data, int32_t bcount)
{
    return 0;
}*/
static int64_t wavpack_get_pos (void *id)
{
    IKpiFile *pFile = (IKpiFile*)id;
    return pFile->Seek(0, FILE_CURRENT);
}

static int wavpack_set_pos_abs (void *id, int64_t pos)
{
    IKpiFile *pFile = (IKpiFile*)id;
    if(pFile->Seek(pos, FILE_BEGIN) == KPI_FILE_EOF){
        return 1;
    }
    return 0;
}

static int wavpack_set_pos_rel (void *id, int64_t delta, int mode)
{
    IKpiFile *pFile = (IKpiFile*)id;
    if(pFile->Seek(delta, FILE_CURRENT) == KPI_FILE_EOF){
        return 1;
    }
    return 0;
}

static int wavpack_push_back_byte (void *id, int c)
{
    IKpiFile *pFile = (IKpiFile*)id;
    if(pFile->Seek(-1, FILE_CURRENT) == KPI_FILE_EOF){
        return 1;
    }
    return 0;
}

static int64_t wavpack_get_length (void *id)
{
    IKpiFile *pFile = (IKpiFile*)id;
    UINT64 qwSize = pFile->GetSize();
    return qwSize;
}

static int wavpack_can_seek (void *id)
{
    return 1;
}
/*
static int wavpack_truncate_here(void *id)
{
    return 0;
}
static int wavpack_close(void *id)
{
    return 0;
}
*/
static WavpackStreamReader64 StreamReader = 
{
    wavpack_read_bytes, 
    NULL,//wavpack_write_bytes,
    wavpack_get_pos, 
    wavpack_set_pos_abs, 
    wavpack_set_pos_rel, 
    wavpack_push_back_byte, 
    wavpack_get_length, 
    wavpack_can_seek,
    NULL,//wavpack_truncate_here,
    NULL//wavpack_close
};
///////////////////////////////////////////////////////////////////////
KbWavPackDecoder::KbWavPackDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pContext = NULL;
    m_dwBlockAlign = 0;
    m_qwDecodeSample = 0;
    m_bDecodeDone = FALSE;
    m_pFile = NULL;
    m_pFileWvc = NULL;
    m_pTempBuf = NULL;
}
///////////////////////////////////////////////////////////////////////
KbWavPackDecoder::~KbWavPackDecoder(void)
{
    if(m_pContext){
        WavpackCloseFile(m_pContext);
    }
    if(m_pFile){
        m_pFile->Release();
    }
    if (m_pFileWvc) {
        m_pFileWvc->Release();
    }
    free(m_pTempBuf);
}
///////////////////////////////////////////////////////////////////////
DWORD __fastcall KbWavPackDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile, 
                                        IKpiFolder *pFolder)
{
    IKpiFile *pFileWvc = NULL;
    wchar_t szWvc[MAX_PATH];
    if(pFile->GetFileName(szWvc, sizeof(szWvc)) < sizeof(szWvc)){
        wchar_t *pszExt = wcsrchr(szWvc, L'.');
        if(pszExt){
            *pszExt = 0;
            wcsncat_s(szWvc, L".wvc", _TRUNCATE);
            pFolder->OpenFile(szWvc, &pFileWvc);
        }
    }
    m_pContext = WavpackOpenFileInputEx64(&StreamReader, pFile, pFileWvc, NULL, OPEN_WVC|OPEN_2CH_MAX, 0);
    if(!m_pContext){
        if(pFileWvc){
            pFileWvc->Release();
        }
        return FALSE;
    }
    m_MediaInfo.nBitsPerSample = WavpackGetBitsPerSample(m_pContext);
    m_MediaInfo.dwChannels = WavpackGetNumChannels(m_pContext);
    m_MediaInfo.dwSampleRate = WavpackGetSampleRate(m_pContext);
    m_bDecodeDone = FALSE;
    if(m_MediaInfo.nBitsPerSample == 32 && (WavpackGetMode (m_pContext) & MODE_FLOAT)){
        m_MediaInfo.nBitsPerSample = -32;
    }
    m_dwBlockAlign = (abs(m_MediaInfo.nBitsPerSample) / 8) * m_MediaInfo.dwChannels;
    m_MediaInfo.qwLength = kpi_SampleTo100ns(WavpackGetNumSamples64(m_pContext),
                                             m_MediaInfo.dwSampleRate);
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    m_pFile = pFile;
    pFile->AddRef();
    m_pFileWvc = pFileWvc;//AddRef は不要(OpenFile の時点で参照カウントを持っている)
    m_pTempBuf = (int32_t*)malloc(TEMP_BUFSIZE_SAMPLE * m_MediaInfo.dwChannels * sizeof(int32_t));
    return 1;
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbWavPackDecoder::Select(DWORD dwNumber,
                                      const KPI_MEDIAINFO **ppMediaInfo,
                                      IKpiTagInfo *pTagInfo,
                                      DWORD dwTagGetFlags)
{
    if (ppMediaInfo) {
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    GetTagInfo(pTagInfo, dwTagGetFlags);
    if (!ppMediaInfo) {
        return dwNumber;
    }
    *ppMediaInfo = &m_MediaInfo;
    m_MediaInfo.dwNumber = dwNumber;
    return dwNumber;
}
///////////////////////////////////////////////////////////////////////
static inline void __fastcall format_samples(int bps, BYTE *dst, int32_t *src, ULONG samcnt)
{
    DWORD temp;
    switch(bps){
        case 8:
            while(samcnt--){
                *dst++ = (BYTE)(*src++) + 128;
            }
            break;
        case 16:
            while(samcnt--) {
                *dst++ = (BYTE)(temp = *src++);
                *dst++ = (BYTE)(temp >> 8);
            }
            break;
        case 24:
            while(samcnt--) {
                *dst++ = (BYTE)(temp = *src++);
                *dst++ = (BYTE)(temp >> 8);
                *dst++ = (BYTE)(temp >> 16);
            }
            break;
        case 32:
        case -32:
            while(samcnt--) {
                *dst++ = (BYTE)(temp = *src++);
                *dst++ = (BYTE)(temp >> 8);
                *dst++ = (BYTE)(temp >> 16);
                *dst++ = (BYTE)(temp >> 24);
            }
            break;
    }
}
///////////////////////////////////////////////////////////////////////
DWORD WINAPI KbWavPackDecoder::Render(BYTE *pData, DWORD dwSizeSample)
{
    if(m_bDecodeDone){
        return 0;
    }
    DWORD dwRetSample = 0;
    while(dwRetSample < dwSizeSample){
        DWORD dwDecodeSample = dwSizeSample - dwRetSample;
        if(dwDecodeSample > TEMP_BUFSIZE_SAMPLE){
            dwDecodeSample = TEMP_BUFSIZE_SAMPLE;
        }
        DWORD dwRet = WavpackUnpackSamples(m_pContext,
                                           m_pTempBuf,
                                           dwDecodeSample);
        format_samples(m_MediaInfo.nBitsPerSample, 
                       pData + dwRetSample*m_dwBlockAlign,
                       m_pTempBuf, 
                       dwRet * m_MediaInfo.dwChannels);
        dwRetSample += dwRet;
        if(dwRet < dwDecodeSample){
            break;
        }
    }
    m_qwDecodeSample += dwRetSample;
    if(dwRetSample < dwSizeSample){
        m_bDecodeDone = TRUE;
    }
    return dwRetSample;
}
///////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbWavPackDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    const UINT64 qwTotalSample = WavpackGetNumSamples64(m_pContext);
    if(qwPosSample > qwTotalSample){
        qwPosSample = qwTotalSample;
    }
    if(!WavpackSeekSample64(m_pContext, qwPosSample)){
        WavpackSeekSample64(m_pContext, 0);
    }
    qwPosSample = WavpackGetSampleIndex64(m_pContext);
    m_qwDecodeSample = qwPosSample;
    m_bDecodeDone = FALSE;
    return qwPosSample;
}
///////////////////////////////////////////////////////////////////////
void __fastcall KbWavPackDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){
        return;
    }
    if(!(dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL)){
        return;
    }
    double dBitrate = WavpackGetAverageBitrate(m_pContext, 0);
    double dBitrateWvc = WavpackGetAverageBitrate(m_pContext, 1);
    wchar_t szBitrate[128];
    if(dBitrateWvc > dBitrate){
        dBitrateWvc /= 1000;
        dBitrateWvc += 0.5;
        dBitrate /= 1000;
        dBitrate += 0.5;
        _snwprintf_s(szBitrate, _TRUNCATE, L"%dkbps(%dkbps+%dkbps)", 
                    (int)dBitrateWvc, (int)dBitrate, (int)dBitrateWvc-(int)dBitrate);
    }
    else if(dBitrate > 0){
        dBitrate /= 1000;
        dBitrate += 0.5;
        _snwprintf_s(szBitrate, _TRUNCATE, L"%dkbps", (int)dBitrate);
    }
    else{
        szBitrate[0] = 0;
    }
    if(szBitrate[0]){
        pTagInfo->wSetValueW(L"bitrate", -1, szBitrate, -1);
    }
// MODE_WVC:  a .wvc file has been found and will be used for lossless
// MODE_LOSSLESS:  file is lossless (either pure or hybrid)
// MODE_HYBRID:  file is hybrid mode (either lossy or lossless)
// MODE_FLOAT:  audio data is 32-bit ieee floating point
// MODE_VALID_TAG:  file conatins a valid ID3v1 or APEv2 tag
// MODE_HIGH:  file was created in "high" mode (information only)
// MODE_FAST:  file was created in "fast" mode (information only)
// MODE_EXTRA:  file was created using "extra" mode (information only)
// MODE_APETAG:  file contains a valid APEv2 tag
// MODE_SFX:  file was created as a "self-extracting" executable
// MODE_VERY_HIGH:  file was created in the "very high" mode (or in
//                  the "high" mode prior to 4.4)
// MODE_MD5:  file contains an MD5 checksum
// MODE_XMODE:  level used for extra mode (1-6, 0=unknown)
// MODE_DNS:  dynamic noise shaping
    int nMode = WavpackGetMode(m_pContext);
    wchar_t szMode[1024];
    szMode[0] = 0;
    struct mode_table
    {
        int nFlag;
        const wchar_t *mode;
    }mode_tables[] = {
        {MODE_WVC, L"WVC"},
        {MODE_LOSSLESS, L"Lossless"},
        {MODE_HYBRID, L"Hybrid"},
        {MODE_FLOAT, L"Float"},
        {MODE_HIGH|MODE_VERY_HIGH, L"High"},
        {MODE_FAST, L"Fast"},
        {MODE_EXTRA, L"Extra"},
        //{MODE_APETAG, L"apetag"},
        {MODE_SFX, L"SFX"},
        //{MODE_VERY_HIGH, L"Very High"},
        {MODE_MD5, L"MD5"},
        //{MODE_XMODE, L"xmode"},
        {MODE_DNS, L"Dynamic Noise Shaping"},
        {0, NULL}
    };
    for(int i = 0; mode_tables[i].mode; i++){
        if(nMode & mode_tables[i].nFlag){
            if(szMode[0]){
                wcsncat_s(szMode, L" | ", _TRUNCATE);
            }
            if(mode_tables[i].nFlag == MODE_EXTRA){
                int level = (nMode & MODE_XMODE) >> 12;
                wchar_t szLevel[128];
                _snwprintf_s(szLevel, _TRUNCATE, L"(Level=%d)", level);
                wcsncat_s(szMode, mode_tables[i].mode, _TRUNCATE);
                wcsncat_s(szMode, szLevel, _TRUNCATE);
            }
            else if(mode_tables[i].nFlag == (MODE_HIGH|MODE_VERY_HIGH)){
                if(nMode & MODE_VERY_HIGH){
                    wcsncat_s(szMode, L"Very ", _TRUNCATE);
                }
                wcsncat_s(szMode, mode_tables[i].mode, _TRUNCATE);
            }
            else{
                wcsncat_s(szMode, mode_tables[i].mode, _TRUNCATE);
            }
        }
    }
    if(szMode[0]){
        pTagInfo->wSetValueW(L"WavPack_Mode", -1, szMode, -1);
    }
}
///////////////////////////////////////////////////////////////////////

