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

#include "pxtone/pxtnService.h"
#include "pxtone/pxtnError.h"

typedef bool (*pxtnIO_r)(void* user, void* p_dst, int32_t size, int32_t num);
typedef bool (*pxtnIO_w)(void* user, const void* p_dst, int32_t size, int32_t num);
typedef bool (*pxtnIO_seek)(void* user, int   mode, int32_t size);
typedef bool (*pxtnIO_pos)(void* user, int32_t* p_pos);

static bool ikpifile_r(void *user, void* p_dst, int32_t size, int32_t num)
{
    IKpiFile *pFile = (IKpiFile*)user;
    DWORD dwRead = pFile->Read(p_dst, size*num);
    if(dwRead < size*num){
        return false;
    }
    return true;
}
static bool ikpifile_w(void* user, const void* p_dst, int32_t size, int32_t num)
{
    return false;
}
static bool ikpifile_seek(void* user, int   mode, int32_t pos)
{
    IKpiFile* pFile = (IKpiFile*)user;
    bool ret = pFile->Seek(pos, mode) != KPI_FILE_EOF;
    if(!ret){
        int t = 0;
    }
    return ret;
}
static bool ikpifile_pos(void* user, int32_t* p_pos)
{
    IKpiFile* pFile = (IKpiFile*)user;
    UINT64 pos = pFile->Seek(0, FILE_CURRENT);
    if(pos != KPI_FILE_EOF){
        *p_pos = pos;
        return true;
    }
    else{
        *p_pos = 0;
        return false;
    }
}
//////////////////////////////////////////////////////////////////////////
KbPxtoneDecoder::KbPxtoneDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pPxtn = NULL;
    m_pFile = NULL;
//    m_pConfig = pConfig; pConfig->AddRef();
    m_qwCurSample = 0;
    m_qwEndSample = 0;
    m_bReady = FALSE;
    kbpxtone_GetSettings(pConfig, &m_Settings);
}
//////////////////////////////////////////////////////////////////////////
KbPxtoneDecoder::~KbPxtoneDecoder(void)
{
    delete m_pPxtn;
    if(m_pFile){
        m_pFile->Release();
    }
//    m_pConfig->Release();
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbPxtoneDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile,
                                        IKpiFolder *pFolder)
{
    DWORD dwSampleRate = cpRequest ? cpRequest->dwSampleRate : 0;
    if (m_Settings.nSampleRate > 0) {
        if (dwSampleRate == 0) {
            dwSampleRate = m_Settings.nSampleRate;
        }
        if(dwSampleRate > 192000){
            dwSampleRate = 192000;
        }
        else if(dwSampleRate < 22050){
            dwSampleRate = 22050;
        }
    }
    else{
        dwSampleRate = (DWORD)(-m_Settings.nSampleRate);
    }
    if(dwSampleRate == 0){//ない筈だが念のため
        dwSampleRate = 44100;
    }
    pxtnService* pxtn = new pxtnService(ikpifile_r, ikpifile_w, ikpifile_seek, ikpifile_pos);
    do{
        pxtnERR pxtn_err = pxtn->init();
        if (pxtn_err != pxtnOK){
            break;
        }
        if (!pxtn->set_destination_quality(2, dwSampleRate)){
            break;
        }
        //pxtnDescriptor desc;
        //desc.set_memory_r((void*)buf, size);
        //desc.set_file_r(pFile);
        pxtn_err = pxtn->read(pFile); 
        if (pxtn_err != pxtnOK){
            break;
        }
        //pxtn_err = pxtn->tones_ready(); //処理時間がかかる
        //cpRequest==NULLのときはタグ取得だけなので呼ばないようにする
        //cpRequest!=NULLでも演奏リスト上での曲長計算かもしれないので
        //再生が必要になるまで(Seekが呼ばれるまで)呼ばない
        m_MediaInfo.dwSampleRate = dwSampleRate;
        m_MediaInfo.nBitsPerSample = -64;
        m_MediaInfo.dwChannels = 2;

        int32_t meas_end = pxtn->master->get_play_meas();
        int32_t meas_repeat  = pxtn->master->get_repeat_meas();
        int32_t meas_last = pxtn->master->get_last_meas();
        //int32_t meas_num = pxtn->master->get_meas_num();

        int32_t meas_intro = meas_repeat;//meas_end まで再生したら meas_repeat に戻るので meas_repeat まではイントロ
        int32_t meas_loop = meas_end - meas_repeat;//meas_repeat～meas_endまでが2ループ目以降の1ループの時間

        //int32_t bt_clock;// = pxtn->master->get_beat_clock();
        int32_t bt_num = pxtn->master->get_beat_num();
        float   bt_tempo   = pxtn->master->get_beat_tempo();
        //int32_t meas_num;
        //pxtn->master->Get(&bt_num, &bt_tempo, &bt_clock, &meas_num);
        //int smp_repeat = pxtnService_moo_CalcSampleNum(meas_repeat, bt_num, dwSampleRate, bt_tempo);
        //int smp_last = pxtnService_moo_CalcSampleNum(meas_last, bt_num, dwSampleRate, bt_tempo);
        int smp_end = pxtnService_moo_CalcSampleNum(meas_end, bt_num, dwSampleRate, bt_tempo);
        //int smp_num = pxtnService_moo_CalcSampleNum(meas_num, bt_num, dwSampleRate, bt_tempo);
        int smp_loop = pxtnService_moo_CalcSampleNum(meas_loop, bt_num, dwSampleRate, bt_tempo);
        //ループの判定法がイマイチよく分からない
        //pxtnService::Moo(_moo_PXTONE_SAMPLE) の実装を見ると、ループ未指定時は
        //meas_end まで再生したら終了し、ループ指定時は meas_end まで再生したら 
        //meas_repeat に戻って再生し続ける
        //
        //実際のデータは
        //meas_last が 0 だとループ曲っぽいのが多いが、その中でも meas_repeat が 
        //0 のものはループっぽくないものがある
        //
        //meas_last が 0 でない場合はループ曲っぽくない(meas_end で演奏が終わる)のが多いが
        //その中でも meas_repeat が 0 でない曲の中にはループっぽいのもあるし、meas_repeat
        //～meas_end までの区間を再生しても音が鳴らないものも多い
        //(イントロに対して meas_repeat～meas_end までの演奏時間が極端に短いものとか)
        //
        m_MediaInfo.qwLength = kpi_SampleTo100ns(smp_end, dwSampleRate);
        m_qwEndSample = smp_end;

        //ループ曲かどうかの確実な判定方法が分からない
        if(m_Settings.nLoop == 0){//ループと見なさない
            m_MediaInfo.qwLoop = 0;
        }
        else if(m_Settings.nLoop == 1){//ループと見なす
            if(meas_loop <= 1){//ループ時間が1小節以内
                m_MediaInfo.qwLoop = 0;
            }
            else{
                m_MediaInfo.qwLoop = kpi_SampleTo100ns(smp_loop, dwSampleRate);
            }
        }
        else if(meas_intro){//イントロがある
            if ((meas_loop <= 1) || //ループ時間が1小節以内
                (meas_intro / meas_loop > m_Settings.dwLoopRatio)) {//イントロ時間に対してループ時間が短い
                m_MediaInfo.qwLoop = 0;
            }
            else {
                m_MediaInfo.qwLoop = kpi_SampleTo100ns(smp_loop, dwSampleRate);
            }
        }
        else{//イントロがない
            if (m_Settings.bIntroLoop) {
                m_MediaInfo.qwLoop = kpi_SampleTo100ns(smp_loop, dwSampleRate);
            }
            else{
                m_MediaInfo.qwLoop = 0;
            }
        }
        if(m_MediaInfo.qwLoop != 0){//ループ曲
            //m_MediaInfo.qwFadeOut = -1;//フェード時間は本体に従う
            if(m_Settings.nFadeOutMS < 0){
                m_MediaInfo.qwFadeOut = -1;
            }
            else{
                m_MediaInfo.qwFadeOut = (UINT64)m_Settings.nFadeOutMS * 10000;
            }
            m_MediaInfo.dwLoopCount = m_Settings.dwLoopCount;
        }
        else{
            m_MediaInfo.qwFadeOut = 0;
            m_MediaInfo.dwLoopCount = 0;
        }
        m_MediaInfo.dwNumber = 1;
        m_MediaInfo.dwCount = 1;
        m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
        m_pPxtn = pxtn;
        m_pFile = pFile;
        pFile->AddRef();
        return m_MediaInfo.dwCount;
    }while(0);
    delete pxtn;
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbPxtoneDecoder::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;
    if(pTagInfo){
        GetTagInfo(pTagInfo, dwTagGetFlags);
    }
    if (!ppMediaInfo) {
    //タイトル/アーティスト取得のみ
        return dwNumber;
    }
    *ppMediaInfo = &m_MediaInfo;
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbPxtoneDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(!m_bReady){
        if(m_pPxtn->tones_ready() != pxtnOK){
            return 0;
        }
        m_bReady = TRUE;
    }
    pxtnVOMITPREPARATION prep = {0};
    UINT64 qwOffsetSample = 0;
    //2ループ目以降の位置へのシークに対応
    if(m_MediaInfo.qwLoop > 0){
        UINT64 qwLoopSample = kpi_100nsToSample(m_MediaInfo.qwLoop, m_MediaInfo.dwSampleRate);
        while(qwPosSample > m_qwEndSample){
            qwPosSample -= qwLoopSample;
            qwOffsetSample += qwLoopSample;
        }
        prep.flags = pxtnVOMITPREPFLAG_loop;
    }
    prep.start_pos_sample = qwPosSample;
    prep.master_volume = m_Settings.dVolume;
    if(m_pPxtn->moo_preparation(&prep)){
        m_qwCurSample = m_pPxtn->moo_get_sampling_offset();
        m_qwCurSample += qwOffsetSample;
    }
    else{
        m_qwCurSample = 0;
    }
    return m_qwCurSample;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbPxtoneDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    if(!m_bReady){
        return 0;
    }
    DWORD dwBlockAlign = 16;//sizeof(double)*2
    if(m_pPxtn->Moo(pBuffer, dwSizeSample*dwBlockAlign)){
        if(m_MediaInfo.qwLoop == 0){
            if(m_qwCurSample + dwSizeSample > m_qwEndSample){
                dwSizeSample = m_qwEndSample - m_qwCurSample;
            }
        }
        m_qwCurSample += dwSizeSample;
        return dwSizeSample;
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbPxtoneDecoder::UpdateConfig(void *pvReserved)
{//Render や Seek 等とは排他的に呼ばれることが保証されている
 //リアルタイムに反映させようと思ったが本体の先読みバッファのサイズに
 //より反映されるまで時間がかかるのでやめた(開き直してもらった方が早い)
//    kbpxtone_GetSettings(m_pConfig, &m_Settings);
//    m_pPxtn->moo_set_master_volume(m_Settings.dVolume);
    return 0;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbPxtoneDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){
        return;
    }
    int32_t buf_size = 0;
    const char* comment;
    if (dwTagGetFlags & KPI_TAGGET_FLAG_TITLE) {
        comment = m_pPxtn->text->get_name_buf(&buf_size);
        if(comment && buf_size){
            pTagInfo->wSetValueA(L"title", -1, comment, buf_size);
        }
    }
    if (dwTagGetFlags & KPI_TAGGET_FLAG_COMMENT) {
        comment = m_pPxtn->text->get_comment_buf(&buf_size);
        if(comment && buf_size){
            pTagInfo->wSetValueA(L"comment", -1, comment, buf_size);
        }
    }
    if (dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL){
        int32_t bt_clock;// = m_pPxtn->master->get_beat_clock();
        int32_t bt_num;// = m_pPxtn->master->get_beat_num();
        float   bt_tempo;// = m_pPxtn->master->get_beat_tempo();
        int32_t meas_num;// = m_pPxtn->master->get_meas_num();
        m_pPxtn->master->Get(&bt_num, &bt_tempo, &bt_clock, &meas_num);
        int32_t meas_end = m_pPxtn->master->get_play_meas();
        int32_t meas_repeat = m_pPxtn->master->get_repeat_meas();
        int32_t meas_last = m_pPxtn->master->get_last_meas();
        int32_t meas_loop = meas_end - meas_repeat;

        wchar_t sz[256];
        _snwprintf_s(sz, _TRUNCATE, 
            L"BEAT=%d\r\n"
            L"TEMPO=%f\r\n"
            L"CLOCK=%d\r\n"
            L"MEAS_NUM=%d\r\n"
            L"MEAS_LAST=%d\r\n"
            L"MEAS_END=%d\r\n"
            L"MEAS_REPEAT=%d\r\n"
            L"MEAS_LOOP=%d(=MEAS_END-MEAS_REPEAT)",
            bt_num, bt_tempo, bt_clock, meas_num,
            meas_last, meas_end, meas_repeat, meas_loop);
        pTagInfo->wSetValueW(L"INFO", -1, sz, -1);
    }
}
//////////////////////////////////////////////////////////////////////////
