#include "kbdshow_decoder.h"
#include <uuids.h>
#include <mmreg.h>

#include "baseclasses/streams.h"
#include "dump/dump.h"

#ifdef _DEBUG
HRESULT AddToRot(IUnknown* pUnkGraph, DWORD* pdwRegister)
{
    IMoniker* pMoniker;
    IRunningObjectTable* pROT;
    if (FAILED(GetRunningObjectTable(0, &pROT))) {
        return E_FAIL;
    }
    WCHAR wsz[256];
    wsprintfW(wsz, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId());
    HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);
    if (SUCCEEDED(hr)) {
        hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
            pMoniker, pdwRegister);
        pMoniker->Release();
    }
    pROT->Release();
    return hr;
}
void RemoveFromRot(DWORD pdwRegister)
{
    IRunningObjectTable* pROT;
    if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) {
        pROT->Revoke(pdwRegister);
        pROT->Release();
    }
}
#endif
static void _FreeMediaType(AM_MEDIA_TYPE& mt)
{
    if (mt.cbFormat != 0)
    {
        CoTaskMemFree((PVOID)mt.pbFormat);
        mt.cbFormat = 0;
        mt.pbFormat = NULL;
    }
    if (mt.pUnk != NULL)
    {
        // pUnk should not be used.
        mt.pUnk->Release();
        mt.pUnk = NULL;
    }
}

// Delete a media type structure that was allocated on the heap.
static void _DeleteMediaType(AM_MEDIA_TYPE* pmt)
{
    if (pmt != NULL)
    {
        _FreeMediaType(*pmt);
        CoTaskMemFree(pmt);
    }
}
/////////////////////////////////////////////////////////////////////////
KbDShowDecoder::KbDShowDecoder(void)
{
    m_dwBlockAlign = 0;
    m_pAMMultiMediaStream = NULL;
    m_pAudioStreamSample = NULL;
    m_pAudioData = NULL;
    m_pFile = NULL;
    m_State = STREAMSTATE_STOP;
    m_szFileName[0] = 0;
    kpi_InitMediaInfo(&m_MediaInfo);
}
/////////////////////////////////////////////////////////////////////////
KbDShowDecoder::~KbDShowDecoder(void)
{
    Close();
}
/////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbDShowDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                     IKpiConfig *pConfig,
                                     IKpiFile   *pFile, 
                                     IKpiFolder *pFolder)
{
    const wchar_t *cszFileName;
    if(!pFile->GetRealFileW(&cszFileName)){
        return 0;
    }
    m_pFile = pFile;
    pFile->AddRef();
    wcsncpy_s(m_szFileName, cszFileName, _TRUNCATE);
    DWORD dwThreadId;
    HANDLE hThread = CreateThread(NULL, 0, ThreadProc, this, 0, &dwThreadId);
    if(hThread){
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
    }
    return m_MediaInfo.dwCount;
}
/////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbDShowDecoder::Select(DWORD dwNumber, 
                                     const KPI_MEDIAINFO **ppMediaInfo,
                                     IKpiTagInfo *pTagInfo,
                                     DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(!ppMediaInfo){
        return 1;
    }
    if(m_MediaInfo.dwSampleRate){
        m_MediaInfo.dwNumber = 1;
        *ppMediaInfo = &m_MediaInfo;
        return 1;
    }
    return 0;
}
/////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbDShowDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    DWORD dwBlockAlign = m_dwBlockAlign;
    DWORD dwSize = dwSizeSample * dwBlockAlign;
    if (m_State == STREAMSTATE_STOP) {
        m_pAMMultiMediaStream->SetState(STREAMSTATE_RUN);
        m_State = STREAMSTATE_RUN;
    }
    m_pAudioData->SetBuffer(dwSize, pBuffer, 0);
    HRESULT hr = m_pAudioStreamSample->Update(0, NULL, NULL, 0);
    if (FAILED(hr)) {
        return 0;
    }
    DWORD dwLength = 0;
    m_pAudioData->GetInfo(NULL, NULL, &dwLength);
    return dwLength / dwBlockAlign;
}
/////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbDShowDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    STREAM_TIME Pos = kpi_SampleTo100ns(qwPosSample, m_MediaInfo.dwSampleRate);
    STREAM_TIME NewPos = 0;
    HRESULT hr;
    m_pAMMultiMediaStream->SetState(STREAMSTATE_STOP);
    hr = m_pAMMultiMediaStream->Seek(Pos);
    if (hr != S_OK) {
        m_pAMMultiMediaStream->Seek(0);
    }
    m_pAMMultiMediaStream->GetTime(&NewPos);
    m_State = STREAMSTATE_STOP;
    UINT64 qwNewPos = kpi_100nsToSample(NewPos, m_MediaInfo.dwSampleRate);
    return qwNewPos;
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoder::Close(void)
{//Open と Close を呼ぶスレッドが異なると、m_pAMMultiMediaStream->Release() 時にフリーズする
 //正確には、m_pAMMultiMediaStream を作成するスレッドと破棄するスレッドが異なるとフリーズする。
 //m_pAMMultiMediaStream を破棄するとき、m_pAMMultiMediaStream を作成するスレッドが終了していればフリーズしない。
    if(m_pAudioStreamSample){
        m_pAudioStreamSample->Release();
        m_pAudioStreamSample = NULL;
    }
    if(m_pAudioData){
        m_pAudioData->Release();
        m_pAudioData = NULL;
    }
    if (m_pAMMultiMediaStream) {//m_pAMMultiMediaStream を作成するスレッドと破棄する
        m_pAMMultiMediaStream->Release();//スレッドが異なるとここでフリーズする。
        m_pAMMultiMediaStream = NULL;
    }
    if(m_pFile){
        m_pFile->Release();
        m_pFile = NULL;
    }
}
/////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbDShowDecoder::ThreadProc(void *pvParam)
{
    ((KbDShowDecoder*)pvParam)->ThreadProc();
    return 0;
}
/////////////////////////////////////////////////////////////////////////
void  __fastcall KbDShowDecoder::ThreadProc(void)
{
    //LAV Filter 使用時、mp3 / ogg / opus / 24bit aiff/ 24bit flac / 32bit float wavpack 等で wBitsPerSample 
    //の値が正しくないのを確認。爆音ノイズが再生される。
    //ビット数が 24 以上だとダメのようだが Monkeys Audio 等、24bit で LAV Filter 使用しても大丈夫な形式はある
    // 
    // mp3/ogg/opus 等不可逆圧縮系(mpc は問題なし) => 16bit (無視して 32bit float にすると問題なく再生出来る)
    // m4a(中身が aac)(alac のときは問題なし) => 16bit (無視して 32bit float にすると問題なく再生出来る)
    // 24bit aiff => 16bit (無視して 24bit にすると問題なし)
    // 96kHz 2ch 24bit flac => 11025Hz, 1ch, 16bit (制御出来なくなって勝手に再生が始まってしまう)
    // 96kHz 2ch 24bit wavpack => ファイルを開けない(WMP でも再生不可)
    // 32bit float wavpack => 16bit (無視して 32bit float にすると問題なし)(WMP では問題なし)
    //Media Foundation を使用した場合、mp3 / m4a(中身がaac) は問題なく再生出来るようになるが
    //m4a(中身が alac) が再生出来ない等、再生可能な形式が DirectShow フィルタと関係なくなる(?)
    IAMMultiMediaStream *pAMMultiMediaStream = NULL;
    IAudioStreamSample *pAudioStreamSample = NULL;
    IAudioData *pAudioData = NULL;
    //WAVEFORMATEX wfx = {0};      //wfx.cbSize = 0 にしても動作は変わりなし
    WAVEFORMATEXTENSIBLE wfx = {0};//wfx.Format.cbSize = 22 にしても float のときに WAVE_FORMAT_IEEE_FLOAT にはならない
    //wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);//=22
    //32bit float でも wfx.Format.wFormatTag == WAVE_FORMAT_IEEE_FLOA になることはない
    wfx.Format.cbSize = 0;
    int nSampleRate = 0;
    int nChannels = 0;
    int nBitsPerSample = 0;

    CoInitialize(NULL);
    //CoInitializeEx(NULL, COINIT_MULTITHREADED);
	CoCreateInstance(CLSID_AMMultiMediaStream, 
                     NULL, 
                     CLSCTX_INPROC_SERVER,
                     IID_IAMMultiMediaStream, 
                     (void **)&pAMMultiMediaStream);
    if(!pAMMultiMediaStream){
        goto FAIL;
    }

    pAMMultiMediaStream->Initialize(STREAMTYPE_READ, AMMSF_NOGRAPHTHREAD, NULL);
    pAMMultiMediaStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, 0, NULL);
    IGraphBuilder* pGraphBuilder = NULL;
    pAMMultiMediaStream->GetFilterGraph(&pGraphBuilder);
/*
    if(!SUCCEEDED(pAMMultiMediaStream->OpenFile(m_szFileName, 
                                        //AMMSF_NOCLOCK | AMMSF_RENDERTOEXISTING
                                        //AMMSF_RUN
                                        AMMSF_RENDERALLSTREAMS //試した限りこのフラグが最も成功率高い(再生出来ないのも結構あるが)
                                        ))){
        goto FAIL;
    }*/
    {//Filter が LAV Audio Decoder のときの pAudioData->GetFormat の結果が当てにならないので Filter と Pin を
     //列挙してビット数取得。面倒くさ過ぎ
        //IGraphBuilder *pGraphBuilder = NULL;
        //pAMMultiMediaStream->GetFilterGraph(&pGraphBuilder);
        if (!pGraphBuilder) {
            goto FAIL;
        }
        //IAMMultiMediaStream は推奨されないらしいので↓にした方が良いのかもしれない
        //IAMMultiMediaStream::OpenFile では開けないが IGraphBuilder::RenderFile なら開けるデータがある
        //IGraphBuilder::RenderFile すれば IPin::ConnectionMediaType と実際の MediaType が一致する
        //だがその後の処理の仕方が分からない。出力用の Filter を自作して AddFilter(?)
        //CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraphBuilder);
        //IMediaControl* pControl = NULL;
        //pGraphBuilder->QueryInterface(IID_IMediaControl, (void**)&pControl);
        //HRESULT hr = 0;
        //CDump *pDump = new CDump(NULL, &hr);
        //IBaseFilter *pDumpFilter = NULL;
        //pDump->QueryInterface(IID_IBaseFilter, (void**)&pDumpFilter);
        //pGraphBuilder->AddFilter(pDumpFilter, L"test");
        if(pGraphBuilder->RenderFile(m_szFileName, NULL) != S_OK){
            pGraphBuilder->Release();
           goto FAIL;
        }
#ifdef _DEBUG
        DWORD dwRegister = 0;
        AddToRot(pGraphBuilder, &dwRegister);
#endif
        IEnumFilters* pEnumFilters = NULL;
        pGraphBuilder->EnumFilters(&pEnumFilters);
        if(!pEnumFilters){
            goto FAIL;
        }
        while (1) {
            IBaseFilter* pFilter = NULL;
            ULONG fetched = 0;
            pEnumFilters->Next(1, &pFilter, &fetched);
            if (!fetched || !pFilter)break;
            FILTER_INFO fi = {0};
            pFilter->QueryFilterInfo(&fi);
            if(fi.pGraph)fi.pGraph->Release();
            if (wcscmp(fi.achName, L"Default DirectSound Device") == 0) {
                // FLAC / Monkeys Audio / OggVorbis 96kHz 24bit で確認(wv だと OpenFile の段階で失敗)
                // DirectSound で勝手に再生されてしまい、本体で制御出来ない
                //pGraphBuilder->RemoveFilter(pFilter);//勝手に再生されないだけで再生出来ないことに変わりはない
                pFilter->Release();
                pEnumFilters->Release();
                pGraphBuilder->Release();
                goto FAIL;//再生出来ないのでファイルオープンに失敗させる
                //break;
            }
            if(wcscmp(fi.achName, L"LAV Audio Decoder") != 0){
                pFilter->Release();//pAudioData->GetFormat が信頼できるのでこれ以上調べない
                continue;
            }
            IEnumPins* pEnumPins = NULL;
            pFilter->EnumPins(&pEnumPins);
            if(!pEnumPins){
                pFilter->Release();
                continue;
            }
            while (1) {
                IPin* pPin = NULL;
                fetched = 0;
                pEnumPins->Next(1, &pPin, &fetched);
                if (!fetched || !pPin)break;
                wchar_t* pszId = NULL;
                pPin->QueryId(&pszId);
                if(!pszId){
                    pPin->Release();
                    continue;
                }
                if (wcscmp(pszId, L"Out") != 0) {//"Out" だけ調べる
                    CoTaskMemFree(pszId);
                    pPin->Release();
                    continue;
                }
                CoTaskMemFree(pszId);
#if 0
                //LAV Audio Decoder だと当てにならない
                AM_MEDIA_TYPE mt = {0};
                pPin->ConnectionMediaType(&mt);
                
                if (mt.majortype != MEDIATYPE_Audio) {
                    _FreeMediaType(mt);
                    continue;
                }
                if ((mt.subtype != MEDIASUBTYPE_PCM) &&
                    (mt.subtype != MEDIASUBTYPE_IEEE_FLOAT)) {
                    _FreeMediaType(mt);
                    continue;
                }
                if (mt.formattype != FORMAT_WaveFormatEx) {
                    _FreeMediaType(mt);
                    continue;
                }
                if (mt.cbFormat < sizeof(WAVEFORMATEX)) {
                    _FreeMediaType(mt);
                    continue;
                }
                WAVEFORMATEX* pwfx = (WAVEFORMATEX*)mt.pbFormat;
                if (pwfx->wFormatTag == WAVE_FORMAT_PCM) {
                    nBitsPerSample = pwfx->wBitsPerSample;
                    nChannels = pwfx->nChannels;
                    nSampleRate = pwfx->nSamplesPerSec;
                }
                else if (pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
                    nBitsPerSample = pwfx->wBitsPerSample * -1;
                    nChannels = pwfx->nChannels;
                    nSampleRate = pwfx->nSamplesPerSec;
                }
                else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
                    if (pwfx->cbSize >= sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
                        WAVEFORMATEXTENSIBLE* pExt = (WAVEFORMATEXTENSIBLE*)pwfx;
                        if (pExt->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) {
                            nBitsPerSample = pwfx->wBitsPerSample;
                            nChannels = pwfx->nChannels;
                            nSampleRate = pwfx->nSamplesPerSec;
                            //if (nBitsPerSample == 32) {
                                //実際は float になる?
                                //wavpack で確認
                            //    nBitsPerSample *= -1;//32bit int もあり得るなら判別不可能?
                            //}
                        }
                        else if (pExt->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
                            nBitsPerSample = pwfx->wBitsPerSample * -1;
                            nChannels = pwfx->nChannels;
                            nSampleRate = pwfx->nSamplesPerSec;
                        }
                    }
                }
                _FreeMediaType(mt);
#else
                IEnumMediaTypes* pEnumMediaTypes = NULL;
                pPin->EnumMediaTypes(&pEnumMediaTypes);
                if(!pEnumMediaTypes){
                    pPin->Release();
                    continue;
                }
                while (!nBitsPerSample) {
                    AM_MEDIA_TYPE* pmt = NULL;
                    pEnumMediaTypes->Next(1, &pmt, &fetched);
                    if (!fetched || !pmt)break;
                    if (pmt->majortype != MEDIATYPE_Audio) {
                        _DeleteMediaType(pmt);
                        continue;
                    }
                    if ((pmt->subtype != MEDIASUBTYPE_PCM) &&
                        (pmt->subtype != MEDIASUBTYPE_IEEE_FLOAT)) {
                        _DeleteMediaType(pmt);
                        continue;
                    }
                    if (pmt->formattype != FORMAT_WaveFormatEx) {
                        _DeleteMediaType(pmt);
                        continue;
                    }
                    if (pmt->cbFormat < sizeof(WAVEFORMATEX)) {
                        _DeleteMediaType(pmt);
                        continue; 
                    }
                    WAVEFORMATEX* pwfx = (WAVEFORMATEX*)pmt->pbFormat;
                    if (pwfx->wFormatTag == WAVE_FORMAT_PCM) {
                        nBitsPerSample = pwfx->wBitsPerSample;
                        nChannels = pwfx->nChannels;
                        nSampleRate = pwfx->nSamplesPerSec;
                    }
                    else if (pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
                        nBitsPerSample = pwfx->wBitsPerSample * -1;
                        nChannels = pwfx->nChannels;
                        nSampleRate = pwfx->nSamplesPerSec;
                    }
                    else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
                        if(pwfx->cbSize >= sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)){
                            WAVEFORMATEXTENSIBLE *pExt = (WAVEFORMATEXTENSIBLE*)pwfx;
                            if(pExt->SubFormat == KSDATAFORMAT_SUBTYPE_PCM){
                                nBitsPerSample = pwfx->wBitsPerSample;//wavpack だと 32bit float のときがある
                                nChannels = pwfx->nChannels;          //32bit int の場合と区別がつかない
                                nSampleRate = pwfx->nSamplesPerSec;   //deprecated なインターフェースを使ってるから?
                                //if(nBitsPerSample == 32){//
                                //    nBitsPerSample *= -1;//
                                //}
                            }
                            else if(pExt->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT){
                                nBitsPerSample = pwfx->wBitsPerSample * -1;
                                nChannels = pwfx->nChannels;
                                nSampleRate = pwfx->nSamplesPerSec;
                            }
                        }
                    }
                    _DeleteMediaType(pmt);
                }
                pEnumMediaTypes->Release();
#endif
                pPin->Release();
            }
            pEnumPins->Release();
            pFilter->Release();
        }
        pEnumFilters->Release();
#ifdef _DEBUG
        RemoveFromRot(dwRegister);
#endif
        pGraphBuilder->Release();
        //pControl->Run();
    }
    IMediaStream* pMediaStream = NULL;
    pAMMultiMediaStream->GetMediaStream(MSPID_PrimaryAudio, &pMediaStream);
    if(!pMediaStream){
        goto FAIL;
    }
    IStreamSample *pStreamSample = NULL;
    pMediaStream->AllocateSample(0, &pStreamSample);
    if(!pStreamSample){
        pMediaStream->Release();
        goto FAIL;
    }
    pMediaStream->Release();
    pStreamSample->QueryInterface(IID_IAudioStreamSample, (void**)&pAudioStreamSample);
    if(!pAudioStreamSample){
        pStreamSample->Release();
        goto FAIL;
    }
    pStreamSample->Release();
    pAudioStreamSample->GetAudioData(&pAudioData);
    if(!pAudioData){
        goto FAIL;
    }
    STREAM_TIME qwDuration = 0;
    pAMMultiMediaStream->GetDuration(&qwDuration);
    int  bits = nBitsPerSample; //wfx.Format.wBitsPerSample;//wfx.wBitsPerSample の値は当てにならない
    DWORD sample_rate = nSampleRate;
    DWORD ch = nChannels;
    //LAV Audio Decoder のときは GetFormat の結果が当てにならない
    if (bits == 0) {
        pAudioData->GetFormat((WAVEFORMATEX*)&wfx);
        bits = wfx.Format.wBitsPerSample;
        sample_rate = wfx.Format.nSamplesPerSec;
        ch = wfx.Format.nChannels;
    }
    DWORD block_align = (abs(bits) / 8) * ch;
    m_pAMMultiMediaStream = pAMMultiMediaStream;
    m_pAudioStreamSample = pAudioStreamSample;
    m_pAudioData = pAudioData;
    m_dwBlockAlign = block_align;

    m_MediaInfo.dwSampleRate = sample_rate;
    m_MediaInfo.dwChannels = ch;
    m_MediaInfo.nBitsPerSample = bits;
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.qwLength = qwDuration;
    m_MediaInfo.dwCount = 1;
    //while(1){Sleep(1);}//スレッドを終了させずに Close したときにフリーズする現象の確認用
    CoUninitialize();
    return;

FAIL:
    if(pAudioStreamSample) pAudioStreamSample->Release();
    if(pAMMultiMediaStream) pAMMultiMediaStream->Release();
    if(pAudioData) pAudioData->Release();
    kpi_InitMediaInfo(&m_MediaInfo);
    CoUninitialize();
    return;
}
