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

struct vfs68_IKpiFile
{
    typedef const char * (*vfs68_name_t) (vfs68_t *);
    typedef int(*vfs68_open_t)    (vfs68_t *);
    typedef int(*vfs68_close_t)   (vfs68_t *);
    typedef int(*vfs68_length_t)  (vfs68_t *);
    typedef int(*vfs68_flush_t)   (vfs68_t *);
    typedef int(*vfs68_tell_t)    (vfs68_t *);
    typedef int(*vfs68_seek_t)    (vfs68_t *, int);
    typedef int(*vfs68_read_t)    (vfs68_t *, void *, int);
    typedef int(*vfs68_write_t)   (vfs68_t *, const void *, int);
    typedef void(*vfs68_destroy_t) (vfs68_t *);
    vfs68_name_t    name;    /**< Get stream name.        */
    vfs68_open_t    open;    /**< Open stream.            */
    vfs68_close_t   close;   /**< Close stream.           */
    vfs68_read_t    read;    /**< Read data from stream.  */
    vfs68_write_t   write;   /**< Write data to stream.   */
    vfs68_flush_t   flush;   /**< Flush data.             */
    vfs68_length_t  length;  /**< Get stream data length. */
    vfs68_tell_t    tell;    /**< Get stream position.    */
    vfs68_seek_t    seekf;   /**< Seek forward.           */
    vfs68_seek_t    seekb;   /**< Seek backward.          */
    vfs68_destroy_t destroy; /**< Destructor.             */
    static const char *vfs68_name(vfs68_t *vfs)
    {//呼ばれるが結果が使われない？
        /*vfs68_IKpiFile *vfs_IKpiFile = (vfs68_IKpiFile*)vfs;
        IKpiFile *file = vfs_IKpiFile->file;
        if (!vfs_IKpiFile->filename[0]) {
            wchar_t filename[MAX_PATH];
            if (file->GetFileName(filename, sizeof(filename)) <= sizeof(filename)) {
                WideCharToMultiByte(CP_ACP, 0, filename, -1,
                    vfs_IKpiFile->filename, _countof(vfs_IKpiFile->filename), NULL, NULL);
            }
        }
        return vfs_IKpiFile->filename;
        */
        return NULL;//NULL でも問題ない
    }
    static int vfs68_open(vfs68_t *vfs)
    {//呼ばれない
        return 0;
    }
    static int vfs68_close(vfs68_t *vfs)
    {//呼ばれない
        return 0;
    }
    static int vfs68_length(vfs68_t *vfs)
    {//呼ばれない
        IKpiFile *file = ((vfs68_IKpiFile*)vfs)->file;
        return file->GetSize();
    }
    static int vfs68_flush(vfs68_t *vfs)
    {//呼ばれない
        return 0;
    }
    static int vfs68_tell(vfs68_t *vfs)
    {//gz のとき呼ばれる
        IKpiFile *file = ((vfs68_IKpiFile*)vfs)->file;
        return file->Seek(0, FILE_CURRENT);
    }
    static int vfs68_seek(vfs68_t *vfs, int offset)
    {//gz のとき呼ばれる
        IKpiFile *file = ((vfs68_IKpiFile*)vfs)->file;
        if (file->Seek(offset, FILE_CURRENT) == KPI_FILE_EOF) {
            return -1;
        }
        return 0;
    }
    static int vfs68_read(vfs68_t *vfs, void *data, int size)
    {
        IKpiFile *file = ((vfs68_IKpiFile*)vfs)->file;
        return file->Read(data, size);
    }
    static int vfs68_write(vfs68_t *, const void *, int)
    {//呼ばれない
        return 0;
    }
    static void vfs68_destroy(vfs68_t *)
    {//呼ばれない
    }
    IKpiFile *file;
    //char filename[MAX_PATH*2];//使われない
    vfs68_IKpiFile(IKpiFile *pFile);
};

vfs68_IKpiFile::vfs68_IKpiFile(IKpiFile *pFile)
{
    name = vfs68_name;    /**< Get stream name.        */
    open = vfs68_open;    /**< Open stream.            */
    close = vfs68_close;  /**< Close stream.           */
    read = vfs68_read;    /**< Read data from stream.  */
    write = vfs68_write;  /**< Write data to stream.   */
    flush = vfs68_flush;  /**< Flush data.             */
    length = vfs68_length;/**< Get stream data length. */
    tell = vfs68_tell;    /**< Get stream position.    */
    seekf = vfs68_seek;   /**< Seek forward.           */
    seekb = vfs68_seek;   /**< Seek backward.          */
    destroy = vfs68_destroy;/**< Destructor.             */
    file = pFile;
    //filename[0] = 0;
}
//////////////////////////////////////////////////////////////////////////
LONG KbSc68Decoder::s_lInitialized = 0;
//////////////////////////////////////////////////////////////////////////
KbSc68Decoder::KbSc68Decoder(IKpiConfig *pConfig)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_nStatus = 2;
    m_sc68 = NULL;
    m_disk68 = NULL;
    kbsc68_GetSettings(pConfig, &m_Settings);
}
//////////////////////////////////////////////////////////////////////////
KbSc68Decoder::~KbSc68Decoder(void)
{
    if (m_sc68) {
        sc68_close(m_sc68);
        sc68_destroy(m_sc68);
    }
    if (m_disk68) {
        file68_free(m_disk68);
    }
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbSc68Decoder::Open(const KPI_MEDIAINFO *cpRequest,
                                        IKpiFile   *pFile,
                                        IKpiFolder *pFolder)
{
    sc68_create_t create = {0};
    if(m_Settings.nSampleRate < 0){//本体からの要求を無視
        create.sampling_rate = -m_Settings.nSampleRate;
    }
    else if(cpRequest){
        if(cpRequest->dwSampleRate == 0){
            create.sampling_rate = m_Settings.nSampleRate;
        }
        else{
            create.sampling_rate = cpRequest->dwSampleRate;
        }
    }
    else{
        create.sampling_rate = 44100;
    }
    if(pFile->GetSize() > 0x7FFFFFFF){//サイズが大きすぎる
        return 0;
    }
    vfs68_IKpiFile vfs(pFile);
    disk68_t *disk68 = file68_load((vfs68_t*)&vfs);
    sc68_t *sc68 = NULL;
    do{
        if(!disk68){
            break;
        }
        sc68 = sc68_create(&create);
        if(!sc68){
            break;
        }
        if(sc68_open(sc68, disk68)){
            break;
        }
        int spr = sc68_cntl(sc68, SC68_GET_SPR);
        if (spr <= 0){
            break;
        }
        sc68_cntl(sc68, SC68_SET_ASID, m_Settings.nASID);//インスタンスごとの設定が可能
        m_MediaInfo.dwSampleRate = spr;
        m_MediaInfo.dwChannels = 2;
        m_MediaInfo.nBitsPerSample = 16;
        m_MediaInfo.qwLength = 0;
        m_MediaInfo.dwSeekableFlags = 0;
        m_MediaInfo.dwUnitSample = 0;
        m_MediaInfo.dwNumber = 1;
        m_MediaInfo.dwCount = disk68->nb_mus;
        m_sc68 = sc68;
        m_disk68 = disk68;
        //pFile->AddRef();//file68_load から戻った後 pFile に
        //m_pFile = pFile;//アクセスすることはないので不要
        return m_MediaInfo.dwCount;
    }while(0);
    if(sc68){
        sc68_close(sc68);
        sc68_destroy(sc68);
    }
    if(disk68){
        file68_free(disk68);
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSc68Decoder::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 (!ppMediaInfo) {
        //タイトル/アーティスト取得のみ
        GetTagInfo(pTagInfo, dwTagGetFlags);
        return dwNumber;
    }
    if(dwNumber == 0){
        return 0;
    }
    *ppMediaInfo = &m_MediaInfo;
    music68_t *mus = &m_disk68->mus[dwNumber-1];
    //int has_loop = mus->has.loop;//全て0?
    int first_fr = mus->first_fr;//イントロ+ループ
    //int loop_fr = mus->loops_fr; //ループ(必ず first_fr==loop_fr になってしまう)
    UINT64 qwLength;
    UINT64 qwLoop;
    UINT64 qwFade;
    //ループしない曲の判別が出来そうにないので
    //全ての曲をループとして扱う
    qwLength = (UINT64)first_fr * 1000 * 10000 / mus->frq;
    /*
    if(has_loop){//存在しない(ループしてもしなくても0)
        qwLoop = (UINT64)loop_fr * 1000 * 10000 / mus->frq;
    }
    else{
        qwLoop = -1;
    }*/
    qwLoop = -1;
    qwFade = -1;
    if (sc68_play(m_sc68, dwNumber, 1/*SC68_INF_LOOP*/) < 0) {
        m_nStatus = 2;
        return 0;
    }
    int code = sc68_process(m_sc68, 0, 0);
    if(code == SC68_ERROR){
        m_nStatus = 2;
        return 0;
    }
    GetTagInfo(pTagInfo, dwTagGetFlags);
    m_nStatus = 1;
    m_MediaInfo.qwLength = qwLength;
    m_MediaInfo.qwLoop = qwLoop;
    m_MediaInfo.qwFadeOut = qwFade;
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbSc68Decoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if (qwPosSample == 0 && !m_nStatus){
        return 0;//まだ再生していない
    }
    //開き直す
    sc68_close(m_sc68);
    if(sc68_open(m_sc68, m_disk68)){//ファイルオープンに失敗
        m_nStatus = 2;//終了したことにする
        return 0;
    }
    if (sc68_play(m_sc68, m_MediaInfo.dwNumber, SC68_INF_LOOP) < 0) {
        m_nStatus = 2;
        return 0;
    }
    int code = sc68_process(m_sc68, 0, 0);
    if (code == SC68_ERROR) {
        m_nStatus = 2;
        return 0;
    }
    m_nStatus = 0;
    return 0;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSc68Decoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    if(m_nStatus == 2){
    //終了状態で sc68_process を呼ぶと落ちる
        return 0;
    }
    m_nStatus = 1;
    int nRet = dwSizeSample;
    int code = sc68_process(m_sc68, pBuffer, &nRet);
    if(code != 0){
        if(nRet < dwSizeSample){
        //sc68_play() の第3引数に SC68_INF_LOOP を渡す限り実行されることはないようだが念のため
            m_nStatus = 2;
        }
    }
    return nRet;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSc68Decoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo){
        return;
    }
    //本体のタグ取得ルーチンを使わない
    pTagInfo->GetTagInfo(NULL, NULL, KPI_TAGTYPE_NONE, 0);

    const tagset68_t *tagset;
    const DWORD dwNumber = m_MediaInfo.dwNumber;
    int i;
    if(dwNumber == 0){
        tagset = &m_disk68->tags;
    }
    else{
        tagset = &m_disk68->mus[m_MediaInfo.dwNumber-1].tags;
    }
    for(i = 0; i < TAG68_ID_MAX; i++){
        const char *key = tagset->array[i].key;
        const char *val = tagset->array[i].val;
        if(i == 0){
            if(dwTagGetFlags & KPI_TAGGET_FLAG_TITLE){
                pTagInfo->wSetValueA(L"title", -1, val, -1);
            }
        }
        else if (i == 1){
            if(dwTagGetFlags & KPI_TAGGET_FLAG_ARTIST) {
                pTagInfo->wSetValueA(L"artist", -1, val, -1);
            }
        }
        else if (i == 2){
            if(dwTagGetFlags & KPI_TAGGET_FLAG_GENRE) {
                pTagInfo->wSetValueA(L"genre", -1, val, -1);
            }
        }
        else if((dwTagGetFlags & KPI_TAGGET_FLAG_GENERAL) ||
                (dwTagGetFlags & KPI_TAGGET_FLAG_ID3)){
            if(key && val){
                pTagInfo->aSetValueA(key, -1, val, -1);
            }
        }
    }
    if(dwNumber > 0){//album
        tagset = &m_disk68->tags;
        if(dwTagGetFlags&KPI_TAGGET_FLAG_ALBUM){
            if(tagset->tag.title.val){
                pTagInfo->wSetValueA(L"album", -1, tagset->tag.title.val, -1);
            }
        }
        if (dwTagGetFlags&KPI_TAGGET_FLAG_GENERAL) {
            if (tagset->tag.genre.val) {
                pTagInfo->wSetValueA(L"format", -1, tagset->tag.genre.val, -1);
            }
            sc68_music_info_t mi = { 0 };
            if (sc68_music_info(m_sc68, &mi, SC68_CUR_TRACK, NULL)) {
                return;
            }
            wchar_t info[512];
            _snwprintf_s(info, _TRUNCATE, 
                         L"tracks=%d\r\n"
                         L"Load address=%d\r\n"
                         L"Replay rate=%d\r\n"
                         L"Replay name=%S\r\n"
                         L"<Disk Info>\r\n"
                         L"time_ms=%d\r\n"  //sc68_play() の第3引数にSC68_INF_LOOPを指定すると常に0になる
                         L"time=%S\r\n"
                         L"<Track Info>\r\n"
                         L"track=%d\r\n"
                         L"time_ms=%d\r\n"  //sc68_play() の第3引数にSC68_INF_LOOPを指定すると常に0になる
                         L"time=%S\r\n"
                         L"aSid=%d\r\n"
                         L"Hardware=%S\r\n"
                         ,
                         mi.tracks, mi.addr, mi.rate, mi.replay,
                         mi.dsk.time_ms, mi.dsk.time, 
                         mi.trk.track, mi.trk.time_ms, mi.trk.time,
                         mi.trk.asid != 0, mi.trk.hw);
            
            pTagInfo->wSetValueW(L"Info", -1, info, -1);
        }
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSc68Decoder::Initialize(IKpiConfig *pConfig)
{
    if(InterlockedIncrement(&s_lInitialized) != 1){
        return;
    }
    //sc68 の一部の設定はグローバル変数内に持ち、インスタンスごとの設定は
    //うまく行かないことが多い為、プラグインロード後に一度だけ設定する
    //(設定を変更した場合はプラグインを再ロードする)
    kbsc68_Settings settings;
    kbsc68_GetSettings(pConfig, &settings);

    sc68_init_t init68 = { 0 };
    init68.flags.no_load_config = 1;
    init68.flags.no_save_config = 1;
    char ym_engine[32];
    char ym_filter[32];
    char ym_volmode[32];
    char amiga_blend[32];
    char amiga_filter[32];
    char amiga_clock[32];
    _snprintf_s(ym_engine, _TRUNCATE, "--ym-engine=%d", settings.nYMEngine);
    _snprintf_s(ym_filter, _TRUNCATE, "--ym-filter=%d", settings.nYMFilter);
    _snprintf_s(ym_volmode, _TRUNCATE, "--ym-volmodel=%d", settings.nYMVolModel);
    _snprintf_s(amiga_blend, _TRUNCATE, "--amiga-blend=%d", settings.nAmigaBlend);
    _snprintf_s(amiga_filter, _TRUNCATE, "--amiga-filter=%d", settings.nAmigaFilter);
    _snprintf_s(amiga_clock, _TRUNCATE, "--amiga-clock=%d", settings.nAmigaClock);

    char *argv[] = { "kbsc68.kpi", ym_engine, ym_filter, ym_volmode, amiga_blend, amiga_filter, amiga_clock};
    init68.argc = _countof(argv);
    init68.argv = argv;
    sc68_init(&init68);

    //sc68_cntl(NULL, SC68_SET_ASID, settings.nASID);//インスタンスごとの設定が可能
    //ここから下はインスタンスごと(第一引数を非NULL)の設定が不可
    //(内部でグローバル変数を使う)
    //sc68_cntl(NULL, SC68_SET_OPT_INT, "ym-engine", settings.nYMEngine);    //ここでも反映される
    //sc68_cntl(NULL, SC68_SET_OPT_INT, "ym-filter", settings.nYMFilter);    //ここでも反映される
    //sc68_cntl(NULL, SC68_SET_OPT_INT, "ym-volmodel", settings.nYMVolModel);//ここでは反映されない
    //sc68_cntl(NULL, SC68_SET_OPT_INT, "amiga-blend", settings.nAmigaBlend);//ここでも反映される
    //sc68_cntl(NULL, SC68_SET_OPT_INT, "amiga-filter", settings.nAmigaFilter);//ここでも反映される

}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSc68Decoder::Uninitialize(void)
{
    if(InterlockedDecrement(&s_lInitialized) == 0){
        sc68_shutdown();
    }
}
//////////////////////////////////////////////////////////////////////////
