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

#include "kbsnsf_decoder.h"
#include "kbsnsf_config.h"
#include "snsf9x/pversion.h"
#include "tagget.h"

extern HINSTANCE g_hKpi;
static const wchar_t DRV_NAME[] = _T(XSFDRIVER_MODULENAME);//"xxx.bin"
///////////////////////////////////////////////////////////////////////////
//KbSnsfDecoder
///////////////////////////////////////////////////////////////////////////
KbSnsfDecoder* KbSnsfDecoder::s_pActive = NULL;
///////////////////////////////////////////////////////////////////////////
bool KbSnsfDecoder::LoadDrv(void)
{
    wchar_t path[MAX_PATH];
    GetModuleFileName(g_hKpi, path, _countof(path));
    wchar_t *path_delimiter = wcsrchr(path, L'\\');
    if (path_delimiter) {
        path_delimiter[1] = 0;
    }
    wcsncat_s(path, DRV_NAME, _TRUNCATE);
    HINSTANCE hDll = LoadLibrary(path);
    if (!hDll) {
        return false;
    }
    LPFNXSFDRVSETUP XSFSetup;
    XSFSetup = (LPFNXSFDRVSETUP)GetProcAddress(hDll, "XSFSetup");
    if (!XSFSetup) {
        FreeLibrary(hDll);
        return false;
    }
    IXSFDRV* drv = XSFSetup(XSFGETLIBCALLBACK, this);
    if (!drv) {
        FreeLibrary(hDll);
        return false;
    }
    m_hXSFDrv = hDll;
    m_xsfdrv = drv;
    return true;
}
///////////////////////////////////////////////////////////////////////////
int KbSnsfDecoder::GetLib(LPSTR lpszFilename, void **ppBuffer, DWORD *pdwSize)
{
    size_t libsize;
    xsfc::TString sLibName = xsfc::TString(m_fUTF8, lpszFilename);
    IKpiFile *pFile = NULL;
    if (m_pFolder) {
        m_pFolder->OpenFile(sLibName, &pFile);
    }
    if (!pFile) {
        return 1;
    }
    libsize = pFile->GetSize();
    void *libdata = m_xsfdrv->LibAlloc(libsize);
    if (!libdata) {
        pFile->Release();
        return 1;
    }
    libsize = pFile->Read(libdata, libsize);
    *ppBuffer = libdata;
    *pdwSize = DWORD(libsize);
    pFile->Release();
    return 0;
}
///////////////////////////////////////////////////////////////////////////
bool KbSnsfDecoder::Load(const BYTE *buf, size_t size)
{
    if (!m_xsfdrv) {
        return false;
    }
    m_fUTF8 = XSFTag::Exists("utf8", buf, size) != 0;
    if (m_xsfdrv->dwInterfaceVersion >= 5)
    {
        DWORD dwSampleRate = m_MediaInfo.dwSampleRate;
        INT32 nInterpolation = m_Settings.nInterpolation;
        INT32 nResampler = m_Settings.nResampler;
        DWORD dwDisableSurround = m_Settings.bDisableSurround;
        DWORD dwReverseStereo = m_Settings.bReverseStereo;

        m_xsfdrv->SetExtendParamVoid(1, &nInterpolation);
        m_xsfdrv->SetExtendParamVoid(2, &nResampler);
        m_xsfdrv->SetExtendParamVoid(3, &dwSampleRate);
        m_xsfdrv->SetExtendParamVoid(4, &dwDisableSurround);
        m_xsfdrv->SetExtendParamVoid(5, &dwReverseStereo);
    }
    if (m_xsfdrv->Start((void*)buf, size)) {
        return false;
    }
    //m_MediaInfo.dwSampleRate = 44100;
    //m_MediaInfo.dwChannels = 2;
    //m_MediaInfo.nBitsPerSample = 16;
    m_MediaInfo.dwSeekableFlags = 0;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwCount = 1;
    m_MediaInfo.dwNumber = 1;
    return true;
}
///////////////////////////////////////////////////////////////////////////
void __fastcall KbSnsfDecoder::Init(IKpiConfig *pConfig)
{
}
///////////////////////////////////////////////////////////////////////////
void __fastcall KbSnsfDecoder::Deinit(void)
{
}
///////////////////////////////////////////////////////////////////////////
KbSnsfDecoder::KbSnsfDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    kbsnsf_GetSettings(pConfig, &m_Settings);
    m_pFile = NULL;
    m_pFolder = NULL;
    m_xsfdrv = NULL;
    m_hXSFDrv = NULL;
    m_fUTF8 = false;
    m_qwCurSample = 0;
    m_qwEndSample = -1;
    m_volume = 1.0;
}
///////////////////////////////////////////////////////////////////////////
KbSnsfDecoder::~KbSnsfDecoder(void)
{
    if (m_xsfdrv) {
        m_xsfdrv->Term();
    }
    if (m_hXSFDrv) {
        FreeLibrary(m_hXSFDrv);
    }
    if (m_pFile) {
        m_pFile->Release();
    }
    if (m_pFolder) {
        m_pFolder->Release();
    }
    InterlockedCompareExchangePointer((void**)&s_pActive, NULL, this);
}
///////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbSnsfDecoder::Open(const KPI_MEDIAINFO *cpRequest, 
                                    IKpiFile *pFile, 
                                    IKpiFolder *pFolder)
{
    if (InterlockedCompareExchangePointer((void**)&s_pActive, this, NULL) != NULL) {
        return 0;
    }
    if (!LoadDrv()) {
        return 0;
    }
    const BYTE *buf;
    size_t size;
    if (!pFile->GetBuffer(&buf, &size)) {
        return 0;
    }
    m_pFile = pFile;
    m_pFolder = pFolder;
    m_pFile->AddRef();
    if (pFolder) {
        m_pFolder->AddRef();
    }
    int nSampleRate = m_Settings.nSampleRate;
    if (nSampleRate < 0) {
        nSampleRate *= -1;
    }
    else if (cpRequest && (cpRequest->dwSampleRate != 0)) {
        nSampleRate = cpRequest->dwSampleRate;
    }
    switch (nSampleRate) {
    case 44100: case 48000: case 88200: case 96000: case 176400: case 192000:break;
    default: nSampleRate = XSFDRIVER_SAMPLERATE;
    }
    m_MediaInfo.dwSampleRate = nSampleRate;
    if (!Load(buf, size)) {
        return 0;
    }
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.nBitsPerSample = 16;

    if (!m_Settings.bIgnoreVolumeTag) {
        xsfc::TString tagvolume = XSFTag::Get("volume", buf, size);
        if (tagvolume[0]) {
            m_volume = tagvolume.GetFloat();
        }
    }
    int length_in_ms = XSFTag::GetLengthMS(buf, size, -1);
    int fade_in_ms = XSFTag::GetFadeMS(buf, size, -1);

    if (length_in_ms > 0) {
        m_MediaInfo.qwLength = kpi_MsTo100ns(length_in_ms);
    }
    else {
        m_MediaInfo.qwLength = (UINT64)-1;
    }
    if (fade_in_ms) {
        m_MediaInfo.qwLoop = -1;
        if (fade_in_ms > 0) {
            m_MediaInfo.qwFadeOut = kpi_MsTo100ns(fade_in_ms);
        }
        else {
            m_MediaInfo.qwFadeOut = (UINT64)-1;
        }
    }
    else {
        if (m_MediaInfo.qwLength != (UINT64)-1) {
            m_MediaInfo.qwLoop = 0;
            m_MediaInfo.qwFadeOut = 0;
            m_qwEndSample = kpi_100nsToSample(m_MediaInfo.qwLength, m_MediaInfo.dwSampleRate);
        }
        else {
            m_MediaInfo.qwLoop = -1;
            m_MediaInfo.qwFadeOut = -1;
        }
    }
    m_volume *= m_Settings.dVolume;
    if (m_volume != 1.0) {
        m_MediaInfo.nBitsPerSample = -64;
    }
    return m_MediaInfo.dwCount;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSnsfDecoder::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;
    }
    *ppMediaInfo = &m_MediaInfo;
    return 1;
}
///////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbSnsfDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if (qwPosSample >= m_qwCurSample) {
        return m_qwCurSample;
    }
    m_xsfdrv->Term();
    m_qwCurSample = -1;
    const BYTE *buf;
    size_t size;
    if (m_pFile->GetBuffer(&buf, &size)) {
        if (Load(buf, size)) {
            m_qwCurSample = 0;
        }
    }
    return 0;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSnsfDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    if (m_qwCurSample == (UINT64)-1) {
        return 0;
    }
    if (m_qwCurSample + dwSizeSample > m_qwEndSample) {
        dwSizeSample = m_qwEndSample - m_qwCurSample;
    }
    if (m_MediaInfo.nBitsPerSample == 16) {
        m_xsfdrv->Gen(pBuffer, dwSizeSample);
    }
    else {
        const double k = m_volume / 0x8000;
        const DWORD unit_sample = 2048;
        short temp[unit_sample * 2];
        DWORD ret = 0;
        double *dest = (double*)pBuffer;
        while (ret < dwSizeSample) {
            DWORD sample = dwSizeSample - ret;
            if (sample > unit_sample) {
                sample = unit_sample;
            }
            m_xsfdrv->Gen(temp, sample);
            for (int i = 0; i < sample * 2; i++) {
                dest[i] = temp[i] * k;
            }
            dest += sample * 2;
            ret += sample;
        }
    }
    m_qwCurSample += dwSizeSample;
    return dwSizeSample;
}
///////////////////////////////////////////////////////////////////////////
