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

#include "kbsap_decoder.h"
#include "libasap\asap\astil.h"

extern "C"{
    //bool ASTIL_Load(ASTIL *self, const char *filename, int song, FILE *fp);
    bool ASTIL_Load_frombuf(ASTIL *self, const char *filename, int song, const unsigned char *buf, size_t bufsize);//astil.c
};
#define ASTIL_MAX_TEXT_LENGTH  100     //astil.c
#define ASTIL_MAX_COMMENT_LENGTH  1000 //astil.c
static void __fastcall GetSTILInfo(IKpiTagInfo *pTagInfo, IKpiFile *pFile, IKpiFolder *pFolder, DWORD dwNumber);
static void __fastcall GetSTILValue(wchar_t *pszValue, int nSize, const char* cszValue, bool isUTF8);
//////////////////////////////////////////////////////////////////////////
KbSapDecoder::KbSapDecoder(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pAsap = NULL;
    m_pFile = NULL;
    m_pFolder = NULL;
    m_pBuffer = NULL;
    m_dwSize = 0;
}
//////////////////////////////////////////////////////////////////////////
KbSapDecoder::~KbSapDecoder(void)
{
    if(m_pAsap){
        ASAP_Delete(m_pAsap);
    }
    if(m_pFile){
        m_pFile->Release();
    }
    if(m_pFolder){
        m_pFolder->Release();
    }
}
//////////////////////////////////////////////////////////////////////////
DWORD __fastcall KbSapDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                    IKpiFile *pFile,
                                    IKpiFolder *pFolder)
{
    //ファイル名(フルパス不要)を渡さないと開くことが出来ないことがある
    //(データによっては内容からの自動判別が出来ない？)
    if(pFile->GetSize() > ASAPInfo_MAX_MODULE_LENGTH){
        return 0;
    }
    const BYTE *pBuffer;
    size_t stSize;
    if(!pFile->GetBuffer(&pBuffer, &stSize)){
        return 0;
    }
    wchar_t szFileName[MAX_PATH];
    pFile->GetFileName(szFileName, sizeof(szFileName));
    //libasap が拡張子検索にマルチバイトを考慮していないので念の為 utf8 に変換
    int utf8_len = WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, NULL, 0, NULL, NULL);
    char *filename_u8 = (char*)malloc(utf8_len + 1);
    WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, filename_u8, utf8_len, NULL, NULL);
    filename_u8[utf8_len] = 0;
    const char *filename = filename_u8[0] ? filename_u8 : NULL;
    ASAP *pAsap = ASAP_New();
    if(!ASAP_Load(pAsap, filename, pBuffer, stSize)){
        free(filename_u8);
        ASAP_Delete(pAsap);
        return 0;
    }
    free(filename_u8);
    const ASAPInfo* pInfo = ASAP_GetInfo(pAsap);
    m_MediaInfo.dwSampleRate = ASAP_SAMPLE_RATE;
    m_MediaInfo.dwChannels = ASAPInfo_GetChannels(pInfo);
    m_MediaInfo.nBitsPerSample = 16;
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_FLAGS_SAMPLE;
    m_MediaInfo.qwLength = -1;
    m_MediaInfo.qwLoop = -1;
    m_MediaInfo.qwFadeOut = -1;
    m_MediaInfo.dwCount = ASAPInfo_GetSongs(pInfo);
    m_pAsap = pAsap;
    m_pFile = pFile;
    pFile->AddRef();
    m_pBuffer = pBuffer;
    m_dwSize = stSize;
    m_pFolder = pFolder;
    pFolder->AddRef();
    return m_MediaInfo.dwCount;
}
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSapDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
    //番号指定が不正
        return 0;
    }
    m_MediaInfo.dwNumber = dwNumber;
    if (!ppMediaInfo) {
    //タイトル/アーティスト取得のみ
        GetTagInfo(pTagInfo, dwTagGetFlags);
        return dwNumber;
    }
    *ppMediaInfo = &m_MediaInfo;
    dwNumber--;//0ベースに変換
    const ASAPInfo* pInfo = ASAP_GetInfo(m_pAsap);
    int nDuration = ASAPInfo_GetDuration(pInfo, dwNumber);
    if(nDuration < 0){
        m_MediaInfo.qwLength = -1;
        m_MediaInfo.qwLoop = -1;
        m_MediaInfo.qwFadeOut = -1;
        if(!ASAP_PlaySong(m_pAsap, dwNumber, -1)){
            return 0;
        }
    }
    else{
        m_MediaInfo.qwLength = kpi_MsTo100ns(nDuration);
        if(ASAPInfo_GetLoop(pInfo, dwNumber)){
            m_MediaInfo.qwLoop = -1;
            m_MediaInfo.qwFadeOut = -1;
            if(!ASAP_PlaySong(m_pAsap, dwNumber, -1)){
                return 0;//m_MediaInfo.dwNumber;
            }
        }
        else{
            m_MediaInfo.qwLoop = 0;
            m_MediaInfo.qwFadeOut = 0;
            if(!ASAP_PlaySong(m_pAsap, dwNumber, nDuration)){
                return 0;//m_MediaInfo.dwNumber;
            }
        }
    }
    GetTagInfo(pTagInfo, dwTagGetFlags);
    return m_MediaInfo.dwNumber;
}
//////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbSapDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    if(ASAP_SeekSample(m_pAsap, qwPosSample)){
        return qwPosSample;
    }
    ASAP_SeekSample(m_pAsap, 0);
    return 0;
 }
//////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbSapDecoder::Render(BYTE* pBuffer, DWORD dwSizeSample)
{
    DWORD dwBlockAlign = m_MediaInfo.dwChannels * 2;
    DWORD dwSizeBytes = dwSizeSample * dwBlockAlign;
    DWORD dwRetBytes = ASAP_Generate(m_pAsap, pBuffer, dwSizeBytes, ASAPSampleFormat_S16_L_E);
    return dwRetBytes / dwBlockAlign;
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbSapDecoder::GetTagInfo(IKpiTagInfo *pTagInfo, DWORD dwTagGetFlags)
{
    if(!pTagInfo)return;
    const ASAPInfo* pInfo = ASAP_GetInfo(m_pAsap);
    pTagInfo->GetTagInfo(NULL, NULL, KPI_TAGTYPE_NONE, 0);
    if(dwTagGetFlags&KPI_TAGGET_FLAG_TITLE){
        const char *cszTitle = ASAPInfo_GetTitle(pInfo);
        pTagInfo->wSetValueA(L"Title", -1, cszTitle, -1);
    }
    if(dwTagGetFlags&KPI_TAGGET_FLAG_ARTIST){
        const char *cszArtist = ASAPInfo_GetAuthor(pInfo);
        pTagInfo->wSetValueA(L"Artist", -1, cszArtist, -1);
    }
    if(dwTagGetFlags&KPI_TAGGET_FLAG_DATE){
        const char *cszDate = ASAPInfo_GetDate(pInfo);
        pTagInfo->wSetValueA(L"Date", -1, cszDate, -1);
    }
    if(!(dwTagGetFlags&KPI_TAGGET_FLAG_GENERAL)){
        return;
    }
    wchar_t composed[128];
    wchar_t songs[32];
    wchar_t defsong[32];
    wchar_t type[32];
    wchar_t channel[16];
    wchar_t ntsc[8];
    wchar_t fastplay[32];
    wchar_t music[32];
    wchar_t init[32];
    wchar_t player[32];
    wchar_t covox[32];

    const char* ext = ASAPInfo_GetOriginalModuleExt(pInfo, m_pBuffer, m_dwSize);
    if(ext){
        _snwprintf_s(composed, _TRUNCATE, L"Composed in %S\r\n", ASAPInfo_GetExtDescription(ext));
    }
    else{
        composed[0] = 0;
    }
    _snwprintf_s(songs, _TRUNCATE, L"SONGS %d\r\n", ASAPInfo_GetSongs(pInfo));
    _snwprintf_s(defsong, _TRUNCATE, L"DEFSONG %d\r\n", ASAPInfo_GetDefaultSong(pInfo));
    wcsncpy_s(channel, ASAPInfo_GetChannels(pInfo) > 1 ? L"STEREO\r\n" : L"MONO\r\n", _TRUNCATE);
    wcsncpy_s(ntsc, ASAPInfo_IsNtsc(pInfo) ? L"NTSC\r\n" : L"PAL\r\n", _TRUNCATE);
    int typeletter = ASAPInfo_GetTypeLetter(pInfo);
    if(typeletter){
        _snwprintf_s(type, _TRUNCATE, L"TYPE %c\r\n", typeletter);
    }
    else{
        type[0] = 0;
    }
    _snwprintf_s(fastplay, _TRUNCATE, L"FASTPLAY %d (%d Hz)\r\n",
                    ASAPInfo_GetPlayerRateScanlines(pInfo), ASAPInfo_GetPlayerRateHz(pInfo));
    if(typeletter == 'C'){
        _snwprintf_s(music, _TRUNCATE, L"MUSIC %04X\r\n", ASAPInfo_GetMusicAddress(pInfo));
    }
    else{
        music[0] = 0;
    }
    if(typeletter){
        int int_addr = ASAPInfo_GetInitAddress(pInfo);
        if(int_addr >= 0){
            _snwprintf_s(init, _TRUNCATE, L"INIT %04X\r\n", int_addr);
        }
        else{
            init[0] = 0;
        }
        int player_addr = ASAPInfo_GetPlayerAddress(pInfo);
        if(player_addr >= 0){
            _snwprintf_s(player, _TRUNCATE, L"PLAYER %04X\r\n", player_addr);
        }
        else{
            player[0] = 0;
        }
        int covox_addr = ASAPInfo_GetCovoxAddress(pInfo);
        if(covox_addr >= 0){
            _snwprintf_s(covox, _TRUNCATE, L"COVOX %04X\r\n", covox_addr);
        }
        else{
            covox[0] = 0;
        }
    }
    else{
        init[0] = player[0] = covox[0] = 0;
    }
    wchar_t szExtra[1024];

    wcsncpy_s(szExtra, composed, _TRUNCATE);
    wcsncat_s(szExtra, songs, _TRUNCATE);
    wcsncat_s(szExtra, defsong, _TRUNCATE);
    wcsncat_s(szExtra, type, _TRUNCATE);
    wcsncat_s(szExtra, channel, _TRUNCATE);
    wcsncat_s(szExtra, ntsc, _TRUNCATE);
    wcsncat_s(szExtra, fastplay, _TRUNCATE);
    wcsncat_s(szExtra, music, _TRUNCATE);
    wcsncat_s(szExtra, init, _TRUNCATE);
    wcsncat_s(szExtra, player, _TRUNCATE);
    wcsncat_s(szExtra, covox, _TRUNCATE);

    int i = ASAPInfo_GetSapHeaderLength(pInfo);
    if (i >= 0) {
        wchar_t load[128];
        while ( i + 4 < m_dwSize) {
            int start = m_pBuffer[i] + (m_pBuffer[i + 1] << 8);
            int end;
            if (start == 0xffff) {
                i += 2;
                start = m_pBuffer[i] + (m_pBuffer[i + 1] << 8);
            }
            end = m_pBuffer[i + 2] + (m_pBuffer[i + 3] << 8);
            _snwprintf_s(load, _TRUNCATE, L"LOAD %04X-%04X\r\n", start, end);
            wcsncat_s(szExtra, load, _TRUNCATE);
            i += 5 + end - start;
        }
    }
    pTagInfo->wSetValueW(L"SAPInfo", -1, szExtra, -1);
    GetSTILInfo(pTagInfo, m_pFile, m_pFolder, m_MediaInfo.dwNumber);
}
//////////////////////////////////////////////////////////////////////////
void __fastcall GetSTILInfo(IKpiTagInfo *pTagInfo, IKpiFile *pSapFile, IKpiFolder *pSapFolder, DWORD dwNumber)
{//STIL.txt
    dwNumber--;//0 ベースに変換
    IKpiFolder *pCurFolder = pSapFolder;
    pCurFolder->AddRef();
    wchar_t szSTILBasePath[MAX_PATH];
    wchar_t szTemp[MAX_PATH];
    pSapFolder->GetFolderName(szSTILBasePath+1, sizeof(szSTILBasePath)-sizeof(wchar_t));
    szSTILBasePath[0] = L'/';
    while(1){
        IKpiFolder *pFolder = NULL;
        pCurFolder->OpenFolder(L"../", &pFolder);
        if(!pFolder){
            pCurFolder->Release();
            break;
        }
        IKpiFile *pFile = NULL;
        if(pFolder->OpenFile(L"Docs/STIL.txt", &pFile)){
            wchar_t szSTILFileName[MAX_PATH];
            pFolder->GetFolderName(szSTILFileName+1, sizeof(szSTILFileName)-sizeof(wchar_t));
            szSTILFileName[0] = L'/';
            wcsncat_s(szSTILFileName, L"/Docs/STIL.txt", _TRUNCATE);
            const BYTE *buf;
            size_t bufsize;
            if(pFile->GetBuffer(&buf, &bufsize)){
                ASTIL* pAstil = ASTIL_New();
                pSapFile->GetFileName(szTemp+1, sizeof(szTemp)-sizeof(wchar_t));
                szTemp[0] = L'/';
                wcsncat_s(szSTILBasePath, szTemp, _TRUNCATE);
                int utf8_len = WideCharToMultiByte(CP_UTF8, 0, szSTILBasePath, -1, NULL, 0, NULL, NULL);
                char *path_u8 = (char*)malloc(utf8_len + 1);
                WideCharToMultiByte(CP_UTF8, 0, szSTILBasePath, -1, path_u8, utf8_len, NULL, NULL);
                path_u8[utf8_len] = 0;
                ASTIL_Load_frombuf(pAstil, path_u8, dwNumber, buf, bufsize);
                free(path_u8);
                //cover
                wchar_t covers[4096];
                wchar_t titleAndSource[ASTIL_MAX_TEXT_LENGTH + 1];
                wchar_t artist[ASTIL_MAX_TEXT_LENGTH + 1];
                wchar_t comment[ASTIL_MAX_COMMENT_LENGTH + 1];
                //stil
                wchar_t title[ASTIL_MAX_TEXT_LENGTH + 1];
                wchar_t author[ASTIL_MAX_TEXT_LENGTH + 1];
                wchar_t directoryComment[ASTIL_MAX_COMMENT_LENGTH + 1];
                wchar_t fileComment[ASTIL_MAX_COMMENT_LENGTH + 1];
                wchar_t songComment[ASTIL_MAX_COMMENT_LENGTH + 1];
                bool isUTF8 = ASTIL_IsUTF8(pAstil);
                //cover
                covers[0] = 0;
                for(int i = 0; ; i++){
                    const ASTILCover *pCover = ASTIL_GetCover(pAstil, i);
                    if(!pCover)break;
                    GetSTILValue(titleAndSource, _countof(titleAndSource), ASTILCover_GetTitleAndSource(pCover), isUTF8);
                    GetSTILValue(artist, _countof(artist), ASTILCover_GetArtist(pCover), isUTF8);
                    GetSTILValue(comment, _countof(comment), ASTILCover_GetComment(pCover), isUTF8);
                    wchar_t cover[1024];
                    _snwprintf_s(cover, _TRUNCATE,
                        L"<Cover(%d)>:\r\n"
                        L"Title: %s\r\n"
                        L"Artist: %s\r\n"
                        L"Comment: %s\r\n",
                        i, titleAndSource, artist, comment);
                    wcsncat_s(covers, cover, _TRUNCATE);
                }
                GetSTILValue(title, _countof(title), ASTIL_GetTitle(pAstil), isUTF8);
                GetSTILValue(author, _countof(author), ASTIL_GetAuthor(pAstil), isUTF8);
                GetSTILValue(directoryComment, _countof(directoryComment), ASTIL_GetDirectoryComment(pAstil), isUTF8);
                GetSTILValue(fileComment, _countof(fileComment), ASTIL_GetFileComment(pAstil), isUTF8);
                GetSTILValue(songComment, _countof(songComment), ASTIL_GetSongComment(pAstil), isUTF8);
                wchar_t szSTILInfo[4096];
                _snwprintf_s(szSTILInfo, _TRUNCATE,
                    L"STIL File: %s\r\n"
                    L"Title: %s\r\n"
                    L"Author: %s\r\n"
                    L"DirectoryComment: %s\r\n"
                    L"FileComment: %s\r\n"
                    L"SongComment: %s\r\n"
                    L"%s"
                    , szSTILFileName,
                    title, author, directoryComment, fileComment, songComment,
                    covers);
                pTagInfo->wSetValueW(L"STIL Info", -1, szSTILInfo, -1);
                ASTIL_Delete(pAstil);
            }
            pFile->Release();
            pFolder->Release();
            pCurFolder->Release();
            break;
        }
        wcsncpy_s(szTemp, szSTILBasePath, _TRUNCATE);
        pFolder->GetFolderName(szSTILBasePath+1, sizeof(szSTILBasePath)-sizeof(wchar_t));
        szSTILBasePath[0] = L'/';
        wcsncat_s(szSTILBasePath, szTemp, _TRUNCATE);
        pCurFolder->Release();
        pCurFolder = pFolder;
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall GetSTILValue(wchar_t *pszValue, int nSize, const char* cszValue, bool isUTF8)
{
    UINT uCodePage = isUTF8 ? CP_UTF8 : CP_ACP;
    MultiByteToWideChar(uCodePage, 0, cszValue, -1, pszValue, nSize);
    pszValue[nSize-1] = 0;
}

