#include <windows.h>
#include <stdio.h>
#include "kbusf_decoder.h"
#include "kbusf_config.h"
#include "lazyusf2/usf/usf.h"
#include "psflib/psflib.h"

struct psflib_context
{
    IKpiFolder *folder;
    IKpiFile   *file;
    void       *state;
    char  length[64];
    char  fade[64];
    char  volume[64];
    bool  utf8;
};
static void context_to_uri(char *uri, int size, psflib_context* context)
{//先頭16文字: context のアドレスを文字列化
 //17文字目:   '/' にする
 //32bit/64bit版共通の処理
    UINT64 context_ui64 = (UINT64)(DWORD_PTR)context;
    _snprintf_s(uri, size, _TRUNCATE, "%016llx/", context_ui64);
}
static psflib_context* path_to_context(const char *path)
{ //path の先頭 16 文字に psflib_context へのアドレス
  //17文字目が '/'
    char context_addr[17];
#ifdef _DEBUG 
    //psflib の仕様上起こり得ない筈
    if(strlen(path) <= 16){//長さがおかしい
        return NULL;
    }
    if(path[16] != '/'){
        return NULL;
    }
#endif
    memcpy(context_addr, path, 16);
    context_addr[16] = 0;
    psflib_context *context = (psflib_context*)_strtoui64(context_addr, NULL, 16);
    return context;
}
static void *fopen_IKpiFile(const char *path)
{//opens the file pointed to by path read-only in binary mode,
 //accepts UTF-8 encoding, returns file handle 
    psflib_context *context = path_to_context(path);
    //if(!context)return NULL;
    //18文字目以降が usflib or usf のパス(空白のときは context->file)
    IKpiFile *file;
    path += 17;
    if(path[0] == 0){
        file = context->file;
        file->AddRef();
    }
    else{
        IKpiFolder *folder = context->folder;
        wchar_t *filename;
        int len = MultiByteToWideChar(context->utf8 ? CP_UTF8 : CP_ACP, 0, path, -1, NULL, 0);
        if(len == 0){
            return NULL;
        }
        filename = (wchar_t*)malloc((len+1)*sizeof(wchar_t));
        MultiByteToWideChar(context->utf8 ? CP_UTF8 : CP_ACP, 0, path, -1, filename, len);
        filename[len] = 0;
        file = NULL;
        folder->OpenFile(filename, &file);
        free(filename);
    }
    return file;
}
static size_t fread_IKpiFile(void * buffer, size_t size, size_t count, void * handle)
{// reads to specified buffer, returns count of size bytes read 
    IKpiFile *file = (IKpiFile*)handle;
    return file->Read(buffer, size*count) / size;
}
static int fseek_IKpiFile(void * handle, int64_t pos, int whence)
{// returns zero on success, -1 on error 
    IKpiFile *file = (IKpiFile*)handle;
    if(file->Seek(pos, whence) != KPI_FILE_EOF){
        return 0;
    }
    return -1;
}
static int fclose_IKpiFile(void * handle)
{// returns zero on success, -1 on error 
    IKpiFile *file = (IKpiFile*)handle;
    file->Release();
    return 0;
}
static long ftell_IKpiFile(void * handle)
{// returns current file offset 
    IKpiFile *file = (IKpiFile*)handle;
    return file->Seek(0, FILE_CURRENT);
}
static int load_callback(void * context, const uint8_t * exe, size_t exe_size,
    const uint8_t * reserved, size_t reserved_size)
{
    psflib_context *p = (psflib_context*)context;
    return usf_upload_section(p->state, reserved, reserved_size);
}
static int info_callback(void * context, const char * name, const char * value)
{
    psflib_context *p = (psflib_context*)context;
    if (_strcmpi(name, "length") == 0) {
        strncpy_s(p->length, value, _TRUNCATE);
    }
    else if (_strcmpi(name, "fade") == 0) {
        strncpy_s(p->fade, value, _TRUNCATE);
    }
    else if (_strcmpi(name, "volume") == 0) {
        strncpy_s(p->volume, value, _TRUNCATE);
    }
    else if (_strcmpi(name, "utf8") == 0) {
        p->utf8 = true;
    }
    return 0;
}
/*
static void status_callback(void * context, const char * message)
{
    psflib_context *p = (psflib_context*)context;
}*/
static bool LoadLib(psflib_context *context, IKpiFolder *pFolder, IKpiFile *pFile)
{
    psf_file_callbacks callbacks =
    {
        "/",   //path_separators
        fopen_IKpiFile,
        fread_IKpiFile,
        fseek_IKpiFile,
        fclose_IKpiFile,
        ftell_IKpiFile
    };
    char uri[64];
    context_to_uri(uri, _countof(uri), context);
    int ret = psf_load(uri, &callbacks, '!', load_callback, context,
        info_callback, context, 1,
        NULL, NULL);//status_callback, context);
    return ret >= 0;
}
static DWORD to_ms(const char* p)
{//tagget.h より
    int f = 0;
    unsigned long b = 0;
    unsigned long r = 0;
    for (; *p; p++)
    {
        if (*p >= '0' && *p <= '9')
        {
            if (f < 1000)
            {
                r = r * 10 + *p - '0';
                if (f) f *= 10;
                continue;
            }
            break;
        }
        if (*p == '.')
        {
            f = 1;
            continue;
        }
        if (*p == ':')
        {
            b = (b + r) * 60;
            r = 0;
            continue;
        }
        break;
    }
    if (f < 10)
        r *= 1000;
    else if (f == 10)
        r *= 100;
    else if (f == 100)
        r *= 10;
    r += b * 1000;
    return r;
}
///////////////////////////////////////////////////////////////////////////
//KbUsfDecoder
///////////////////////////////////////////////////////////////////////////
void __fastcall KbUsfDecoder::Init(IKpiConfig *pConfig)
{
}
///////////////////////////////////////////////////////////////////////////
void __fastcall KbUsfDecoder::Deinit(void)
{
}
///////////////////////////////////////////////////////////////////////////
KbUsfDecoder::KbUsfDecoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_state = NULL;
    kbusf_GetSettings(pConfig, &m_Settings);
    m_qwCurSample = 0;
    m_qwEndSample = -1;
    m_volume = 1.0;
}
///////////////////////////////////////////////////////////////////////////
KbUsfDecoder::~KbUsfDecoder(void)
{
    if(m_state){
        usf_shutdown(m_state);
        free(m_state);
    }
    m_qwCurSample = -1;
}
///////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbUsfDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                    IKpiFile *pFile, 
                                    IKpiFolder *pFolder)
{
    int state_size = usf_get_state_size();
    m_state = malloc(state_size);
    usf_clear(m_state);
    psflib_context context =
    {
        pFolder,
        pFile,  
        m_state,
        "",//length
        "",//fade
        "",//volume
        false, //utf8
    };
    if (!LoadLib(&context, pFolder, pFile)) {
        return 0;
    }
    int  length_in_ms = to_ms(context.length);
    int  fade_in_ms = to_ms(context.fade);
    bool haslength = context.length[0] != 0;
    bool hasfade = context.fade[0] != 0;
    if(!hasfade){
        fade_in_ms = -1;
    }

    usf_set_hle_audio(m_state, m_Settings.bEnableHLE);
    int sample_rate = 0;
    const char *ret_str = usf_render(m_state,
        NULL, 0, &sample_rate);
    if(ret_str){
        return 0;
    }
    int nSampleRate = m_Settings.nSampleRate;
    if (nSampleRate < 0) {
        nSampleRate *= -1;
    }
    else if (cpRequest) {
        if (cpRequest->dwSampleRate != 0) {
            nSampleRate = cpRequest->dwSampleRate;
        }
    }
    if (nSampleRate < 8000) {
        nSampleRate = 8000;
    }
    else if (nSampleRate > 192000) {
        nSampleRate = 192000;
    }
    if(haslength && length_in_ms > 0){
        m_MediaInfo.qwLength = (UINT64)length_in_ms * 10000;
    }
    else{
        m_MediaInfo.qwLength = -1;
    }
    m_MediaInfo.qwLoop = -1;
    if(fade_in_ms == 0){
        m_MediaInfo.qwFadeOut = 0;
        if(m_MediaInfo.qwLength != (UINT64)-1){
            m_MediaInfo.qwLoop = 0;
            m_qwEndSample = kpi_100nsToSample(m_MediaInfo.qwLength, nSampleRate);
        }
    }
    else if(fade_in_ms > 0){
        m_MediaInfo.qwFadeOut =(UINT64)fade_in_ms * 10000;
    }
    else{
        m_MediaInfo.qwFadeOut = -1;
    }
    m_volume = m_Settings.dVolume;
    if (!m_Settings.bIgnoreVolumeTag && context.volume[0]) {
        double vol = atof(context.volume);
        if (vol != 0.0) {
            m_volume *= vol;
        }
    }
    m_MediaInfo.dwSampleRate = nSampleRate;
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.nBitsPerSample = 16;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwCount = 1;
    m_qwCurSample = 0;
    if(m_volume != 1.0){
        m_MediaInfo.nBitsPerSample = -64;
    }
	return m_MediaInfo.dwCount;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbUsfDecoder::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 KbUsfDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(qwPosSample < m_qwCurSample){
        usf_restart(m_state);
        const char *ret_str = usf_render(m_state,
            NULL, 0, NULL);
        if(ret_str){//失敗
            m_qwCurSample = -1;
        }
        else{
            m_qwCurSample = 0;
        }
        return 0;
    }
    return qwPosSample;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbUsfDecoder::Render(BYTE *pBuffer, DWORD dwSizeSample)
{
    if(m_qwCurSample == (UINT64)-1){
        return 0;
    }
    if(m_qwCurSample + dwSizeSample > m_qwEndSample){
        dwSizeSample = m_qwEndSample - m_qwCurSample;
    }
    int sample_rate = m_MediaInfo.dwSampleRate;
    const char *ret_str = NULL;
    if(m_MediaInfo.nBitsPerSample == 16){
        ret_str = usf_render_resampled(m_state,
            (int16_t*)pBuffer, dwSizeSample, sample_rate);
    }
    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;
            }
            ret_str = usf_render_resampled(m_state,
                temp, sample, sample_rate);
            if(ret_str){
                break;
            }
            for (int i = 0; i < sample * 2; i++) {
                dest[i] = temp[i] * k;
            }
            dest += sample * 2;
            ret += sample;
        }
    }
    if(ret_str){
        m_qwCurSample = (UINT64)-1;
        return 0;
    }
    m_qwCurSample += dwSizeSample;
    return dwSizeSample;
}
///////////////////////////////////////////////////////////////////////////