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

#include "kmp_pi.h"
#include "kbtak_decoder.h"
#include "tak_deco_lib_loader.h"


static BOOL __fastcall GetRIFFTagInfoFromBuffer(BYTE *pData, 
                                                DWORD dwHeadSize, DWORD dwTailSize, 
                                                IKpiTagInfo *pInfo, 
                                                DWORD dwTagGetFlags);

class KbKpiMemoryFile : public KbKpiFileImpl
{//メモリバッファを IKpiFile に対応させる
private:
    const BYTE *m_pMemData;
    size_t      m_stPointer;
    size_t      m_stSize;
    KbKpiMemoryFile(void);
public:
    KbKpiMemoryFile(const BYTE* pBuffer, UINT64 qwSize);
    virtual ~KbKpiMemoryFile(void);
    //IKpiFile 派生
    BOOL   WINAPI GetBuffer(const BYTE **ppBuffer, size_t *pstSize);
    DWORD  WINAPI Read(void *pBuffer, DWORD dwSize);
    UINT64 WINAPI Seek(INT64 i64Pos, DWORD dwOrigin);
    UINT64 WINAPI GetSize(void){return m_stSize;}
};
///////////////////////////////////////////////////////////////////////////////
KbKpiMemoryFile::KbKpiMemoryFile(const BYTE *pBuffer, UINT64 qwSize)
    :KbKpiFileImpl()
{
    m_pMemData = pBuffer;
    m_stSize = (size_t)qwSize;
    m_stPointer = 0;
}
///////////////////////////////////////////////////////////////////////////////
KbKpiMemoryFile::~KbKpiMemoryFile(void)
{
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbKpiMemoryFile::GetBuffer(const BYTE **ppBuffer, size_t *pstSize)
{
    if (m_pMemData) {
        *ppBuffer = m_pMemData;
        *pstSize = m_stSize;
        return TRUE;
    }
    else {
        *ppBuffer = NULL;
        *pstSize = 0;
        return FALSE;
    }
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbKpiMemoryFile::Read(void *pBuffer, DWORD dwSize)
{
    DWORD dwRead = 0;
    if (m_pMemData) {
        if (m_stPointer >= m_stSize) {
            ZeroMemory(pBuffer, dwSize);
            return 0;////
        }
        const size_t stCopyMax = m_stSize - m_stPointer;
        if (dwSize > stCopyMax) {
            dwSize = stCopyMax;
        }
        memcpy(pBuffer, &m_pMemData[m_stPointer], dwSize);
        dwRead = dwSize;
        m_stPointer += dwSize;
    }
    return dwRead;
}
///////////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbKpiMemoryFile::Seek(INT64 i64Pos, DWORD dwOrigin)
{
    if (m_pMemData) {
        INT64 nPointer = i64Pos;
        switch (dwOrigin) {
        case FILE_BEGIN:
            break;
        case FILE_CURRENT:
            nPointer += m_stPointer;
            break;
        case FILE_END:
            nPointer += m_stSize;
            break;
        }
        if (nPointer < 0) {
            nPointer = 0;
        }
        else if (nPointer > m_stSize) {
            nPointer = m_stSize;
        }
        m_stPointer = nPointer;
    }
    return m_stPointer;
}
///////////////////////////////////////////////////////////////////////////////

/*
typedef struct TtakStreamIoInterface {
    TtakBool (*CanRead)  (void * AUser);
    TtakBool (*CanWrite) (void * AUser);
    TtakBool (*CanSeek)  (void * AUser);

    TtakBool (*Read)     (void *      AUser,
                          void *      ABuf,
                          TtakInt32   ANum,
                          TtakInt32 * AReadNum );

    TtakBool (*Write)    (void *       AUser,
                          const void * ABuf,
                          TtakInt32    ANum);

    TtakBool (*Flush)    (void * AUser);

    TtakBool (*Truncate) (void * AUser);

    TtakBool (*Seek)     (void *    AUser,
                          TtakInt64 APos);

    TtakBool (*GetLength)(void *      AUser,
                          TtakInt64 * ALength);

} TtakStreamIoInterface;
*/
////////////////////////////////////////////////////////
static TtakBool kbtak_CanRead(void * AUser)
{
    return tak_True;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_CanWrite(void * AUser)
{
    return tak_False;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_CanSeek(void * AUser)
{
    return tak_True;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_Read(void *      AUser,
                           void *      ABuf,
                           TtakInt32   ANum,
                           TtakInt32 * AReadNum )
{
    IKpiFile *pFile = (IKpiFile*)AUser;
    *AReadNum = pFile->Read(ABuf, ANum);
    return tak_True;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_Write(void *       AUser,
                            const void * ABuf,
                            TtakInt32    ANum)
{
    return tak_False;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_Flush(void * AUser)
{
    return tak_False;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_Truncate(void * AUser)
{
    return tak_False;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_Seek(void *    AUser,
                           TtakInt64 APos)
{
    IKpiFile *pFile = (IKpiFile*)AUser;
    if(pFile->Seek(APos, FILE_BEGIN) == KPI_FILE_EOF){
        return tak_False;
    }
    return tak_True;
}
////////////////////////////////////////////////////////
static TtakBool kbtak_GetLength(void *      AUser,
                                TtakInt64 * ALength)
{
    IKpiFile *pFile = (IKpiFile*)AUser;
    *ALength = pFile->GetSize();
    if(*ALength == KPI_FILE_EOF){
        *ALength = 0;
        return tak_False;
    }
    return tak_True;
}
///////////////////////////////////////////////////////////////////////////
//KbTakDecoder
///////////////////////////////////////////////////////////////////////////
BOOL KbTakDecoder::Initialize(HINSTANCE hKpi, TtakInt32* pnVersion, TtakInt32* pnCompatibility)
{
    *pnVersion = *pnCompatibility = 0;
    
    if(!LoadTakLib(hKpi)){
        return FALSE;
    }
    takapi.tak_GetLibraryVersion(pnVersion, pnCompatibility);
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////
void KbTakDecoder::Uninitialize(void)
{
    UnloadTakLib();
}
///////////////////////////////////////////////////////////////////////////
KbTakDecoder::KbTakDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_TakDecoder = NULL;
    m_TakStreamIo.CanRead = kbtak_CanRead;
    m_TakStreamIo.CanWrite = kbtak_CanWrite;
    m_TakStreamIo.CanSeek = kbtak_CanSeek;
    m_TakStreamIo.Read = kbtak_Read,
    m_TakStreamIo.Write = kbtak_Write;
    m_TakStreamIo.Flush = kbtak_Flush;
    m_TakStreamIo.Truncate = kbtak_Truncate;
    m_TakStreamIo.Seek = kbtak_Seek;
    m_TakStreamIo.GetLength = kbtak_GetLength;
    m_pFile = NULL;
}
///////////////////////////////////////////////////////////////////////////
KbTakDecoder::~KbTakDecoder(void)
{
    if(m_TakDecoder){
        takapi.tak_SSD_Destroy(m_TakDecoder);
    }
    if(m_pFile){
        m_pFile->Release();
    }
}
///////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbTakDecoder::Open(const KPI_MEDIAINFO *cpRequesst, 
                                    IKpiFile *pFile, 
                                    IKpiFolder *pFolder)
{
    if(!takapi.tak_SSD_Create_FromStream){
        return 0;
    }
    TtakSSDOptions opt = {tak_Cpu_Any, 0};
    TtakSeekableStreamDecoder Decoder = takapi.tak_SSD_Create_FromStream(&m_TakStreamIo,
                                                                  pFile,
                                                                  &opt, 
                                                                  NULL, 
                                                                  NULL);
    if(!Decoder){
        return 0;
    }
    TtakSSDResult result = {0};
    Ttak_str_StreamInfo stream_info = {0};

    takapi.tak_SSD_GetStreamInfo(Decoder, &stream_info);
    
    m_MediaInfo.dwSampleRate = stream_info.Audio.SampleRate;
    m_MediaInfo.dwChannels = stream_info.Audio.ChannelNum;
    m_MediaInfo.nBitsPerSample = stream_info.Audio.SampleBits;
    m_MediaInfo.qwLength = kpi_SampleTo100ns(stream_info.Sizes.SampleNum, stream_info.Audio.SampleRate);
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    m_TakDecoder  = Decoder;
    m_pFile = pFile;
    pFile->AddRef();
    return 1;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbTakDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if (dwNumber > m_MediaInfo.dwCount) {
        return 0;
    }
    if (!ppMediaInfo) {//タグ取得のみ(タイトル/アーティストのみ)
        GetTagInfo(pTagInfo, dwTagGetFlags);
        return 1;
    }
    GetTagInfo(pTagInfo, dwTagGetFlags);
    *ppMediaInfo = &m_MediaInfo;
    return 1;
}
///////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbTakDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    takapi.tak_SSD_Seek(m_TakDecoder, qwPosSample);
    return qwPosSample;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbTakDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    TtakInt32 RetSample = 0;
    int tak_ret = takapi.tak_SSD_ReadAudio(m_TakDecoder, pBuffer, dwSizeSample, &RetSample);
    return RetSample;
}
///////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbTakDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){//NULL が渡されることがある
        return TRUE;
    }
    TtakSeekableStreamDecoder Decoder = m_TakDecoder;
    if(!Decoder){
        return FALSE;
    }
    //TAK ライブラリの API では埋め込み画像に対応出来ないため、本体のルーチンを呼び出す
    if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_APE, 
                            dwTagGetFlags) != 0){//ApeTag を含まない
        Ttak_str_SimpleWaveDataHeader waveHeader = {0};
        takapi.tak_SSD_GetSimpleWaveDataDesc(Decoder, &waveHeader);
        //waveHeader.HeadSize ; //LIST チャンクの始まりの位置
        //waveHeader.TailSize ; //LIST チャンクのサイズ
        int nSize = waveHeader.TailSize + waveHeader.HeadSize;
        if(nSize > 0){
            BYTE *pData = (BYTE*)malloc(nSize);
            if(takapi.tak_SSD_ReadSimpleWaveData(Decoder, pData, nSize) == 0){
                GetRIFFTagInfoFromBuffer(pData, //呼び出し先で内容を書き換える
                                         waveHeader.HeadSize, 
                                         waveHeader.TailSize, 
                                         pTagInfo, dwTagGetFlags);
            }        
            free(pData);
        }
    }
    if(dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL){
        char szValue[256];
        Ttak_str_StreamInfo stream_info;
        Ttak_str_MetaEncoderInfo meta_encoder_info;
        if(takapi.tak_SSD_GetEncoderInfo(Decoder, &meta_encoder_info) == 0){
            int major =  (meta_encoder_info.Version >> 16);
            int minor =  (meta_encoder_info.Version >> 8) & 0xFF;
            int build =  (meta_encoder_info.Version & 0xFF);
            int preset = meta_encoder_info.Preset;
            int evaluation;
            switch(meta_encoder_info.Evaluation){
                case tak_PresetEval_Extra: evaluation = 'e';break;
                case tak_PresetEval_Max:   evaluation = 'm';break;
                default: evaluation = 0;break;
            }
            _snprintf_s(szValue, _TRUNCATE, "V %d.%d.%d, -p%d%c", major, minor, build, preset, evaluation);
            pTagInfo->wSetValueA(L"Encoder", -1, szValue, -1);
        }
        if(takapi.tak_SSD_GetStreamInfo(Decoder, &stream_info) == 0){
            if(takapi.tak_GetCodecName(stream_info.Encoder.Codec, szValue, sizeof(szValue)) == 0){
                pTagInfo->wSetValueA(L"Codec", -1, szValue, -1);
            }
        }
        //Ttak_str_MD5 szMD5 = {0};
        //if(takapi.tak_SSD_GetMD5(Decoder, szMD5)==0){
        //    pTagInfo->wSetValueA(L"MD5", -1, szMD5, -1);
        //}
    }
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////
BOOL __fastcall GetRIFFTagInfoFromBuffer(BYTE *pData, 
                                         DWORD dwHeadSize, DWORD dwTailSize,
                                         IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    struct RIFF_HEADER {
        DWORD dwFourCC;
        DWORD dwChunkSize;
        DWORD dwType;
    };
    //pData は "RIFF" で始まっているが、そのまま RIFF と見なして解析すると
    //"data" チャンクのサイズがおかしいため正しく読み取れない。
    //"LIST" チャンクの直前に "RIFF" を配置させることで対処する
    //"LIST" チャンクの始まりの位置は pData + dwHeadSize
    //"LIST" チャンクの終わりの位置は pData + dwHeadSize + dwTailSize
    if(dwHeadSize < sizeof(RIFF_HEADER)){//"LIST" チャンクの始まりの位置がおかしい
        return TRUE;
    }
    BYTE* pListChunk = pData + dwHeadSize;
    RIFF_HEADER *pRiff = (RIFF_HEADER*)(pListChunk - sizeof(RIFF_HEADER));
    pRiff->dwFourCC = *(DWORD*)"RIFF";
    pRiff->dwChunkSize = dwTailSize + 4;
    pRiff->dwType = *(DWORD*)"WAVE";
    KbKpiMemoryFile mem((BYTE*)pRiff, dwTailSize + sizeof(RIFF_HEADER));
    pTagInfo->GetTagInfo(&mem, NULL, KPI_TAGTYPE_RIFF, dwTagGetFlags);
    return true;
}
///////////////////////////////////////////////////////////////////////////
