#include <windows.h>
#include <stdio.h>
#include "kbspu_decoder.h"
#include "kbspu_config.h"
#include "KbGzFile.h"

//static const int UNIT_SAMPLE = 512;
static const int NCH = 2;
static const int BPS = 16;
static const int SAMPLE_RATE = 44100;

extern HINSTANCE  g_hKpi;//kbspu.kpi のインスタンスハンドル
static HINSTANCE  g_hSpu = NULL;//spuEternal.dll のインスタンスハンドル

static long(__stdcall *SPUinit)(void);
//static long(__stdcall *SPUopen)(HWND);
static long(__stdcall *SPUshutdown)(void);
//static long(__stdcall *SPUclose)(void);
static void/*long*/(__stdcall *SPUwriteRegister)(unsigned long, unsigned short);
static long(__stdcall *SPUfreeze)(unsigned long, SPUFreeze_t *);
static long(__stdcall *SPUrender)(BYTE *, DWORD);
static unsigned short(__stdcall *SPUreadRegister)(unsigned long);
static void/*long*/(__stdcall *SPUwriteDMAMem)(unsigned short *, int);
static void/*long*/(__stdcall *SPUreadDMAMem)(unsigned short *, int);
static void/*long*/(__stdcall *SPUplayADPCMchannel)(xa_decode_t *);
static long(__stdcall *SPUplayCDDAchannel)(short *, int);
static long(__stdcall *SPUconfigure)(void);

//////////////////////////////////////////////////////////////////////////
KbSpuDecoder* KbSpuDecoder::s_pActive = NULL;//マルチプルインスタンスでない場合に使う
//////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbSpuDecoder::LoadSpuPlugin(const wchar_t *cszDllPath)
{//SPU プラグインをロード
    if (g_hSpu){
        wchar_t szDll[MAX_PATH];
        GetModuleFileName(g_hSpu, szDll, _countof(szDll));
        if(_wcsicmp(szDll, cszDllPath) == 0){
            return TRUE;
        }
        else{
            UnloadSpuPlugin();
        }
    }
    g_hSpu = LoadLibrary(cszDllPath);
    if (!g_hSpu) {
        return FALSE;
    }
    SPUinit = (long(__stdcall *)(void))GetProcAddress(g_hSpu, "SPUinit");
    SPUshutdown = (long(__stdcall *)(void))GetProcAddress(g_hSpu, "SPUshutdown");
    //SPUopen = (long(__stdcall *)(HWND))GetProcAddress(g_hSpu, "SPUopen");
    //SPUclose = (long(__stdcall *)(void))GetProcAddress(g_hSpu, "SPUclose");
    SPUwriteRegister = (void/*long*/(__stdcall *)(unsigned long, unsigned short))GetProcAddress(g_hSpu, "SPUwriteRegister");
    SPUfreeze = (long(__stdcall *)(unsigned long, SPUFreeze_t *))GetProcAddress(g_hSpu, "SPUfreeze");
    SPUrender = (long(__stdcall *)(BYTE *, DWORD))GetProcAddress(g_hSpu, "SPUrender");
    SPUreadRegister = (unsigned short(__stdcall *)(unsigned long))GetProcAddress(g_hSpu, "SPUreadRegister");
    SPUwriteDMAMem = (void/*long*/(__stdcall *)(unsigned short *, int))GetProcAddress(g_hSpu, "SPUwriteDMAMem");
    SPUreadDMAMem = (void/*long*/(__stdcall *)(unsigned short *, int))GetProcAddress(g_hSpu, "SPUreadDMAMem");
    SPUplayADPCMchannel = (void/*long*/(__stdcall *)(xa_decode_t *))GetProcAddress(g_hSpu, "SPUplayADPCMchannel");
    SPUplayCDDAchannel = (long(__stdcall *)(short *, int))GetProcAddress(g_hSpu, "SPUplayCDDAchannel");
    SPUconfigure = (long(__stdcall *)(void))GetProcAddress(g_hSpu, "SPUconfigure");
    if (!SPUinit ||
        !SPUshutdown ||
        //!SPUopen ||
        //!SPUclose ||
        !SPUwriteRegister ||
        !SPUfreeze ||
        !SPUrender ||
        !SPUreadRegister ||
        !SPUwriteDMAMem ||
        !SPUreadDMAMem ||
        !SPUplayADPCMchannel ||
        !SPUplayCDDAchannel
        ) {
        FreeLibrary(g_hSpu);
        g_hSpu = NULL;
        SPUinit = NULL;
        SPUshutdown = NULL;
        //SPUopen = NULL;
        //SPUclose = NULL;
        SPUwriteRegister = NULL;
        SPUfreeze = NULL;
        SPUrender = NULL;
        SPUreadRegister = NULL;
        SPUwriteDMAMem = NULL;
        SPUreadDMAMem = NULL;
        SPUplayADPCMchannel = NULL;
        SPUplayCDDAchannel = NULL;
        SPUconfigure = NULL;
    }
    return g_hSpu != NULL;;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSpuDecoder::UnloadSpuPlugin(void)
{
    if (g_hSpu) {
        FreeLibrary(g_hSpu);
        g_hSpu = NULL;
        SPUinit = NULL;
        SPUshutdown = NULL;
        //SPUopen = NULL;
        //SPUclose = NULL;
        SPUwriteRegister = NULL;
        SPUfreeze = NULL;
        SPUrender = NULL;
        SPUreadRegister = NULL;
        SPUwriteDMAMem = NULL;
        SPUreadDMAMem = NULL;
        SPUplayADPCMchannel = NULL;
        SPUplayCDDAchannel = NULL;
        SPUconfigure = NULL;
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSpuDecoder::Config(void)
{
    if(SPUconfigure){
        SPUconfigure();
    }
}
//////////////////////////////////////////////////////////////////////////
KbSpuDecoder::KbSpuDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_pLog = NULL;
    m_dwLogCount = 0;
    m_dwCurrentLog = 0;
    m_dwCurrentTick = (DWORD)-1;
    m_dwStopTick = 0;
    m_dwTickPerSecond = 0;
    m_dwPrevTick = 0;
    kbspu_GetSettings(pConfig, &m_Settings);
}
//////////////////////////////////////////////////////////////////////////
KbSpuDecoder::~KbSpuDecoder(void)
{
    if (m_pFile || m_pLog) {//SPU を開いている
        if(m_dwCurrentTick != (DWORD)-1){
            //次回の冒頭で未処理のバッファが再生されてしまうのを回避
            for (int i = 0; i < 10; i++) {
                SPUrender((BYTE*)m_work.dma, 441 * 4);
            }
            SPUshutdown();
        }
        if (m_pFile) {
            m_pFile->Release();
        }
        delete[] m_pLog;
    }
    InterlockedCompareExchangePointer((void**)&s_pActive, NULL, this);//マルチプルインスタンスでない場合
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbSpuDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile,
                                        IKpiFolder *pFolder)
{
    if(InterlockedCompareExchangePointer((void**)&s_pActive, this, NULL) != NULL){
    //マルチプルインスタンスでない場合
    //複数のインスタンスを同時に持つことは出来ない
    //コンストラクタ内でグローバル変数にアクセスする場合はコンストラクタ内の方で適切に処理する
        return 0;
    }
    if(!LoadSpuPlugin(m_Settings.szDllPath)){
        return 0;
    }
    //gzip?
    pFile->Seek(0, FILE_BEGIN);
    BYTE magic[2] = { 0, 0 };
    if(pFile->Read(magic, 2) != 2){
        return 0;
    }
    if (magic[0] == 0x1f && magic[1] == 0x8b) {//gzip
        KbGzFile *pGzFile = new KbGzFile;
        if(!pGzFile->Open(pFile)){
            pGzFile->Release();
            return 0;
        }
        m_pFile = pGzFile;
    }
    else{
        m_pFile = pFile;
        m_pFile->AddRef();
    }
    m_pFile->Seek(0, FILE_BEGIN);

    ZeroMemory(&m_State.Freeze.szSPUName, sizeof(m_State.Freeze.szSPUName));
    ZeroMemory(&m_State.Freeze.xaS, sizeof(m_State.Freeze.xaS));
    ZeroMemory(&m_State.OSSFreeze, sizeof(m_State.OSSFreeze));
    m_State.Freeze.ulFreezeVersion = 1;
    m_State.Freeze.ulFreezeSize = sizeof(m_State);
    //新旧共通
    m_pFile->Read(m_State.Freeze.cSPURam, 0x80000);
    m_pFile->Read(m_State.Freeze.cSPUPort, 0x200);
    //新旧フォーマット判別
    union {
        struct {
            DWORD dwTickPerSecond;
            DWORD dwLogCount;
        };//old
        struct {
            DWORD dwStopTick;
            DWORD dwCurTick;
        };//new
    }h;
    if (m_pFile->Read(&h, 8) != 8) {
        m_pFile->Release();
        m_pFile = NULL;
        return 0;
    }
    do {
        if (h.dwTickPerSecond != 44100 || h.dwLogCount == 0) {
            break;
        }
        //old format?
        //dwStopTick == 44100 な new format を old format と
        //誤判別するのを回避
        DWORD dwSize = h.dwLogCount * sizeof(WriteRegLog);
        if (dwSize > 128 * 1024 * 1024) {
            //サイズがおかしい
            break;
        }
        WriteRegLog* pLog = new WriteRegLog[h.dwLogCount];
        if (pFile->Read(pLog, dwSize) != dwSize) {
            pFile->Seek(0x80000 + 0x200 + 8, SEEK_SET);
            delete[] pLog;
            break;
        }
        BYTE dummy;
        if (pFile->Read(&dummy, 1) == 1) {
            //サイズがおかしい
            pFile->Seek(0x80000 + 0x200 + 8, SEEK_SET);
            delete[] pLog;
            break;
        }
        m_pLog = pLog;
        m_dwTickPerSecond = h.dwTickPerSecond;
        m_dwLogCount = h.dwLogCount;
        m_dwStopTick = m_pLog[h.dwLogCount - 1].Tick;
        m_pFile->Release();
        m_pFile = NULL;
    } while (0);
    if (m_pFile) {//new format
        BYTE byLogEvent;
        if (m_pFile->Read(&byLogEvent, 1) != 1) {
            m_pFile->Release();
            m_pFile = NULL;
            return 0;
        }
        if (byLogEvent != REG_WRITE &&
            byLogEvent != REG_READ &&
            byLogEvent != DMA_WRITE &&
            byLogEvent != DMA_READ &&
            byLogEvent != XA_PLAY &&
            byLogEvent != CDDA_PLAY) {
            m_pFile->Release();
            m_pFile = NULL;
            return 0;
        }
        m_pFile->Seek(-1, SEEK_CUR);
        m_dwPrevTick = h.dwCurTick;
        m_dwStopTick = h.dwStopTick;
        m_dwTickPerSecond = 44100;
    }
    if(m_dwTickPerSecond == 0){
        m_pFile->Release();
        m_pFile = NULL;
        return 0;
    }
    m_dwCurrentTick = (DWORD)-1;
    m_MediaInfo.dwSampleRate = m_dwTickPerSecond;
    m_MediaInfo.dwChannels = NCH;
    m_MediaInfo.nBitsPerSample = BPS;
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.dwUnitSample = 0;//UNIT_SAMPLE;
    m_MediaInfo.qwLength = (UINT64)1000*10000*m_dwStopTick/m_dwTickPerSecond;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSpuDecoder::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 KbSpuDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    {//開き直す
        if(m_dwCurrentTick!=(DWORD)-1){//-1のときはまだ SPUinitしていない
            //SPUclose();
            //次回の冒頭で未処理のバッファが再生されてしまうのを回避
            for (int i = 0; i < 10; i++) {
                SPUrender((BYTE*)m_work.dma, 441*4);
            }
            SPUshutdown();
        }
        SPUinit();
        //SPUopen を呼べば spuPeopsSound.dll でも音が鳴るようになるが 
        //XA は鳴らない
        //SPUopen(NULL);
        m_State.Freeze.ulFreezeVersion = 1;
        m_State.Freeze.ulFreezeSize = sizeof(m_State);
        ZeroMemory(&m_State.Freeze.szSPUName, sizeof(m_State.Freeze.szSPUName));
        ZeroMemory(&m_State.Freeze.xaS, sizeof(m_State.Freeze.xaS));
        ZeroMemory(&m_State.OSSFreeze, sizeof(m_State.OSSFreeze));
        SPUfreeze(0, &m_State.Freeze);
    }
    if (!IsOld()) {
        m_pFile->Seek(0x80000 + 0x200 + 4, SEEK_SET);
    }
    m_dwCurrentTick = qwPosSample;
    m_dwCurrentLog = 0;
    m_dwPrevTick = 0;
    return qwPosSample;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSpuDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    const DWORD dwBlockAlign = 4;//16bit stereo
    DWORD dwRet = 0;
    while (m_dwCurrentTick < m_dwStopTick &&
        dwRet < dwSizeSample) {
        if (updateSPU()) {
            SPUrender(pBuffer, dwBlockAlign);
            pBuffer += dwBlockAlign;
            dwRet++;
            m_dwCurrentTick++;
        }
        else {
            m_dwCurrentTick = m_dwStopTick;
        }
    }
    return dwRet;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSpuDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    //if(!pTagInfo){
    //    return;
    //}
}
//////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbSpuDecoder::updateSPU(void)
{//エラーが見つかった場合は FALSE を返す
    if (IsOld()) {
        while (m_dwCurrentLog < m_dwLogCount && m_pLog[m_dwCurrentLog].Tick < m_dwCurrentTick)
        {   // SPUレジスタに書込み
            SPUwriteRegister(m_pLog[m_dwCurrentLog].reg, m_pLog[m_dwCurrentLog].val);
            m_dwCurrentLog++;
        }
        return TRUE;
    }
    while (1) {
        DWORD dwCurTick;
        BYTE  byLogEvent;
        if (m_dwPrevTick) {
            dwCurTick = m_dwPrevTick;
        }
        else {
            if (m_pFile->Read(&dwCurTick, 4) != 4) {
                return FALSE;
            }
        }
        if (dwCurTick > m_dwStopTick) {
            m_dwPrevTick = 0;
            return FALSE;
        }
        if (dwCurTick > m_dwCurrentTick) {
            m_dwPrevTick = dwCurTick;
            break;
        }
        m_dwPrevTick = 0;
        if (m_pFile->Read(&byLogEvent, 1) != 1) {
            return FALSE;
        }
        switch (byLogEvent) {
        case REG_WRITE:
        {
            //unsigned long reg;
            //unsigned short val;
            //fread(&reg, sizeof(reg), 1, fpSPU);
            //fread(&val, sizeof(val), 1, fpSPU);
#pragma pack(push, 1)
            struct {
                unsigned long  reg;
                unsigned short val;
            }t;
#pragma pack(pop)
            if (m_pFile->Read(&t, sizeof(t)) == sizeof(t)) {
                SPUwriteRegister(t.reg, t.val);
            }
            else {
                return FALSE;
            }
            break;
        }
        case REG_READ:
        {
            unsigned long reg;
            //unsigned short val;
            //fread(&reg, sizeof(reg), 1, fpSPU);
            //val = SPUreadRegister(reg);
            if (m_pFile->Read(&reg, 4) == 4) {
                SPUreadRegister(reg);
            }
            else {
                return FALSE;
            }
            break;
        }
        case DMA_WRITE:
        {
            /*int size;
            unsigned short *mem;
            fread(&size, sizeof(size), 1, fpSPU);
            mem = new unsigned short[size];
            fread(mem, size * 2, 1, fpSPU);
            SPUwriteDMAMem(mem, size);
            */
            DWORD size;
            if (m_pFile->Read(&size, 4) == 4) {
                unsigned short *mem;
                if (size > 64 * 1024 * 1024) {
                    return FALSE;
                }
                if (size > _countof(m_work.dma)) {
                    mem = new unsigned short[size];
                }
                else {
                    mem = m_work.dma;
                }
                if (m_pFile->Read(mem, size * 2) == size * 2) {
                    SPUwriteDMAMem(mem, size);
                }
                if (mem != m_work.dma) {
                    delete[] mem;
                }
            }
            else {
                return FALSE;
            }
            break;
        }
        case DMA_READ:
        {
            /*int size;
            unsigned short *mem;
            fread(&size, sizeof(size), 1, fpSPU);
            mem = new unsigned short[size];
            SPUreadDMAMem(mem, size);
            */
            int size;
            if (m_pFile->Read(&size, 4) == 4) {
                unsigned short *mem;
                if (size <= 0 || size > 64 * 1024 * 1024) {
                    return FALSE;
                }
                if (size > _countof(m_work.dma)) {
                    mem = new unsigned short[size];
                }
                else {
                    mem = m_work.dma;
                }
                SPUreadDMAMem(mem, size);
                if (mem != m_work.dma) {
                    delete[] mem;
                }
            }
            else {
                return FALSE;
            }
            break;
        }
        case XA_PLAY:
        {
            /*xa_decode_t xa;
            fread(&xa, sizeof(xa), 1, fpSPU);
            SPUplayADPCMchannel(&xa);
            */
            if (m_pFile->Read(&m_work.xa, sizeof(xa_decode_t)) == sizeof(xa_decode_t)) {
                SPUplayADPCMchannel(&m_work.xa);
            }
            else {
                return FALSE;
            }
            break;
        }
        case CDDA_PLAY:
        {
            /*int nbytes;
            short *pcm;
            fread(&nbytes, sizeof(nbytes), 1, fpSPU);
            pcm = new short[nbytes / 2];
            fread(pcm, nbytes, 1, fpSPU);
            SPUplayCDDAchannel(pcm, nbytes);
            */
            DWORD bytes;
            if (m_pFile->Read(&bytes, sizeof(bytes)) == 4) {
                short *pcm;
                if (bytes > 128 * 1024 * 1024) {
                    return FALSE;
                }
                if (bytes > sizeof(m_work.cdda)) {
                    pcm = new short[bytes / 2];
                }
                else {
                    pcm = m_work.cdda;
                }
                if (m_pFile->Read(pcm, bytes) == bytes) {
                    SPUplayCDDAchannel(pcm, bytes);
                }
                if (pcm != m_work.cdda) {
                    delete[] pcm;
                }
            }
            else {
                return FALSE;
            }
            break;
        }
        default:
        {
            return FALSE;
        }
        }
    }
    return TRUE;
}
//////////////////////////////////////////////////////////////////////////
