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

#include "kbmdx_decoder.h"
#include "lzx/lzx042.h"
#include "mdc/mdc.h"

#include "mdx_util.h"
#include "mxdrv.h"


#define MDX_BUFFER_SIZE		1 * 1024 * 1024 //exsample main.c より
#define PDX_BUFFER_SIZE		2 * 1024 * 1024 //
#define MEMORY_POOL_SIZE	8 * 1024 * 1024 //

#define MAX_TITLE 512
#define MAX_PDXNAME 512

static __inline void SetWordBE(unsigned char *p, unsigned v)
{
	p[0] = (unsigned char)(v >> 8);
	p[1] = (unsigned char)(v);
}
static __inline unsigned GetWordBE(const unsigned char *p)
{
	return ((unsigned)p[0] << 8) + p[1];
}
static int GetDwordBE(const BYTE *p)
{
	int ret;
	ret  = ((int)p[0]) << 0x18;
	ret |= ((int)p[1]) << 0x10;
	ret |= ((int)p[2]) << 0x08;
	ret |= ((int)p[3]) << 0x00;
	return ret;
}
static BYTE *load_pdxfile(IKpiFolder *pFolder, const wchar_t *cszPdxW, const char *cszPdxA, DWORD *psize)
{
    IKpiFile *pPdxFile = NULL;
    if(!pFolder->OpenFile(cszPdxW, &pPdxFile)){
        return NULL;
    }
    UINT64 qwPdxSize = pPdxFile->GetSize();
    if(qwPdxSize > 1024 * 1024 * 1024){//大きすぎ
        pPdxFile->Release();
        return NULL;
    }
    BYTE *ret = NULL;
    BYTE *pdxbuf = NULL;
    BYTE *pdxfile = (BYTE*)malloc((size_t)qwPdxSize);
    qwPdxSize = pPdxFile->Read(pdxfile, (DWORD)qwPdxSize);
	
    do
	{
        unsigned pdxsize = qwPdxSize;
		unsigned lzxsize, pdxbufsize, pdxnamelen, pdxbodyptr, pdxbodysize;
		if (!pdxfile) break;
		lzxsize = lzx042check(pdxfile);
		pdxnamelen = strlen(cszPdxA) + 1;
		pdxbodyptr = 8 + pdxnamelen;
		pdxbodysize = (lzxsize ? lzxsize : pdxsize);
		pdxbufsize = pdxbodyptr + pdxbodysize;
		/* pdxbufsize = (pdxbufsize + 1024) & ~ 1023; */
		pdxbuf = (BYTE*)malloc(pdxbufsize * 2 + 8 + 1);
		if (!pdxbuf) break;
		//if (((unsigned)(pdxbuf + pdxbodyptr)) & 1)
		if (((UINT_PTR)(pdxbuf + pdxbodyptr)) & 1)
		{
			pdxbodyptr++;
			pdxbufsize++;
		}
		ZeroMemory(pdxbuf, pdxbodyptr);
		memcpy(pdxbuf + 8, cszPdxA, pdxnamelen);
		if (lzxsize)
			lzx042decode(pdxbuf + pdxbodyptr, lzxsize, pdxfile, pdxsize);
		else
			memcpy(pdxbuf + pdxbodyptr, pdxfile, pdxsize);
		SetWordBE(pdxbuf + 0, 0);
		SetWordBE(pdxbuf + 2, 0);
		SetWordBE(pdxbuf + 4, pdxbodyptr);
		SetWordBE(pdxbuf + 6, pdxnamelen);
		*psize = pdxbodysize;
		ret = pdxbuf; pdxbuf = 0;
	} while (0);
	if (pdxfile) free(pdxfile);
	if (pdxbuf) free(pdxbuf);
	pPdxFile->Release();
    return ret;
}
//////////////////////////////////////////////////////////////////////////
KbMdxDecoder::KbMdxDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_Context.m_impl = NULL;
    m_qwCurSample = 0;
    m_qwEndSample = 0;
    m_szPdx[0] = 0;
    m_bIsPdxLoaded = false;
    m_bIsMdc = false;
    m_bIsLzx = false;
    kbmdx_GetSettings(pConfig, &m_Settings);
}
//////////////////////////////////////////////////////////////////////////
KbMdxDecoder::~KbMdxDecoder(void)
{
    MxdrvContext_Terminate(&m_Context);
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbMdxDecoder::Open(const KPI_MEDIAINFO *cpRequest, 
                                    IKpiFile   *pFile, 
                                    IKpiFolder *pFolder,
                                    IKpiConfig *pConfig)
{
    if(!MxdrvContext_Initialize(&m_Context, MEMORY_POOL_SIZE)){
        return 0;
    }
    DWORD dwSampleRate = cpRequest ? cpRequest->dwSampleRate : 0;
    if(m_Settings.nSampleRate < 0){//要求無視
        dwSampleRate = -m_Settings.nSampleRate;
    }
    else if(dwSampleRate == 0){
        dwSampleRate = m_Settings.nSampleRate;
    }
    //22050Hz, 44100Hz, 48000Hz 以外は受け付けない
    if(dwSampleRate < 44100){
        dwSampleRate = 22050;
    }
    else if(dwSampleRate < 48000){
        dwSampleRate = 44100;
    }
    else{
        dwSampleRate = 48000;
    }
    UINT64 qwSize = pFile->GetSize();
    if(qwSize > 1024 * 1024 * 1024){//大きすぎる
        return 0;
    }
    BYTE *mdxfile = (BYTE*)malloc((size_t)qwSize);
    BYTE *mdxbuf = NULL;
    BYTE *pdxbuf = NULL;
    do{
        unsigned int mdxsize = pFile->Read(mdxfile, (DWORD)qwSize);
        if(mdxsize <= 3 + 1 + 20){
            break;
        }
        /* MDC to MDX */
	    if (GetDwordBE(mdxfile) == 0x4d44431a){
		    mdxfile = mdc2mdx(mdxfile, &mdxsize);
		    if (!mdxfile || mdxsize <= 3 + 1 + 20) {
                break;
            }
            m_bIsMdc = true;
        }
        char headtitle[MAX_TITLE+32];
        char headpdxname[MAX_PDXNAME+32];
        BYTE *p;
        BYTE *p2;
        BYTE c;
        // SKIP TITLE 
        p = mdxfile;
	    while (mdxsize){
	        c = *(p++);
	        if (--mdxsize == 0) break;
	        if (c == 0x0d) break;
	        if (c == 0x0a) break;
	        if (c < 0x20 && c != 0x1b)
	        {
		        mdxsize = 0;
		        break;
	        }
        }
        if (mdxsize == 0){
            break;
        }
        p[-1] = 0x00;
        strncpy_s(headtitle, (char*)mdxfile, _TRUNCATE);
        if ((p - mdxfile) & 1)
        {
	        *(p++) = 0x00;
			if (--mdxsize == 0) break;
        }
        p2 = p;
        if (c != 0x0d)
        {
	        while (mdxsize && *p2 != 0x0d) p2++, mdxsize--;
	        if (mdxsize == 0) break;
	        p2++;
	        if (--mdxsize == 0) break;
        }
        while (mdxsize && *p2 != 0x1a) p2++, mdxsize--;
        if (mdxsize == 0) break;
        p2++;
        if (--mdxsize == 0) break;
        DWORD pdxbufsize = 0;
        /* LOAD PDX */
        strncpy_s(headpdxname, (char*)p2, _TRUNCATE);
        if (*headpdxname){
            MultiByteToWideChar(CP_ACP, 0, headpdxname, -1, m_szPdx, _countof(m_szPdx));
            m_szPdx[_countof(m_szPdx)-1] = 0;
            wchar_t *pszExt = wcsrchr(m_szPdx, L'.');
            if(!pszExt || _wcsicmp(pszExt, L".pdx") != 0){
                wcsncat_s(m_szPdx, L".pdx", _TRUNCATE);
            }
            pdxbuf = load_pdxfile(pFolder, m_szPdx, headpdxname, &pdxbufsize);
	        if (!pdxbuf){
		        IKpiFolder *pPdxFolder = NULL;
                if(m_Settings.szPdxPath[0]){
                    pFolder->OpenFolder(m_Settings.szPdxPath, &pPdxFolder);
                }
                if(pPdxFolder){
                    pdxbuf = load_pdxfile(pPdxFolder, m_szPdx, headpdxname, &pdxbufsize);
                    pPdxFolder->Release();
                }
	        }
            m_bIsPdxLoaded = pdxbuf != NULL;
        }
        if (!pdxbuf) pdxbufsize = 0;
        /* SKIP PDX NAME */
        while (mdxsize && *p2) p2++, mdxsize--;
        if (mdxsize == 0) break;
        p2++;
        if (--mdxsize == 0) break;

		unsigned mdxbufsize, lzxsize;
		unsigned mdxtitlelen, mdxbodyptr, mdxbodysize;
        lzxsize = lzx042check(p2);
        m_bIsLzx = (lzxsize > 0);
        mdxtitlelen = strlen(headtitle) + 1;
        mdxbodyptr = 8 + mdxtitlelen;
        mdxbodysize = (lzxsize ? lzxsize : mdxsize);
        mdxbufsize = mdxbodyptr + mdxbodysize;
        /* mdxbufsize = (mdxbufsize + 1024) & ~ 1023; */
        mdxbuf = (BYTE*)malloc(mdxbufsize * 2 + 8 + 1);
        if (!mdxbuf) break;
        //if (((unsigned)(mdxbuf + mdxbodyptr)) & 1)
        if (((UINT_PTR)(mdxbuf + mdxbodyptr)) & 1)
        {
	        mdxbodyptr++;
	        mdxbufsize++;
        }
        ZeroMemory(mdxbuf, mdxbodyptr);
        memcpy(mdxbuf + 8, headtitle, mdxtitlelen);
        if (lzxsize)
	        lzx042decode(mdxbuf + mdxbodyptr, lzxsize, p2, mdxsize);
        else
	        memcpy(mdxbuf + mdxbodyptr, p2, mdxsize);
        SetWordBE(mdxbuf + 0, 0);
        SetWordBE(mdxbuf + 2, pdxbuf ? 0 : 0xffff);
        SetWordBE(mdxbuf + 4, mdxbodyptr);
        SetWordBE(mdxbuf + 6, 8);
        free(mdxfile);	mdxfile = 0;
        /*
	        再生周波数(Hz)
	        割込周期(ms) 未使用(0)
	        PCM変換バッファ長(ms)
	        発声遅延(ms) 未使用
	        MDXバッファサイズ(byte)
	        PDXバッファサイズ(byte)
        */
        int ret = MXDRV_Start(&m_Context, dwSampleRate, 0, 0, 0, mdxbufsize * 2, pdxbufsize * 2, 0);
        if (ret != 0) break;

        if (1/*pdxbuf*/)
        {
	        unsigned expcm;
	        expcm = GetWordBE(&mdxbuf[mdxbodyptr + 2]);
	        if (expcm < mdxbufsize)
		        expcm = mdxbuf[mdxbodyptr + expcm] == 0xe8;
	        else
		        expcm = 0;
	        p2 = (unsigned char *)MXDRV_GetWork(&m_Context, MXDRV_WORK_PCM8);
	        *p2 = expcm ? 1 : 0;
        }
        //演奏時間は全て2000ms加算されている
        //フェード時間は2000ms減算する
        //ループ曲でない場合は返された値をそのまま曲長とする
        //ループ曲の1ループ目の曲長は 2000ms 減算する
        int length1 = MXDRV_MeasurePlayTime(&m_Context, mdxbuf, mdxbufsize, pdxbuf, pdxbufsize, 1, 0);
        int length2 = MXDRV_MeasurePlayTime(&m_Context, mdxbuf, mdxbufsize, pdxbuf, pdxbufsize, 2, 0);
        int loop = length2-length1; //元々加算された値同士の差なので減算不要
        if(loop > 0)length1 -= 2000;//ループ曲の1ループ目の曲長は 2000ms 減算する

        m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
        m_MediaInfo.dwSampleRate = dwSampleRate;
        m_MediaInfo.nBitsPerSample = 16;
        m_MediaInfo.dwChannels = 2;
        m_MediaInfo.qwLength = length1 * 10000ui64;
        m_MediaInfo.qwLoop = loop * 10000ui64;
        if(loop > 0){
            int fade = MXDRV_MeasurePlayTime(&m_Context, mdxbuf, mdxbufsize, pdxbuf, pdxbufsize, 0, 1) - 2000;
            m_MediaInfo.qwFadeOut = fade * 10000ui64;
        }
        else{
            m_MediaInfo.qwFadeOut = 0;
        }
        m_MediaInfo.dwCount = 1;
        //
        if(loop == 0){
            m_qwEndSample = kpi_100nsToSample(m_MediaInfo.qwLength, m_MediaInfo.dwSampleRate);
        }
        else{
            m_qwEndSample = (UINT64)-1;
        }
        MXDRV_Play(&m_Context, mdxbuf, mdxbufsize, pdxbuf, pdxbufsize);
        if (mdxbuf) free(mdxbuf);
        if (pdxbuf) free(pdxbuf);
        return 1;
    } while (0);
    if (mdxbuf) free(mdxbuf);
    if (mdxfile) free(mdxfile);
    if (pdxbuf) free(pdxbuf);
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMdxDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(dwNumber < 0 || dwNumber > m_MediaInfo.dwCount){
    //番号指定が不正
        return 0;
    }
    if (!ppMediaInfo) {
    //タイトル/アーティスト取得のみ
        return dwNumber;
    }
    m_MediaInfo.dwNumber = dwNumber;
    *ppMediaInfo = &m_MediaInfo;
    if(pTagInfo){
        GetTagInfo(pTagInfo);
    }
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbMdxDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{//シーク（戻り値はシーク後の再生位置）
    if(qwPosSample > m_qwEndSample){
        qwPosSample = m_qwEndSample;
    }
    UINT64 qwPos100ns = kpi_SampleTo100ns(qwPosSample, m_MediaInfo.dwSampleRate);
	MXDRV_PlayAt(&m_Context, qwPos100ns/10000, 0x7FFFFFFF/*m_loop*/, 0);
    m_qwCurSample = kpi_100nsToSample(qwPos100ns, m_MediaInfo.dwSampleRate);
    return m_qwCurSample;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMdxDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    if(m_qwCurSample >= m_qwEndSample){
        return 0;
    }
	MXDRV_GetPCM(&m_Context, pBuffer, dwSizeSample);
    if(m_qwCurSample + dwSizeSample >= m_qwEndSample){
        dwSizeSample = m_qwEndSample - m_qwCurSample;
    }
    m_qwCurSample += dwSizeSample;
    return dwSizeSample;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbMdxDecoder::GetTagInfo(IKpiTagInfo *pTagInfo)
{
    if(!pTagInfo)return;
    if(m_szPdx[0]){
        wchar_t szValue[MAX_PATH+32];
        wcsncpy_s(szValue, m_szPdx, _TRUNCATE);
        if(m_bIsPdxLoaded){
            wcsncat_s(szValue, L"(loaded)", _TRUNCATE);
        }
        else{
            wcsncat_s(szValue, L"(failed)", _TRUNCATE);
        }
        pTagInfo->wSetValueW(L"PDX", -1, szValue, -1);
    }
    else{
        pTagInfo->wSetValueW(L"PDX", -1, L"none", -1);
    }
    pTagInfo->wSetValueW(L"IsMDC", -1, m_bIsMdc ? L"true" : L"false", -1);
    pTagInfo->wSetValueW(L"IsLZX", -1, m_bIsLzx ? L"true" : L"false", -1);
#ifdef _DEBUG
    if(m_bIsMdc){
        int t = 0;
    }
    if(m_bIsLzx){
        int t = 0;
    }
#endif
}
//////////////////////////////////////////////////////////////////////////
