#include <windows.h>
#include <cstdio>
#include <algorithm>
#include <process.h>

#include "CSMFPlay.hpp"
#include "COpllDevice.hpp"
#include "CSccDevice.hpp"
#include "CPSGDrum.hpp"

#if defined (_MSC_VER)
#if defined (_DEBUG)
#define new new( _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

using namespace dsa;

CSMFPlay::CSMFPlay(DWORD rate, bool bEnableOPLL, bool bEnableSCC, bool bEnablePSGDrum, int mods, int threads) {
    m_mods = mods;
    m_threads = threads;
    if(m_threads > m_mods / 2){
        m_threads = m_mods / 2;
    }
    m_bEnableOPLL = bEnableOPLL;
    m_bEnableSCC = bEnableSCC;
    m_bEnablePSGDrum = bEnablePSGDrum;
    m_rate = rate;
    m_port_num = 1;
    m_modules = NULL;
    m_dwCurSample = 0;
    m_dwEndSample = 0;
    m_dwSilentSample = 0;
    m_loop_point = -1;
    m_cur_port = 0;
    m_cur_idx = 0;
    ZeroMemory(&m_ThreadInfo, sizeof(m_ThreadInfo));
}

CSMFPlay::~CSMFPlay() {
    //for(int i=0;i<m_mods+m_bEnablePSGDrum;i++) delete m_module[i].DetachDevice();
    TerminateRenderThreads();
    if(m_modules){
        for(int port = 0; port < m_port_num; port++){
            for (int i = 0; i<m_mods + m_bEnablePSGDrum; i++){
                delete m_modules[port][i].DetachDevice();
            }
            delete[] m_modules[port];
        }
        free(m_modules);
    }
}
#if 0
bool CSMFPlay::Open(const char *filename) {

  FILE *fp;
  DWORD length;
  char *data;

  if((fp=fopen(filename,"rb"))==NULL) {
    // printf("Can't open %s\n",filename);
    return false;
  };

  fseek(fp,0L,SEEK_END);
  length = ftell(fp);
  fseek(fp,0L,SEEK_SET);
  data = new char [length];
  if(fread(data,sizeof(char),length,fp)!=length) {
    // printf("Fatal Error.\n");
    return false;
  };
  fclose(fp);

  if(!m_smf.Load(data,length)) { 
    // printf("Invalid SMF.\n"); 
    delete [] data;
    return false; 
  }

  /*
  printf("[MThd]\n");
  printf("MThd Length: %d\n", m_smf.m_MThd_length);
  printf("Format     : %2d\n", m_smf.m_format);
  printf("TrackNum   : %2d\n",   m_smf.m_track_num);
  printf("TimeBase   : %2d\n\n", m_smf.m_time_base); 
  for(int trk=0; trk<m_smf.m_track_num; trk++) {
    printf("MTrk[%03d]: %d bytes at %p\n",trk,m_smf.m_MTrk_length[trk],m_smf.m_MTrk_data[trk]);
  }
  */

  delete [] data;
  return true;
}
#endif
bool CSMFPlay::Open(const BYTE *data, DWORD data_length)
{
    if (!m_smf.Load((const char*)data, data_length)) {
        return false;
    }
    
    m_events.clear();
    m_loop_point = -1;
    m_dwCurSample = 0;
    m_dwEndSample = 0;
    m_dwSilentSample = 0;
    struct TEMPO{
        int    tempo;
        int    tick;
    };
    std::vector<TEMPO> tempo_table;
    TEMPO initial_tempo{500000, 0};

    tempo_table.push_back(initial_tempo);

    int port_num = 1;
    int trk;
    for (trk = 0; trk<m_smf.m_track_num; trk++) {
        m_smf.Seek(trk, 0);
        int tick = 0;
        try {
        while(1){
            CSMFEvent ev = m_smf.ReadNextEvent(trk);
            tick += ev.m_delta;
            ev.m_tick = tick;
            m_events.push_back(ev);
            if (ev.m_type == CSMFEvent::META_EVENT) {
                if (ev.m_status == 0xFF2F) { // ENDOFTRACK
                    break;
                }
                else if (ev.m_status == 0xFF21) {//port
                    int port = ev.m_data[0];
                    if (port >= MAX_PORTNUM) {
                        port = 0;
                    }
                    if (port >= port_num) {
                        port_num = port + 1;
                    }
                }
                else if (ev.m_status == 0xFF51) { // TEMPO
                    int tempo = (ev.m_data[0] << 16) | (ev.m_data[1] << 8) | (ev.m_data[2]);
                    for(int i = tempo_table.size() -1; i >= 0; i--){
                        if(tick == tempo_table[i].tick){//同一時刻にテンポが複数ある場合は後の方を優先
                            tempo_table[i].tempo = tempo;
                            break;
                        }
                        else if(tick > tempo_table[i].tick){
                            TEMPO tmp = {tempo, tick};
                            if(i == tempo_table.size() - 1){
                                tempo_table.push_back(tmp);
                            }
                            else{
                                tempo_table.insert(tempo_table.begin() + i + 1, tmp);
                            }
                            break;
                        }
                    }
                }
            }
        }
        }
        catch (CSMF_Exception) {
        }
    }
    if(m_smf.m_track_num > 1){
    //KbMedia Player から渡される SMF は必ず Format 0 なのでここが呼ばれることはないが
    //他のソフトから使用される可能性があるので
        struct event_sort
        {
            bool operator()(const CSMFEvent& l, const CSMFEvent& r) const
            {
                return (l.m_tick < r.m_tick);
            }
        };
        std::stable_sort(m_events.begin(), m_events.end(), event_sort());
    }
    const int event_num = m_events.size();
    const int tempo_num = tempo_table.size();
    const double inv_timebase = 1.0 / (m_smf.m_time_base * 1000000.0);
    int i, j;
    for(i = 0; i < event_num; i++){
        //各MIDIイベントの絶対時刻の計算
        int tick = m_events[i].m_tick;
        double time = 0;
        double add;
        for(j = 0; j < tempo_num; j++){
            if(tick == 0)break;
            int tick_diff;
            if(j == tempo_num - 1){
                tick_diff = tick;
            }
            else{
                tick_diff = tempo_table[j + 1].tick - tempo_table[j].tick;
            }
            if(tick < tick_diff){
                tick_diff = tick;
            }
            add = (double)tempo_table[j].tempo * tick_diff;
            tick -= tick_diff;
            time += add;
        }
        time *= inv_timebase;
        m_events[i].m_time_sec = time;
        //ついでにループ検出(CC111)
        if (m_loop_point < 0) {
            if ((m_events[i].m_type == CSMFEvent::MIDI_EVENT) &&
                ((m_events[i].m_status >> 4) == 0xB) && //ControlChange
                (m_events[i].m_data[0] == 0x6F)) {//loop(CC111)
                m_loop_point = i;
            }
        }
    }
    m_port_num = port_num;
    m_modules = (CMIDIModule**)malloc(sizeof(CMIDIModule*)*port_num);
    for (int port = 0; port < port_num; port++) {
        CMIDIModule *module = new CMIDIModule[m_mods + m_bEnablePSGDrum];
        m_modules[port] = module;
        for (int i = 0; i<m_mods; i++) {
            if (i & 1) {
                if (m_bEnableSCC) {
                    module[i].AttachDevice(new CSccDevice(m_rate, 2));
                }
            }
            else if (m_bEnableOPLL) {
                module[i].AttachDevice(new COpllDevice(m_rate, 2));
            }
        }
        if (m_bEnablePSGDrum)module[m_mods].AttachDevice(new CPSGDrum(m_rate, 2));
    }
    return true;
}
void CSMFPlay::Start() 
{
    //TerminateRenderThreads();
    for(int port = 0; port < m_port_num; port++){
        for(int i=0;i<m_mods+m_bEnablePSGDrum;i++) m_modules[port][i].Reset();
    }
    m_cur_idx = 0;
    m_cur_port = 0;
    m_dwCurSample = 0;
    m_dwEndSample = 0;
    m_dwSilentSample = 0;
}

DWORD CSMFPlay::Render(double *buf, DWORD length) 
{
    //if (buf)memset(buf, 0, length * sizeof(double) * 2);
    CreateRenderThreads();
    int i;
    {//各スレッドに割り当てるチャンネルをランダムに設定
     //使用する MIDI チャンネルに偏りがあっても速度に影響
     //しないようにする(1つしか使ってない場合はどうにもならないが)
        const int max_ch = m_mods / 2;
        int channels[17];
        for(i = 0; i < max_ch; i++){
            channels[i] = i;
        }
        if(m_bEnablePSGDrum){
            channels[i] = 16;//PSGDrum
        }
        for(i = 0; i < max_ch + m_bEnablePSGDrum; i++){
            int swap_idx = rand() % (max_ch + m_bEnablePSGDrum);
            int tmp = channels[swap_idx];
            channels[swap_idx] = channels[i];
            channels[i] = tmp;
        }
        for (i = 0; i < m_threads; i++) {
            m_ThreadInfo[i].nChannels = 0;
        }
        for (i = 0; i < max_ch + m_bEnablePSGDrum; i++) {
            int thr_idx = i % m_threads;
            m_ThreadInfo[thr_idx].Channels[m_ThreadInfo[thr_idx].nChannels++] = channels[i];
        }
    }
    double *buffers[MAX_THREADS];
    double *buffer;
    if(buf){
        buffer = new double[length * sizeof(double) * 2 * (m_threads-1)];
    }
    else{
        buffer = NULL;
    }
    for(i = 1; i < m_threads; i++){
    //各スレッドに処理を促す
    //1番目のスレッドは除く(本体から呼ばれたスレッド内で処理)
    //先に他のスレッドに処理を促しておいて、終わるのを待つ間に1番目のスレッドを処理
        if(!m_ThreadInfo[i].hThread){
            continue;
        }
        if(buf){
            buffers[i] = &buffer[length * sizeof(double) * 2 * (i-1)];
        }
        else{
            buffers[i] = NULL;
        }
        PostThreadMessage(m_ThreadInfo[i].dwThreadId, WM_USER, length, (LPARAM)buffers[i]);
    }
    int new_idx;
    int new_port;
    DWORD new_cur_sample;
    //1番目のスレッドは常に本体から Render が呼ばれるスレッド
    RenderToChannel(this, buf, length, m_ThreadInfo[0].Channels, m_ThreadInfo[0].nChannels, &new_idx, &new_port, &new_cur_sample);
    //2番目以降のスレッドの処理が終わるのを待つ
    for(i = 1; i < m_threads; i++){
        WaitForSingleObject(m_ThreadInfo[i].hEvent, INFINITE);
    }
    m_cur_idx = new_idx;
    m_cur_port = new_port;
    m_dwCurSample = new_cur_sample;
    if(buf){
        DWORD idx;
        for(idx = 0; idx < length; idx++){
            for(i = 1; i < m_threads; i++){//i==0 は処理不要
                buf[2*idx] += buffers[i][2*idx];
                buf[2*idx+1] += buffers[i][2*idx+1];
            }
        }
        delete[] buffer;
    }
    m_dwCurSample = new_cur_sample;
    m_cur_idx = new_idx;
    m_cur_port = new_port;
    if (m_cur_idx >= m_events.size() && !IsLoopMIDI()) {
        const double limit = 0.01;
        const double *pcm = buf;
        for (DWORD i = 0; i < length; i++) {
            if (m_dwEndSample++ >= m_rate * 5) {//終了後 5 秒以上再生
                return i;
            }
            if (m_dwSilentSample++ >= (m_rate * 5 / 10)) {//0.5秒以上の無音
                return i;
            }
            if (pcm[i * 2] < -limit || pcm[i * 2] > limit ||
                pcm[i * 2 + 1] < -limit || pcm[i * 2 + 1] > limit) {//無音でない
                m_dwSilentSample = 0;
            }
        }
    }
    return length;//_length - length;//idx;
}

double CSMFPlay::GetDuration(void)const
{
    const int num_events = m_events.size();
    if(num_events > 0){
        return m_events[num_events-1].m_time_sec;
    }
    return 0;
}

unsigned __stdcall CSMFPlay::ThreadProc(void* pParam)
{
    THREAD_INFO *pThreadInfo = (THREAD_INFO*)pParam;
    CSMFPlay *pThis = pThreadInfo->pThis;
    HANDLE hEvent = pThreadInfo->hEvent;

    MSG message;
    PeekMessage(&message, NULL, 0, 0, PM_NOREMOVE);//メッセージループを作成
    SetEvent(hEvent);//準備の完了通知

    while(GetMessage(&message, NULL, 0, 0) > 0){
        TranslateMessage(&message);
        DispatchMessage(&message);
        if(message.message != WM_USER){
            continue;
        }
        DWORD length = message.wParam;
        double *buf = (double*)message.lParam;
        RenderToChannel(pThis, buf, length, pThreadInfo->Channels, pThreadInfo->nChannels, NULL, NULL, NULL);
        SetEvent(hEvent);
    }
    return 0;
}

DWORD CSMFPlay::RenderToChannel(CSMFPlay *pThis, double *buf, DWORD length, int Channels[17], int nChannels, 
                       int *pNewIdx, int *pNewPort, DWORD *pdwNewCurSample)
{//Channels[] で指定されたチャンネルに対してのみレンダリングする
 //(Channels[x] == 16 の場合は PSGDrum)
 //pNewIdx:  レンダリング後の MIDI 位置(m_events のインデックス番号)
 //pNewPort: レンダリング後のポート番号
 //pdwNewCurSample: レンダリング後の再生位置
    const int  mods = pThis->m_mods;
    const int  port_num = pThis->m_port_num;
    const bool bEnableOPLL = pThis->m_bEnableOPLL;
    const bool bEnableSCC = pThis->m_bEnableSCC;
    const bool bEnablePSGDrum = pThis->m_bEnablePSGDrum;
    const int  rate = pThis->m_rate;
    const int  loop_point = pThis->m_loop_point;
    const std::vector<CSMFEvent>& events = pThis->m_events;
    CMIDIModule **modules = pThis->m_modules;
    int cur_idx = pThis->m_cur_idx;
    int cur_port = pThis->m_cur_port;
    DWORD dwCurSample = pThis->m_dwCurSample;
    DWORD idx = 0;
    CMIDIMsg msg;
    CMIDIMsgInterpreter mi;

    if (buf) {
        memset(buf, 0, sizeof(double)*length * 2);
    }
    while (idx < length) {
        while (cur_idx < events.size()) {
            const CSMFEvent &ev = events[cur_idx];
            UINT64 cur_time_sample = ev.m_time_sec * rate;
            if (cur_time_sample > dwCurSample) {
                break;
            }
            if (ev.m_type == CSMFEvent::MIDI_EVENT ||
                ev.m_type == CSMFEvent::SYSEX_EVENT) {
                mi.Interpret((BYTE)ev.m_status);
                for (DWORD i = 0; i < ev.m_length; i++) mi.Interpret(ev.m_data[i]);
                while (mi.GetMsgCount()) {
                    const CMIDIMsg &msg = mi.GetMsg();
                    if (msg.m_type != CMIDIMsg::SYSTEM_EXCLUSIVE) {
                        for (int c = 0; c < nChannels; c++) {
                            int ch = Channels[c];
                            if (ch == 16) {
                                if (msg.m_ch == 9) {
                                    if (bEnablePSGDrum) {
                                        modules[cur_port][mods].SendMIDIMsg(msg);//PSGDrum
                                    }
                                }
                                continue;
                            }
                            if (ch * 2 == ((msg.m_ch * 2) % mods)) {
                                if (bEnableOPLL)modules[cur_port][ch * 2].SendMIDIMsg(msg);
                                if (msg.m_ch != 9) {
                                    if (bEnableSCC) {
                                        modules[cur_port][ch * 2 + 1].SendMIDIMsg(msg);
                                    }
                                }
                            }
                        }
                    }
                    else {//SysEx
                    //全てのポートに SysEx 送信
                    //ポートごとに送信すべきSysExがあるかもしれないが、
                    //現状マスターボリューム系しか対応しないので
                        for(int port = 0; port < port_num; port++){
                        for (int c = 0; c < nChannels; c++) {
                            int ch = Channels[c];
                            if (ch < 16) {
                                //if (bEnableOPLL)modules[cur_port][(ch * 2) % mods].SendMIDIMsg(msg); //OPLL
                                //if (bEnableSCC)modules[cur_port][(ch * 2 + 1) % mods].SendMIDIMsg(msg);//SCC
                                if (bEnableOPLL)modules[port][(ch * 2) % mods].SendMIDIMsg(msg); //OPLL
                                if (bEnableSCC)modules[port][(ch * 2 + 1) % mods].SendMIDIMsg(msg);//SCC
                            }
                            else {
                                //if (bEnablePSGDrum)modules[cur_port][mods].SendMIDIMsg(msg);
                                if (bEnablePSGDrum)modules[port][mods].SendMIDIMsg(msg);
                            }
                        }
                        }
                    }
                    mi.PopMsg();
                }
            }
            else if (ev.m_type == CSMFEvent::META_EVENT) {
                if (ev.m_status == 0xFF21) {// port
                    cur_port = ev.m_data[0];
                    if (cur_port >= port_num) {
                        cur_port = 0;
                    }
                }
                else if (ev.m_status == 0xFF2F && (cur_idx == events.size() - 1)) { // ENDOFTRACK
                    if (loop_point >= 0) {
                        cur_idx = loop_point;
                        dwCurSample = events[loop_point].m_time_sec * rate;
                        continue;
                    }
                }
            }
            cur_idx++;
        }
        if (buf) {
            const double k = 1.0 / 0x80000000i64;
            for (int port = 0; port < port_num; port++) {
                for (int c = 0; c < nChannels; c++) {
                    int ch = Channels[c];
                    INT32 b[2] = { 0 };
                    if (ch != 16) {
                        if (bEnableOPLL) {
                            modules[port][ch * 2].Render(b);
                            buf[idx * 2] += b[0] * k;
                            buf[idx * 2 + 1] += b[1] * k;
                        }
                        if (bEnableSCC) {
                            modules[port][ch * 2 + 1].Render(b);
                            buf[idx * 2] += b[0] * k;
                            buf[idx * 2 + 1] += b[1] * k;
                        }

                    }
                    else if (bEnablePSGDrum) {
                        modules[port][mods].Render(b);
                        buf[idx * 2] += b[0] * k;
                        buf[idx * 2 + 1] += b[1] * k;
                    }
                }
            }
        }
        else {
            for (int port = 0; port < port_num; port++) {
                for (int c = 0; c < nChannels; c++) {
                    int ch = Channels[c];
                    if (ch != 16) {
                        if (bEnableOPLL) {
                            modules[port][ch * 2].Render(NULL);
                        }
                        if (bEnableSCC) {
                            modules[port][ch * 2 + 1].Render(NULL);
                        }
                    }
                    else if (bEnablePSGDrum) {
                        modules[port][mods].Render(NULL);
                    }
                }
            }
        }
        idx++;
        dwCurSample++;
    }
    if(pNewIdx){
        *pNewIdx = cur_idx;
    }
    if(pNewPort){
        *pNewPort = cur_port;
    }
    if(pdwNewCurSample){
        *pdwNewCurSample = dwCurSample;
    }
    return length;
}

void CSMFPlay::CreateRenderThreads(void)
{
    if(m_ThreadInfo[0].pThis){//既にスレッドを作成済み
        return;
    }
    int i;
    //スレッドを作成
    //実際に作るのは2番目(インデックスが2以上)以降
    //(1番目は本体から呼ばれる Render と同じスレッドで処理)
    m_ThreadInfo[0].pThis = this;
    for(i = 1; i < m_threads; i++){//実際にスレッドを作るのは2番目(インデックスが 1 以上) 以降
        unsigned uThreadId;
        HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        HANDLE hThread = (HANDLE)(HANDLE)_beginthreadex(NULL, 0, ThreadProc, &m_ThreadInfo[i], 0, &uThreadId);
        m_ThreadInfo[i].pThis = this;
        m_ThreadInfo[i].hEvent = hEvent;
        m_ThreadInfo[i].hThread = hThread;
        m_ThreadInfo[i].dwThreadId = uThreadId;
    }
    //スレッドの準備が完了(メッセージループの作成)するのを待つ
    for(i = 1; i < m_threads; i++){
        WaitForSingleObject(m_ThreadInfo[i].hEvent, INFINITE);
    }
}
void CSMFPlay::TerminateRenderThreads(void)
{
    if(!m_ThreadInfo[0].pThis){//まだスレッドを作成していない
        return;
    }
    for (int i = 0; i < m_threads; i++) {
        if(m_ThreadInfo[i].hThread){
            PostThreadMessage(m_ThreadInfo[i].dwThreadId, WM_QUIT, 0, 0);
            WaitForSingleObject(m_ThreadInfo[i].hThread, INFINITE);
            CloseHandle(m_ThreadInfo[i].hEvent);
            CloseHandle(m_ThreadInfo[i].hThread);
        }
    }
    ZeroMemory(&m_ThreadInfo, sizeof(m_ThreadInfo));
}

