#include <windows.h>
#include <math.h>
#include <string.h>
#include <crtdbg.h>
#include <stdio.h>
#include <stdlib.h>

#include "kbmad_decoder.h"

const int KBMAD_BPS = (sizeof(double)*8);
//////////////////////////////////////////////////////////////////////////
KbMadDecoder::KbMadDecoder(void)
{
    m_pFile = NULL;
    m_dwInputBufLen = 0;
    m_bEof = 0;    
    m_qwEndSample = 0;
    m_dwOutputRemain = 0;
    m_dwStartSample = 0;
    m_qwDecodedSample = 0;
    m_dwSkipRemain = 0;
    m_dwOutputBufSizeSample = 0;
    m_dwBlockAlign = 0;

    ZeroMemory(&m_stream, sizeof(m_stream));
    ZeroMemory(&m_header, sizeof(m_header));
    ZeroMemory(&m_frame, sizeof(m_frame));
    ZeroMemory(&m_synth, sizeof(m_synth));
    kpi_InitMediaInfo(&m_MediaInfo);
}
//////////////////////////////////////////////////////////////////////////
KbMadDecoder::~KbMadDecoder(void)
{
    mad_header_finish(&m_header);
    mad_stream_finish(&m_stream);
    mad_frame_finish(&m_frame);
    mad_synth_finish(&m_synth);
    if(m_pFile)m_pFile->Release();
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbMadDecoder::Open(const KPI_MEDIAINFO *cpRequest, 
                                    IKpiFile   *pFile, 
                                    IKpiFolder *pFolder)
{
    if(!cpRequest){//タグ取得専用モード
        m_pFile = pFile;
        pFile->AddRef();
        return 1;
    }
    kpi_InitMediaInfo(&m_MediaInfo);
    if (!m_Mp3Info.GetMp3Info(pFile)) {
        return 0;
    }
    m_dwBlockAlign = (KBMAD_BPS / 8) * m_Mp3Info.GetChannels();
    m_dwStartSample = m_Mp3Info.GetStartSample();
    m_qwEndSample = m_Mp3Info.GetEndSample();
    m_MediaInfo.dwSampleRate = m_Mp3Info.GetSampleRate();
    m_MediaInfo.dwChannels = m_Mp3Info.GetChannels();
    m_MediaInfo.nBitsPerSample = -KBMAD_BPS;//float なので負の値
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.qwLength = m_Mp3Info.GetLength100ns();
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwNumber = 1;//常に曲番号=1
    m_MediaInfo.dwCount = 1; //常に曲数=1
    m_pFile = pFile;
    pFile->AddRef();
    return 1;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMadDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){//番号がおかしい
        return 0;
    }
    if(pTagInfo){
        while(1){//ID3v2=>APE=>RIFF=>Lyric3v2=>ID3v1 の順に取得
            if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_ID3V2, dwTagGetFlags) == 0){
                break;
            }
            if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_APE, dwTagGetFlags) == 0){
                break;
            }
            if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_RIFF, dwTagGetFlags) == 0){
                break;
            }
            if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_LYR3TAG, dwTagGetFlags) == 0){
                break;
            }
            if(pTagInfo->GetTagInfo(m_pFile, NULL, KPI_TAGTYPE_ID3V1, dwTagGetFlags) == 0){
                break;
            }
            break;
        }
        //pTagInfo->SetOverwrite(FALSE);//上書き禁止(デフォルトが禁止なので呼ばない)
        m_Mp3Info.GetTagInfo(pTagInfo, dwTagGetFlags);
    }
    if(!ppMediaInfo){
        return 1;
    }
    m_MediaInfo.dwNumber = dwNumber;
    *ppMediaInfo = &m_MediaInfo;
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbMadDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{//シーク（戻り値はシーク後の再生位置）
    m_dwInputBufLen = 0;
    m_dwOutputRemain = 0;
    m_qwDecodedSample = 0;
    m_dwSkipRemain = m_dwStartSample;
    m_dwOutputBufSizeSample = 0;
    m_bEof = FALSE;
    mad_header_finish(&m_header);
    mad_stream_finish(&m_stream);
    mad_frame_finish(&m_frame);
    mad_synth_finish(&m_synth);

    mad_stream_init(&m_stream);
    mad_header_init(&m_header);
    mad_frame_init(&m_frame);
    mad_synth_init(&m_synth);

    //N(=5)フレーム分だけ手前にシークする
    //先頭フレーム以外へのシークの直後はなぜか mad_frame_decode に失敗する
    //手前にシークして先頭の数フレーム(最大Nフレーム)はデコードを諦める
    const int N = 5;//N=3 でも問題なさそう
    DWORD dwFrameSample = m_Mp3Info.GetFrameSamples();
    if (qwPosSample < dwFrameSample * N) {
        return m_Mp3Info.Mp3SeekSample(0);//先頭にシークする分には問題なし
    }
    UINT64 qwRetSample = m_Mp3Info.Mp3SeekSample(qwPosSample - dwFrameSample * N);
    if (qwRetSample == 0) {
        return 0;
    }
    //シークの直後はなぜか mad_frame_decode に失敗する
    int nTryCount = 0;
    BOOL bEof = FALSE;
    m_dwInputBufLen = m_Mp3Info.ReadFrames(m_InputBuf, sizeof(m_InputBuf), &bEof);
    while (nTryCount++ < N) {
        mad_stream_buffer(&m_stream, m_InputBuf, m_dwInputBufLen);
        if (mad_frame_decode(&m_frame, &m_stream) == -1) {//失敗
            qwRetSample += dwFrameSample;
            memmove(m_InputBuf, m_stream.next_frame, &m_InputBuf[m_dwInputBufLen] - m_stream.next_frame);
            m_dwInputBufLen = &m_InputBuf[m_dwInputBufLen] - m_stream.next_frame;
            DWORD dwRead = m_Mp3Info.ReadFrames(m_InputBuf + m_dwInputBufLen, sizeof(m_InputBuf) - m_dwInputBufLen, &bEof);
            m_dwInputBufLen += dwRead;
            m_stream.error = MAD_ERROR_NONE;
        }
        else {//成功
            mad_synth_frame(&m_synth, &m_frame);
            int nch = m_synth.pcm.channels;
            int *ch1 = m_synth.pcm.samples[0];
            int *ch2 = m_synth.pcm.samples[1];
            if (nch == 1) {
                ch2 = NULL;
            }
            int pcm_length = m_synth.pcm.length;
            pack_pcm(m_OutputBuf,
                     pcm_length,
                     ch1,
                     ch2);
            m_dwOutputBufSizeSample = m_dwOutputRemain = pcm_length;
            break;
        }
    }
    m_qwDecodedSample = qwRetSample;
    return qwRetSample;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMadDecoder::Render(BYTE* out, DWORD dwSizeSample)
{
    const DWORD dwBlockAlign = m_dwBlockAlign;
    DWORD dwRetSample = 0;
    int err_count = 0;
    while (dwRetSample < dwSizeSample){
        if(m_dwSkipRemain > 0){
            if(m_dwOutputRemain >= m_dwSkipRemain){
                m_dwOutputRemain -= m_dwSkipRemain;
                m_qwDecodedSample += m_dwSkipRemain;
                m_dwSkipRemain = 0;
            }
            else{
                m_dwSkipRemain -= m_dwOutputRemain;
                m_qwDecodedSample += m_dwOutputRemain;
                m_dwOutputRemain = 0;
            }
        }
        if(m_dwOutputRemain){
            if(dwRetSample + m_dwOutputRemain > dwSizeSample){
                DWORD dwCopySizeSample = dwSizeSample - dwRetSample;
                memcpy(out + dwRetSample * dwBlockAlign,
                       m_OutputBuf + (m_dwOutputBufSizeSample - m_dwOutputRemain)*dwBlockAlign,
                       dwCopySizeSample * dwBlockAlign);
                m_dwOutputRemain -= dwCopySizeSample;
                dwRetSample += dwCopySizeSample;
                break;
            }
            memcpy(out + dwRetSample * dwBlockAlign,
                   m_OutputBuf + (m_dwOutputBufSizeSample - m_dwOutputRemain)*dwBlockAlign,
                   m_dwOutputRemain * dwBlockAlign);
            dwRetSample += m_dwOutputRemain;
            m_dwOutputRemain = 0;
        }
        if(m_dwInputBufLen == 0 || m_stream.error){
            if(m_dwInputBufLen){
                memmove(m_InputBuf, m_stream.next_frame, 
                        &m_InputBuf[sizeof(m_InputBuf)] - m_stream.next_frame);
                m_dwInputBufLen -= m_stream.next_frame - m_InputBuf;
            }
            DWORD dwRead = m_Mp3Info.ReadFrames(m_InputBuf + m_dwInputBufLen, 
                                                sizeof(m_InputBuf) - m_dwInputBufLen,
                                                &m_bEof);
            if(m_bEof){
                //次のフレームに何かしらのデータがないと最後のフレームをデコード出来ないようなので
                //最終フレームの後ろにゼロクリアしたデータを付加
                ZeroMemory(m_InputBuf + dwRead + m_dwInputBufLen, 
                           sizeof(m_Dummy) - m_dwInputBufLen - dwRead);
                m_dwInputBufLen = sizeof(m_Dummy);
            }
            else{
                m_dwInputBufLen += dwRead;
            }
            mad_stream_buffer(&m_stream, m_InputBuf, m_dwInputBufLen);
            m_stream.error = MAD_ERROR_NONE;
        }
        while (1) {
            if(mad_frame_decode(&m_frame, &m_stream) == -1) {
                if(m_bEof){
                   goto finish;
                }
                if(m_stream.error == MAD_ERROR_BUFLEN){
                    break;
                }
                if(!MAD_RECOVERABLE(m_stream.error)){
                    goto finish;
                }
                if(++err_count > 4){
                    goto finish;
                }
                int framesamples = m_Mp3Info.GetFrameSamples();
                ZeroMemory(m_OutputBuf, framesamples * dwBlockAlign);
                m_dwOutputBufSizeSample = m_dwOutputRemain = framesamples;
                break;
            }
            mad_synth_frame(&m_synth, &m_frame);
            int nch = m_synth.pcm.channels;
            int *ch1 = m_synth.pcm.samples[0];
            int *ch2 = nch == 1 ? NULL : m_synth.pcm.samples[1];
            int pcm_length = m_synth.pcm.length;
            pack_pcm(m_OutputBuf, pcm_length, ch1, ch2);
            m_dwOutputBufSizeSample = m_dwOutputRemain = pcm_length;
            break;
        }
    }
finish:
    if (m_qwDecodedSample + dwRetSample >= m_qwEndSample) {
        dwRetSample = m_qwEndSample - m_qwDecodedSample;
    }
    m_qwDecodedSample += dwRetSample;
    if (dwRetSample < dwSizeSample) {
#ifdef _DEBUG
        DWORD dw = dwRetSample;
#endif
#if KBMAD_BPS==32
            typedef float  kbmad_float;
#else
            typedef double kbmad_float;
#endif
            if (m_Mp3Info.GetEncPadding() < 0) {
                //末尾の無音を削除
                //今回の Render が全て無音で、1つ前の Render の最後にも無音が
                //含まれているような場合には未対応
                //（Render の dwSize 次第で演奏時間が微妙に変わる）
                int nch = m_Mp3Info.GetChannels();
                if (nch == 1) {
                    while (dwRetSample) {
                        kbmad_float *l = (kbmad_float*)(out + (dwRetSample - 1)*dwBlockAlign);
                        if (*l == 0.0) {
                            dwRetSample--;
                        }
                        else {
                            break;
                        }
                    }
                }
                else {
                    while (dwRetSample) {
                        kbmad_float *l = (kbmad_float*)(out + (dwRetSample - 1)*dwBlockAlign);
                        kbmad_float *r = l + 1;
                        if (*l == 0.0 && *r == 0.0) {
                            dwRetSample--;
                        }
                        else {
                            break;
                        }
                    }

                }
            }//
#ifdef _DEBUG
            DWORD dwDiscardSample = (dw - dwRetSample);
            int diff_sample = m_qwEndSample - m_qwDecodedSample;
            if (diff_sample != 0) {
                int dec_delay = m_Mp3Info.GetDecDelay();
                int enc_delay = m_Mp3Info.GetEncDelay();
                int enc_padding = m_Mp3Info.GetEncPadding();
                wchar_t szMes[512];
                _snwprintf_s(szMes, _TRUNCATE, L"ギャップレス再生に失敗? diff = %d samples\n", diff_sample);
                OutputDebugString(szMes);
                _snwprintf_s(szMes, _TRUNCATE, L"dec_delay = %d, enc_delay = %d, enc_padding = %d, discard = %d\n", dec_delay, enc_delay, enc_padding, dwDiscardSample);
                OutputDebugString(szMes);
            }
#endif
    }
    return dwRetSample;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbMadDecoder::pack_pcm(unsigned char *data,
                                       unsigned int nsamples,
                                       mad_fixed_t const *left,
                                       mad_fixed_t const *right)
{
    double *dst = (double*)data;
    if(right){//stereo
        while(nsamples--) {
            *dst++ = mad_f_todouble(*left++);
            *dst++ = mad_f_todouble(*right++);
        }
    }
    else{//mono
        while(nsamples--) {
            *dst++ = mad_f_todouble(*left++);
        }
    }
}
//////////////////////////////////////////////////////////////////////////
