#include <windows.h>
#include <wchar.h>
#include "kbbassmidi_decoder.h"
#include "kbbassmidi_config.h"
#include "bass/bass.h"
#include "bass/bassmidi.h"

static const int MAX_FONTS = 32;
static HPLUGIN g_hBassMidi = 0;
static HSOUNDFONT g_hFonts[MAX_FONTS] = {0};

//////////////////////////////////////////////////////////////////////////
static void CALLBACK ikpifile_close(void* user)
{
    //IKpiFile *pFile = (IKpiFile*)user;
    //pFile->Release();
}
static QWORD CALLBACK ikpifile_length(void* user)
{
    IKpiFile* pFile = (IKpiFile*)user;
    return pFile->GetSize();
}
static DWORD CALLBACK ikpifile_read(void* buffer, DWORD length, void* user)
{
    IKpiFile* pFile = (IKpiFile*)user;
    return pFile->Read(buffer, length);
}
static BOOL CALLBACK ikpifile_seek(QWORD offset, void* user)
{
    IKpiFile* pFile = (IKpiFile*)user;
    return pFile->Seek(offset, FILE_BEGIN) != KPI_FILE_EOF;
}
static BASS_FILEPROCS bass_ikpifile_procs = {
    ikpifile_close,
    ikpifile_length,
    ikpifile_read,
    ikpifile_seek
};
//////////////////////////////////////////////////////////////////////////
KbBassmidiDecoder::KbBassmidiDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pConfig = pConfig;
    pConfig->AddRef();
    m_hStream = NULL;
    m_pFile = NULL;
}
//////////////////////////////////////////////////////////////////////////
KbBassmidiDecoder::~KbBassmidiDecoder(void)
{
    if(m_hStream){
        BASS_StreamFree(m_hStream);
    }
    if(m_pFile){
        m_pFile->Release();
    }
    m_pConfig->Release();
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbBassmidiDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                    IKpiFile *pFile,
                                    IKpiFolder *pFolder)
{
    if(!g_hBassMidi)return 0;
    kbbassmidi_Settings settings;
    kbbassmidi_GetSettings(m_pConfig, &settings);
    int nSampleRate = settings.nSampleRate;
    if(cpRequest && cpRequest->dwSampleRate){
        if(nSampleRate >= 0){
            nSampleRate = cpRequest->dwSampleRate;
        }
    }
    if(nSampleRate < 0) {
        nSampleRate *= -1;
    }
    HSTREAM chan = BASS_MIDI_StreamCreateFileUser(STREAMFILE_NOBUFFER, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, 
                            &bass_ikpifile_procs, pFile, nSampleRate);
    if(!chan)return 0;
    UINT64 len = BASS_ChannelGetLength(chan, BASS_POS_BYTE);
    BASS_CHANNELINFO info;
    BASS_ChannelGetInfo(chan, &info);
    int nBits = (info.flags & BASS_SAMPLE_FLOAT ? -32 : 16);
    m_MediaInfo.dwSampleRate   = info.freq; 
    m_MediaInfo.nBitsPerSample = nBits;
    m_MediaInfo.dwChannels        = info.chans;
    m_MediaInfo.dwSeekableFlags   = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;

    m_MediaInfo.qwLength = len / (double)((abs(nBits)/8)*info.chans) / info.freq * 1000 * 10000ui64;
    m_MediaInfo.dwUnitSample = 0;
    m_MediaInfo.dwCount = 1;
    m_hStream = chan;
    m_pFile = pFile;
    pFile->AddRef();
    return 1;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbBassmidiDecoder::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 dwNumber;
    }
    m_MediaInfo.dwNumber = dwNumber;
    *ppMediaInfo = &m_MediaInfo;
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbBassmidiDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    int block_align = (abs(m_MediaInfo.nBitsPerSample) / 8) * m_MediaInfo.dwChannels;
    //シーク直後が少し不自然なので1秒前にシーク
    //要求先より1秒手前だが本体が空読みして調整してくれる
    if(qwPosSample < m_MediaInfo.dwSampleRate){
        qwPosSample = 0;
    }
    else{
        qwPosSample -= m_MediaInfo.dwSampleRate;
    }
    UINT64 pos = qwPosSample * block_align;
    BASS_ChannelSetPosition(m_hStream, pos, BASS_POS_BYTE);
    return qwPosSample;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbBassmidiDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    int block_align = (abs(m_MediaInfo.nBitsPerSample)/8) * m_MediaInfo.dwChannels;
    int ret = BASS_ChannelGetData(m_hStream, pBuffer, dwSizeSample * block_align);
    if(ret == -1)return 0;
    return ret / block_align;
}
//////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbBassmidiDecoder::UpdateConfig(void *pvReserved)
{
    return 0;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbBassmidiDecoder::Initialize(IKpiConfig *pConfig)
{
    extern HINSTANCE g_hKpi;
    if(g_hBassMidi)return;

    if(HIWORD(BASS_GetVersion()) != BASSVERSION){
        return ;
    }
    if (!BASS_Init(0, 44100, 0, 0, NULL)) {
        return ;
    }
    g_hBassMidi = BASS_PluginLoad(L"bassmidi.dll", 0);
    if(!g_hBassMidi)return;

    kbbassmidi_Settings settings;
    kbbassmidi_GetSettings(pConfig, &settings);
    const wchar_t *cszFont = settings.szSoundFont;
    wchar_t *pszFont = settings.szSoundFont;//settings.szSoundFont の内容が変わるが問題なし
    wchar_t *context = NULL;
    BASS_MIDI_FONT fonts[MAX_FONTS] = {0};
    int nFonts = 0;
    wchar_t szPrevPath[MAX_PATH];
    szPrevPath[0] = 0;
    while(nFonts < MAX_FONTS){
        wchar_t *p = wcstok_s(pszFont, L"|", &context);
        if(!p)break;
        pszFont = NULL;
        while(*p == ' ')p++;//空白をスキップ
        wchar_t *t = p + wcslen(p) - 1;
        while(t >= p && *t == ' '){//末尾の空白を除く
            *t = 0;
            t--;
        }
        HSOUNDFONT font = NULL;
        if (wcschr(p, L'\\') == NULL) {//フルパスでない
            wchar_t* t = wcsrchr(szPrevPath, L'\\');
            if (t) {
                t[1] = 0;//1つ前と同じフォルダ
            }
            else{
                szPrevPath[0] = 0;//おそらくカレントディレクトリから検索(未確認)
            }
            wcsncat_s(szPrevPath, p, _TRUNCATE);
            font = BASS_MIDI_FontInit(szPrevPath, 0);
            if(!font)continue;
        }
        else{
            font = BASS_MIDI_FontInit(p, 0);
            wcsncpy_s(szPrevPath, p, _TRUNCATE);
        }
        if(font){
            fonts[nFonts].font = font;
            fonts[nFonts].preset = -1; // use all presets
            fonts[nFonts].bank = 0;    // use default bank(s)
            g_hFonts[nFonts] = font;
            nFonts++;
        }
    }
    if (nFonts > 0) {
        BASS_MIDI_StreamSetFonts(0, &fonts, nFonts); // set default soundfont
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbBassmidiDecoder::Uninitialize(void)
{
    for(int i = 0; i < MAX_FONTS; i++){
        if(!g_hFonts[i])break;
        BASS_MIDI_FontFree(g_hFonts[i]);
        g_hFonts[i] = NULL;
    }
    BASS_PluginFree(0);
    if(g_hBassMidi){
        BASS_PluginFree(g_hBassMidi);
        g_hBassMidi = NULL;
    }
    BASS_Free();
}
//////////////////////////////////////////////////////////////////////////

