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

#ifdef _DEBUG
#include <crtdbg.h>
#endif

#include "kbvorbis_decoder.h"
#include "../../../kpi_common/base64.h"

//ov_callbacks::tell_func の戻り値の型を long から ogg_int64_t に修正
//ライブラリ更新時に vorbisfile.h を要確認

static void __fastcall SetPicture(const char* cszPicture, IKpiTagInfo *pTagInfo);

//IKpiFile 用
//////////////////////////////////////////////////////////////////////////////
static size_t read_func_stream(void *ptr, size_t size, size_t nmemb, void *datasource)
{
    IKpiFile *pStream = (IKpiFile*)datasource;
    DWORD dwRet = pStream->Read(ptr, size * nmemb);
    return dwRet / size;
}
//////////////////////////////////////////////////////////////////////////////
static int seek_func_stream(void *datasource, ogg_int64_t offset, int whence)
{ /* Note that we still need stdio.h even though we don't use stdio,
   * in order to get appropriate definitions for SEEK_SET, etc.
   */
    IKpiFile *pFile = (IKpiFile*)datasource;
    UINT64 ui64Ret = pFile->Seek(offset, whence);
    if(ui64Ret == KPI_FILE_EOF){
        return -1;
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////////
static ogg_int64_t tell_func_stream(void *datasource)
{
    IKpiFile *pFile = (IKpiFile*)datasource;
    return pFile->Seek(0, FILE_CURRENT);
}
//////////////////////////////////////////////////////////////////////////////
//KbVorbisDecoder
//////////////////////////////////////////////////////////////////////////////
KbVorbisDecoder::KbVorbisDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    ZeroMemory(&m_VorbisFile, sizeof(m_VorbisFile));
    m_current_section = -1;
    m_pFile = NULL;
}
//////////////////////////////////////////////////////////////////////////////
KbVorbisDecoder::~KbVorbisDecoder(void)
{
    ov_clear(&m_VorbisFile);
    m_current_section = -1;
    if(m_pFile){
        m_pFile->Release();
    }
}
//////////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbVorbisDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                       IKpiFile   *pFile,
                                       IKpiFolder *pFolder)
{
    vorbis_info *vi = NULL;
    ov_callbacks callbacks;
    callbacks.read_func = read_func_stream;
    callbacks.seek_func = seek_func_stream;
    callbacks.close_func = NULL;
    callbacks.tell_func = tell_func_stream;
    pFile->Seek(0, FILE_BEGIN);

    if (ov_open_callbacks(pFile, &m_VorbisFile, NULL, 0, callbacks) < 0) {
        return FALSE;
    }
    kpi_InitMediaInfo(&m_MediaInfo);
    vi = ov_info(&m_VorbisFile, -1);
    if(!vi || !vi->rate){
        return 0;
    }
    m_MediaInfo.dwSampleRate = vi->rate;
    m_MediaInfo.dwChannels = vi->channels;
    m_MediaInfo.nBitsPerSample = -64;//常に 64bit float で出力
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    m_MediaInfo.qwLength = kpi_SampleTo100ns(ov_pcm_total(&m_VorbisFile, -1), vi->rate);
    m_pFile = pFile;
    pFile->AddRef();
    return 1;
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbVorbisDecoder::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;
    return dwNumber;
}
//////////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbVorbisDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    ov_pcm_seek(&m_VorbisFile, qwPosSample);
    return ov_pcm_tell(&m_VorbisFile);
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbVorbisDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    const int nBlockAlign = sizeof(double) * m_MediaInfo.dwChannels;
    long ov_read_ret;
    DWORD dwRetSample = 0;
    while(dwRetSample < dwSizeSample){
        float **pcm = NULL;
        ov_read_ret = ov_read_float(&m_VorbisFile, &pcm,
                                    dwSizeSample - dwRetSample,
                                    &m_current_section);
        if(ov_read_ret > 0){
            int i, j;
            const int samples = ov_read_ret;
            const int channels = m_MediaInfo.dwChannels;
            double *buf = (double*)&pBuffer[dwRetSample * nBlockAlign];
            for(i = 0; i < channels; i++) {
                float  *src = pcm[i];
                double *dst = &buf[i];
                for(j = 0; j < samples;j++) {
                    *dst = src[j];
                    dst += channels;
                }
            }
            dwRetSample += ov_read_ret;
        }
        else{
            break;
        }
    }
    return dwRetSample;
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall KbVorbisDecoder::GetTagInfo(IKpiTagInfo *pInfo, DWORD dwTagGetFlags)
{
    if(!pInfo)return;
    static const char cszMetaDataBlockPic[] = "METADATA_BLOCK_PICTURE";
    vorbis_comment *comment = ov_comment(&m_VorbisFile, -1);
    if(!comment){
        return;
    }
    static const struct _S
    {
        const char    *cszField;
        const wchar_t *cszName;
        DWORD dwFlag;
    }_s[] = {
        {"title=",  L"title" , KPI_TAGGET_FLAG_TITLE},
        {"artist=", L"artist" , KPI_TAGGET_FLAG_ARTIST},
        {"album=",  L"album" , KPI_TAGGET_FLAG_ALBUM},
        {"albumartist=",  L"albumartist" , KPI_TAGGET_FLAG_ALBMART},
        {"copyright=",  L"copyright" , KPI_TAGGET_FLAG_COPYRIGHT},
        {"genre=",  L"genre" , KPI_TAGGET_FLAG_GENRE},
        {"comment=",  L"comment" , KPI_TAGGET_FLAG_COMMENT},
        {"description=",  L"description" , KPI_TAGGET_FLAG_COMMENT},
        {"tracknumber=",  L"tracknumber" , KPI_TAGGET_FLAG_TRACK},
        {"tracktotal=",  L"tracktotal" , KPI_TAGGET_FLAG_TRACK},
        {"discnumber=",  L"discnumber" , KPI_TAGGET_FLAG_TRACK},
        {"disctotal=",  L"disctotal" , KPI_TAGGET_FLAG_TRACK},
        {"replaygain_track_gain=",  L"replaygain_track_gain" , KPI_TAGGET_FLAG_RG},
        {"replaygain_track_peak=",  L"replaygain_track_peak" , KPI_TAGGET_FLAG_RG},
        {"replaygain_album_gain=",  L"replaygain_album_gain" , KPI_TAGGET_FLAG_RG},
        {"replaygain_album_peak=",  L"replaygain_album_peak" , KPI_TAGGET_FLAG_RG},
        {"METADATA_BLOCK_PICTURE=",  L"METADATA_BLOCK_PICTURE" , KPI_TAGGET_FLAG_PICTURE},
        {NULL, NULL, 0}
    };
    int i;
    for(i = 0; i < comment->comments; i++){
        bool bNotify = false;
        char *pszComment = comment->user_comments[i];
        const _S *s = _s;
        while(s->cszField){
            const int field_len = strlen(s->cszField);
            if(_strnicmp(pszComment, s->cszField, field_len) == 0 &&
               pszComment[field_len-1] == '='){
                if((s->dwFlag & dwTagGetFlags)){
                    const char *cszContentsU8 = pszComment + field_len;
                    if(s->dwFlag & KPI_TAGGET_FLAG_PICTURE){
                        SetPicture(cszContentsU8, pInfo);
                    }
                    else{
                        pInfo->wSetValueU8(s->cszName, -1, cszContentsU8, -1);
                    }
                    bNotify = true;
                }
                break;
            }
            s++;
        }
        if(bNotify){//通知済み
            continue;
        }
        if(!(dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL)){//一般的でないタグは取得不要
            continue;
        }
        int j = 0;
        while(pszComment[j] != '='){
            if(pszComment[j] == 0)break;
            j++;
        }
        if(pszComment[j] == 0){
            continue;
        }
        if(!pInfo){
            continue;
        }
        int nNameLen = j;
        int nValueLen = comment->comment_lengths[i] - j - 1;
        pInfo->aSetValueU8(pszComment, nNameLen,
                           &pszComment[j+1], nValueLen);
    }
    if(pInfo && (dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL)){
        wchar_t szBitrate[256];
        _snwprintf_s(szBitrate, _TRUNCATE, L"%dkbps(VBR)", ov_bitrate(&m_VorbisFile, -1)/1000);
        pInfo->wSetValueW(SZ_KMP_NAME_BITRATE_W, -1, szBitrate, -1);
        pInfo->wSetValueU8(L"vendor", -1, comment->vendor, -1);
    }
}
//////////////////////////////////////////////////////////////////////////////
static void __fastcall kbUtf8ToUtf16(const char *cszUtf8,
                              wchar_t **ppszUtf16,
                              int nSrcSize)
{// BOMなしUTF8 を UTF16 に変換
    // UTF8 => Unicode
    int nLenUtf16 = MultiByteToWideChar(CP_UTF8, 0, cszUtf8, nSrcSize, NULL, 0);
    wchar_t *pszUtf16;
    pszUtf16 = (wchar_t*)malloc((nLenUtf16 + 1)*sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, cszUtf8, nSrcSize, pszUtf16, nLenUtf16);
    pszUtf16[nLenUtf16] = 0;
    *ppszUtf16 = pszUtf16;
}
//////////////////////////////////////////////////////////////////////////////
static inline DWORD bswap32(DWORD x)
{
    return (x << 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x >> 24);
}
//////////////////////////////////////////////////////////////////////////////
static void __fastcall SetPicture(const char* cszPicture, IKpiTagInfo *pTagInfo)
{
//int __fastcall b64Decode(const wchar_t *cszSrc, BYTE **ppDest, DWORD *pdwSize)
    wchar_t *pszPictureW = NULL;
    BYTE *pData = NULL;
    DWORD dwSize = 0;
    kbUtf8ToUtf16(cszPicture, &pszPictureW, -1);
    b64Decode(pszPictureW, &pData, &dwSize);
    free(pszPictureW);
    if(!pData){
        return;
    }
    /*
    struct METDATA_BLOCK_PICTURE{
        DWORD dwType;       //4
        DWORD dwSizeMIME;   //4
        char  szMIME[];     //
        DWORD dwSizeDesc;   //4
        char  szDesc[];
        DWORD width;        //4
        DWORD height;       //4
        DWORD colordepth;   //4
        DWORD indexed_color;//4
        DWORD dwPictureSize;//4
        BYTE  PictureData[];
        //TotalSize=32バイト以上(szMIME/szDesc/PictureDataを除く)
    */
    if(dwSize < 32){
        free(pData);
        return;
    }
    BYTE *p = pData;
    DWORD dwType = bswap32(*(DWORD*)p);     p += sizeof(DWORD);
    DWORD dwSizeMIME = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    if(dwSize < 32 + dwSizeMIME){
        free(pData);
        return;
    }
    char  *pszMIME = (char*)p;     p += dwSizeMIME;
    DWORD dwSizeDesc = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    if(dwSize < 32 + dwSizeMIME + dwSizeDesc){
        free(pData);
        return;
    }
    char  *pszDesc = (char*)p; p += dwSizeDesc;
    DWORD width = bswap32(*(DWORD*)p);  p += sizeof(DWORD);
    DWORD height = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    DWORD color_depth = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    DWORD indexed_color = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    DWORD dwPictureSize = bswap32(*(DWORD*)p); p += sizeof(DWORD);
    if(p + dwPictureSize != pData + dwSize){
        free(pData);
        return;
    }
    BYTE *pPictureData = p;
    wchar_t *pszMIMEW;
    wchar_t *pszDescW;
    kbUtf8ToUtf16(pszMIME, &pszMIMEW, dwSizeMIME);//正確には pszMIME は Ascii だがどっちでも良い
    kbUtf8ToUtf16(pszDesc, &pszDescW, dwSizeDesc);
    pTagInfo->SetPicture(dwType, L"", pszMIMEW, pszDescW, width, height, pPictureData, dwPictureSize);
    free(pszDescW);
    free(pszMIMEW);
    free(pData);
}
