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

#include "kbmp4aac_decoder.h"

//////////////////////////////////////////////////////////////////////////////
//mp4ff/metadata.c で実装
//処理速度の関係上内部データに直接アクセスしたかったので元ソース内に追加
extern "C" int32_t mp4ff_meta_find_by_name_added_by_kobarin(const mp4ff_t *f, 
                                                            const char *item, 
                                                            const char **value);

//////////////////////////////////////////////////////////////////////////////
//faad の aacDECdrop/decode.c から引用
int GetAACTrack(mp4ff_t *infile, uint8_t** ppBuf, uint32_t* pBufSize)
{
    /* find AAC track */
    int i, rc;
    int numTracks = mp4ff_total_tracks(infile);

    for (i = 0; i < numTracks; i++)
    {
        unsigned char *buff = NULL;
        unsigned int buff_size = 0;

        mp4ff_get_decoder_config(infile, i, &buff, &buff_size);

        if (buff)
        {
            mp4AudioSpecificConfig mp4ASC;
            rc = NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC);
            if (rc < 0){
                free(buff);
                continue;
            }
            *ppBuf = buff;
            *pBufSize = buff_size;
            return i;
        }
    }

    /* can't decode this */
    return -1;
}
//////////////////////////////////////////////////////////////////////////////
KbMp4AacDecoder::KbMp4AacDecoder(void)
    :KbMp4Decoder()
{
    m_hDecoder = NULL;
    m_pSample = NULL;
    m_objectTypeIndex = 0;
    m_dwEncDelay = 0;
    m_dwSkippedSample = 0;
    m_dwRemainSample = 0;
}
//////////////////////////////////////////////////////////////////////////////
KbMp4AacDecoder::~KbMp4AacDecoder(void)
{
    Close();
}
//////////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbMp4AacDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                       IKpiFile   *pFile, 
                                       IKpiFolder *pFolder)
{
    if(!cpRequest){
        m_callback.user_data = pFile;
        m_pMp4ff = mp4ff_open_read_metaonly(&m_callback);
        if(m_pMp4ff){
            m_MediaInfo.dwNumber = 1;
            m_MediaInfo.dwCount = 1;
            m_pFile = pFile;
            pFile->AddRef();
            return 1;
        }
        m_callback.user_data = NULL;
        return 0;
    }
    int track;
    unsigned long samplerate;
    unsigned char channels;
    mp4ff_t *infile;
    NeAACDecHandle hDecoder;

    uint8_t  *buffer;
    uint32_t buffer_size;
    m_callback.user_data = pFile;
    infile = mp4ff_open_read(&m_callback);
    if (!infile){
        m_callback.user_data = NULL;
        return 0;
    }
    if ((track = GetAACTrack(infile, &buffer, &buffer_size)) < 0){
        mp4ff_close(infile);
        m_callback.user_data = NULL;
        return 0;
    }

    hDecoder = NeAACDecOpen();

    NeAACDecConfigurationPtr pconfig = NeAACDecGetCurrentConfiguration(hDecoder);
    pconfig->outputFormat = FAAD_FMT_DOUBLE;//64bit float
    //pconfig->dontUpSampleImplicitSBR = 1;
    if(NeAACDecInit2(hDecoder, buffer, buffer_size, &samplerate, &channels) < 0){
       // If some error initializing occured, skip the file
        NeAACDecClose(hDecoder);
        mp4ff_close(infile);
        m_callback.user_data = NULL;
        if (buffer){
            free(buffer);
        }
        return 0;
    }
    mp4AudioSpecificConfig mp4ASC;
    NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC);
    if (buffer){
        free(buffer);
    }
    m_nNumSampleId = mp4ff_num_samples(infile, track);
    m_qwLastSample = mp4ff_get_track_duration(infile, 0);
    if(mp4ASC.forceUpSampling){
        m_qwLastSample *= 2;
    }
    //ギャップレス情報を取得
    const char *pszGapless;
    if(mp4ff_meta_find_by_name_added_by_kobarin(infile, "iTunSMPB", &pszGapless)){
        DWORD dwDummy = 0;      //????
        DWORD dwEncDelay = 0;   //encdelay
        DWORD dwEncPadding = 0; //encpadding
        union{
            UINT64 qwTotalSample;
            struct{
                DWORD dwTotalSampleLow; //totalsample
                DWORD dwTotalSampleHigh;//totalsample
            };
        };
        qwTotalSample = 0;
        //????     encdelay encpadding totalsample   以降????
        //00000000 00000A40 000001B4 000000000070200C 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
        //のように入っている。totalsamples はおそらく64bit
        sscanf_s(pszGapless, "%08x %08x %08x %08x%08x", &dwDummy, &dwEncDelay, &dwEncPadding, &dwTotalSampleHigh, &dwTotalSampleLow);
        if(dwEncDelay + qwTotalSample + dwEncPadding == m_qwLastSample){
            m_dwEncDelay = dwEncDelay;
            m_qwLastSample = qwTotalSample;
        }
        //free(pszGapless);
    }
    kpi_InitMediaInfo(&m_MediaInfo);
    m_MediaInfo.dwSampleRate = samplerate;
    m_MediaInfo.dwChannels = channels;
    m_MediaInfo.nBitsPerSample = -64;
    m_MediaInfo.qwLength = kpi_SampleTo100ns(m_qwLastSample, samplerate);
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    m_pMp4ff = infile;
    m_hDecoder = hDecoder;
    m_nTrack = track;
    pFile->AddRef();
    m_pFile = pFile;
    m_objectTypeIndex = mp4ASC.objectTypeIndex;
    return 1;
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall KbMp4AacDecoder::GetTagInfo(IKpiTagInfo *pTagInfo,
                                            DWORD dwTagGetFlags)
{
    KbMp4Decoder::GetTagInfo(pTagInfo, dwTagGetFlags);
    if(!pTagInfo)return;
    if(!(dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL)){
        return;
    }
    wchar_t szValue[128];
    wcsncpy_s(szValue, L"MPEG-4 AAC", _TRUNCATE);
    static const struct AAC_TYPE {
        int nTypeIndex;
        const wchar_t *cszTypeName;
    }aac_types[] = {
        { MAIN,      L"MAIN" },
        { LC,        L"LC" },
        { SSR,       L"SSR" },
        { LTP,       L"LTP" },
        { HE_AAC,    L"HE AAC" },
        { ER_LC,     L"ER LC" },
        { ER_LTP,    L"ER LTP" },
        { LD,        L"LD" },
        { DRM_ER_LC, L"DRM ER LC" },
        { -1, NULL },
    };
    int i;
    for (i = 0; aac_types[i].nTypeIndex > 0; i++) {
        if (aac_types[i].nTypeIndex == m_objectTypeIndex) {
            wcsncat_s(szValue, L" ", _TRUNCATE);
            wcsncat_s(szValue, aac_types[i].cszTypeName, _TRUNCATE);
            break;
        }
    }
    pTagInfo->wSetValueW(L"Codec", -1, szValue, -1);
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbMp4AacDecoder::Close(void)
{
    if(m_hDecoder){
        NeAACDecClose(m_hDecoder);
        m_hDecoder = NULL;
    }
    if(m_pMp4ff){
        mp4ff_close(m_pMp4ff);
        m_pMp4ff = NULL;
    }
    if(m_pFile){
        m_pFile->Release();
        m_pFile = NULL;
    }
    m_callback.user_data = NULL;
    m_pSample = NULL;
    m_qwLastSample = 0;
    m_qwCurrentSample = 0;
    m_dwRemainSample = 0;
    m_nNumSampleId = 0;
    m_nCurrentSampleId = 0;
    m_nTrack = 0;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbMp4AacDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    m_dwSkippedSample = 0;
    m_pSample = NULL;
    m_dwRemainSample = 0;
    if(m_qwCurrentSample > 0 && qwPosSample == 0){
        //ファイルを開き直す
        IKpiFile *pFile = m_pFile;
        KPI_MEDIAINFO mi = m_MediaInfo;
        pFile->AddRef();
        Close();
        pFile->Seek(0, FILE_BEGIN);
        Open(&mi, pFile, NULL);
        pFile->Release();
    }
    return KbMp4Decoder::Seek(qwPosSample, dwFlag);
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMp4AacDecoder::Render(BYTE *Buffer, DWORD dwSizeSample)
{
    const DWORD cdwBlockAlign = sizeof(double) * m_MediaInfo.dwChannels;
    DWORD dwRetSample = 0;
    DWORD dwCopySample = m_dwRemainSample;
    if(dwCopySample){
        if(dwCopySample > dwSizeSample){
            m_dwRemainSample -= dwSizeSample;
            dwCopySample = dwSizeSample;
        }
        else{
            m_dwRemainSample -= dwCopySample;
        }
        memcpy(Buffer, m_pSample, dwCopySample * cdwBlockAlign);
        m_pSample += (dwCopySample * cdwBlockAlign);
        dwRetSample += dwCopySample;
    }
    while(dwRetSample < dwSizeSample && m_nCurrentSampleId < m_nNumSampleId){
        BYTE *buffer = NULL;
        UINT buffer_size = 0;
        int rc = mp4ff_read_sample(m_pMp4ff, m_nTrack, m_nCurrentSampleId, &buffer, &buffer_size);
        if (rc == 0){
            break;
        }
        NeAACDecFrameInfo frameInfo;
        void *sample_buffer;
        sample_buffer = NeAACDecDecode(m_hDecoder, &frameInfo, buffer, buffer_size);
        if (buffer){
            free(buffer);
        }
        if (frameInfo.error > 0){
            break;
        }
     
        if(m_dwSkippedSample < m_dwEncDelay){
            DWORD dwSkipSample = m_dwEncDelay - m_dwSkippedSample;
            dwCopySample = frameInfo.samples / m_MediaInfo.dwChannels;
            if(dwCopySample < dwSkipSample){      
                dwSkipSample = dwCopySample;
            }
            dwCopySample -= dwSkipSample;
            m_dwSkippedSample += dwSkipSample;
            //この時点で dwRetSample == 0 でないとおかしい
            if(dwCopySample > dwSizeSample){
                dwCopySample = dwSizeSample;
            }
            memcpy(Buffer, ((BYTE*)sample_buffer) + dwSkipSample * cdwBlockAlign, 
                            dwCopySample * cdwBlockAlign);
            m_dwRemainSample = frameInfo.samples / m_MediaInfo.dwChannels - (dwSkipSample + dwCopySample);
            m_pSample = ((BYTE*)sample_buffer) + (dwSkipSample + dwCopySample) * cdwBlockAlign;
        }
        else{
            dwCopySample = frameInfo.samples / m_MediaInfo.dwChannels;
            if(dwRetSample + dwCopySample > dwSizeSample){
                dwCopySample = dwSizeSample - dwRetSample;
            }
            memcpy(Buffer + dwRetSample * cdwBlockAlign, sample_buffer, dwCopySample * cdwBlockAlign);
            m_dwRemainSample = frameInfo.samples / m_MediaInfo.dwChannels - dwCopySample;
            m_pSample = ((BYTE*)sample_buffer) + dwCopySample * cdwBlockAlign;
        }
        m_nCurrentSampleId++;
        dwRetSample += dwCopySample;
    }
    m_qwCurrentSample += dwRetSample;
    if(m_qwCurrentSample > m_qwLastSample){
        dwRetSample -= (m_qwCurrentSample - m_qwLastSample);
        m_qwCurrentSample = m_qwLastSample;
    }
    return dwRetSample;
}
//////////////////////////////////////////////////////////////////////////
