#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <wchar.h>
#include "NSFDialogs.h"
#include "kbnsfplug_config.h"
#include "kbnsfplug_thread.h"

static const int MIN_POS_X = -1000;
static const int MIN_POS_Y = -1000;
static const int MIN_POS_W = 10;
static const int MIN_POS_H = 10;
static bool CheckPos(int x, int y)
{//false を返した場合はデフォルトの位置とサイズで表示
    if(x < MIN_POS_X || y < MIN_POS_Y)return false;
    return true;
}
static bool CheckSize(int w, int h)
{//false を返した場合はデフォルトの位置とサイズで表示
    if(w < MIN_POS_W || h < MIN_POS_H)return false;
    return true;
}
//////////////////////////////////////////////////////////////////////////
bool KbNsfplugUIThread::s_bResetPos = false;
//////////////////////////////////////////////////////////////////////////
KbNsfplugUIThread::KbNsfplugUIThread(IKpiConfig *pConfig)
{
    m_pConfig = pConfig;
    pConfig->AddRef();
    InitializeCriticalSection(&m_cs);
    m_hEventCmd = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hEventDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_pCache = NULL;
    ZeroMemory(&m_Command, sizeof(m_Command));
    unsigned thread_id;
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, 0/*CREATE_SUSPENDED*/, &thread_id);
}
//////////////////////////////////////////////////////////////////////////
KbNsfplugUIThread::~KbNsfplugUIThread(void)
{
    SendCommand(CMD_EXIT, 0, 0);
    WaitForSingleObject(m_hThread, INFINITE);
    CloseHandle(m_hThread);
    CloseHandle(m_hEventCmd);
    CloseHandle(m_hEventDone);
    DeleteCriticalSection(&m_cs);
    m_pConfig->Release();
}
//////////////////////////////////////////////////////////////////////////
unsigned WINAPI KbNsfplugUIThread::ThreadProc(void *pThis)
{
    return ((KbNsfplugUIThread*)pThis)->ThreadProc();
}
//////////////////////////////////////////////////////////////////////////
unsigned __fastcall KbNsfplugUIThread::ThreadProc(void)
{
    HANDLE hHandles[] = {m_hEventCmd};
    bool bExit = false;
    const DWORD dwHideWait = 1000;//キャッシュ後、1秒後にダイアログを隠す(NSF=>NSF への曲切り替え時のチラつき防止)
    DWORD dwWaitTime = INFINITE;
    DWORD dwHideTime = 0;//ダイアログを隠す時刻
    while(!bExit){
        //DWORD dwWait = MsgWaitForMultipleObjects(1, hHandles, FALSE, INFINITE, QS_ALLINPUT);
        DWORD dwWait = MsgWaitForMultipleObjects(1, hHandles, FALSE, dwWaitTime, QS_ALLINPUT);
        if(dwWait == WAIT_TIMEOUT){
            dwWaitTime = INFINITE;
            dwHideTime = 0;
            if(!m_pCache){
                continue;//m_pCache == NULL なら dwWaitTime == INFINITE なのであり得ない筈だが念の為
            }
            //キャッシュしていたダイアログを隠す
            m_pCache->track->ShowWindow(SW_HIDE);//一瞬手前に来るのが気に入らない
            m_pCache->memory->ShowWindow(SW_HIDE);
            m_pCache->info->ShowWindow(SW_HIDE);
            continue;
        }
        else if(dwWait == WAIT_OBJECT_0 + 1){
        //本スレッド内で作成したダイアログのメッセージを処理
            MSG msg;
            while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            if(dwHideTime){
            //ここが実行されることは滅多にない筈だが dwHideWait だけ経過する間にダイアログを動かした時とか
            //これまでの経過時間分だけ待ち時間を減らす
                DWORD dwCurTime = GetTickCount();
                if(dwCurTime > dwHideTime){
                    dwWaitTime = 0;
                }
                else{
                    dwWaitTime = dwHideTime - dwCurTime;
                }
            }
            continue;
        }
        {
        AFX_MANAGE_STATE(AfxGetStaticModuleState());//ダイアログにアクセスする前に呼ぶ
        switch(m_Command.nCommand){
        case CMD_CREATE:{
            NSFplug_Model *pModel = (NSFplug_Model*)m_Command.wParam;
            int mode = m_Command.lParam;
            NSFDialogManager *pManager = NULL;
            if(mode == 0){//キャッシュがあればそれを使う
                if(m_pCache){
                    dwWaitTime = INFINITE;
                    dwHideTime = 0;
                    pManager = m_pCache;
                    m_pCache = NULL;
                    pManager->pm = pModel;
                    pManager->track->SetDialogManager(pManager); //呼んでおかないと pm が更新されない
                    pManager->memory->SetDialogManager(pManager);//呼んでおかないと pm が更新されない
                    pManager->info->SetDialogManager(pManager);  //呼んでおかないと pm が更新されない
                }
            }
            if(!pManager){
                pManager = new NSFDialogManager(pModel, mode);
                if(mode == 0){
                    pManager->track->ModifyStyleEx(WS_EX_ACCEPTFILES, 0);//演奏情報へのドラッグアンドドロップ防止
                    pManager->info->ModifyStyleEx(WS_EX_ACCEPTFILES, 0);//プロパティへのドラッグアンドドロップ防止
                    pManager->info->SetMenu(NULL);//プロパティのメインメニューを削除
                }
            }
            LoadDialogPos(pManager);//ダイアログの位置をセット
            m_Command.lResult = (LPARAM)pManager;
            break;
        }
        case CMD_DELETE:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            SaveDialogPos(pManager);//ダイアログの位置を保存
            if(pManager->mode == 0 && !m_pCache){//空きがあればキャッシュする
            //既に X ボタンで閉じられていると ShowWindow(SW_HIDE) で OnShowWindow が呼ばれない
            //(track/memory は OnShowWindow 内でタイマーを止めている)
                pManager->track->OnShowWindow(false, 0);//タイマーを止める
                pManager->memory->KillTimer(1);//同上(OnShowWindow は protected でアクセス不可)(なるべく元ソースを変えたくないので)
                //pManager->track->ShowWindow(SW_HIDE); //直ぐには非表示にしない
                //pManager->memory->ShowWindow(SW_HIDE);//同上
                //pManager->info->ShowWindow(SW_HIDE);  //同上
                //pManager->StopUpdate();//タイマーを止めていれば不要
                m_pCache = pManager;
                dwHideTime = GetTickCount() + dwHideWait;//1秒後にダイアログを非表示にする
                dwWaitTime = dwHideWait;
            }
            else{
                delete pManager;
            }
            break;
        }
        case CMD_OPEN_DLG:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            int id = m_Command.lParam;
            CWnd *pWnd = NULL;//SetForegroundWindow を呼ぶウィンドウ
            //NSFDialogs.cpp の NSFDialogManager::OpenDialog 参照
            if(id == -1){
                pManager->panner->Open();//NSFDialogManager::OpenDialog では開けない
                pWnd = pManager->panner;
            }
            else{
                pManager->OpenDialog(id);
                switch(id){//曲ごとに表示するダイアログ(演奏情報、メモリ表示、プロパティ)は前面に出さない
                    case NSFplug_UI::DLG_CONFIG: pWnd = pManager->config; break;
                    //case NSFplug_UI::DLG_INFO: pWnd = pManager->info; break;//プロパティは前面に出さない
                    case NSFplug_UI::DLG_EASY: pWnd = pManager->easy; break;
                    case NSFplug_UI::DLG_MASK: pWnd = pManager->mask; break;
                    //case NSFplug_UI::DLG_MEMORY: pWnd = pManager->memory; break;//メモリ表示は前面に出さない(他の作業の邪魔にならないように)
                    //case NSFplug_UI::DLG_TRACK: pWnd = pManager->track; break;  //演奏情報は前面に出さない(他の作業の邪魔にならないように)
                    case NSFplug_UI::DLG_MIXER: pWnd = pManager->mixer; break;
                    case NSFplug_UI::DLG_PRESET: pWnd = pManager->preset; break;
                    default: break;
                }
            }
            if(pWnd){
                pWnd->SetForegroundWindow();
            }
            break;
        }
        case CMD_HIDE_DLG:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            int id = m_Command.lParam;
            CWnd *pWnd = NULL;
            switch(id){
            case NSFplug_UI::DLG_CONFIG: pWnd = pManager->config; break;
            case NSFplug_UI::DLG_INFO:   pWnd = pManager->info; break;
            case NSFplug_UI::DLG_EASY:   pWnd = pManager->easy; break;
            case NSFplug_UI::DLG_MASK:   pWnd = pManager->mask; break;
            case NSFplug_UI::DLG_MEMORY: pWnd = pManager->memory; break;
            case NSFplug_UI::DLG_TRACK:  pWnd = pManager->track; break;
            case NSFplug_UI::DLG_MIXER:  pWnd = pManager->mixer; break;
            case NSFplug_UI::DLG_PRESET: pWnd = pManager->preset; break;
            case -1: pWnd = pManager->panner; break;
            default: break;
            }
            if(pWnd){
                pWnd->ShowWindow(SW_HIDE);
                if(id == NSFplug_UI::DLG_MEMORY){
                    pManager->memory->KillTimer(1);//タイマーを止める
                }
                else if(id == NSFplug_UI::DLG_TRACK){
                    pManager->track->OnShowWindow(false, 0);//タイマーを止める
                }
            }
            break;
        }
        case CMD_START_TRACK:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            pManager->StartUpdate();//別スレッドから呼ぶと落ちることがある
            pManager->track->OnShowWindow(true, 0);//タイマー開始
            break;
        }
        case CMD_START_MEMORY:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            pManager->memory->SetTimer(1, 250, NULL);//タイマー開始
            break;
        }
        case CMD_SET_INFODATA:{
            NSFDialogManager *pManager = (NSFDialogManager*)m_Command.wParam;
            xgm::NSF *pNsf = (xgm::NSF*)m_Command.lParam;
            pManager->SetInfoData(pNsf);//関数から戻った後、pNsf は参照されない
            break;
        }
        case CMD_EXIT:{
            delete m_pCache;//キャッシュを削除
            m_pCache = NULL;
            bExit = true;//スレッド終了
            break;
        }
        default:
            break;
        }
        }
        SetEvent(m_hEventDone);
    }
    return 0;
}
//////////////////////////////////////////////////////////////////////////
LRESULT __fastcall KbNsfplugUIThread::SendCommand(int nCommand, WPARAM wParam, LPARAM lParam)
{
    Lock();
    m_Command.nCommand = nCommand;
    m_Command.wParam = wParam;
    m_Command.lParam = lParam;
    m_Command.lResult = 0;
    SetEvent(m_hEventCmd);
    WaitForSingleObject(m_hEventDone, INFINITE);
    Unlock();
    return m_Command.lResult;
}
//////////////////////////////////////////////////////////////////////////
NSFDialogManager* KbNsfplugUIThread::CreateManager(NSFplug_Model *pModel, int mode)
{
    return (NSFDialogManager*)SendCommand(CMD_CREATE, (WPARAM)pModel, mode);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::DeleteManager(NSFDialogManager* pManager)
{
    SendCommand(CMD_DELETE, (WPARAM)pManager, 0);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::OpenDialog(NSFDialogManager *pManager, int id)
{
    SendCommand(CMD_OPEN_DLG, (WPARAM)pManager, id);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::HideDialog(NSFDialogManager *pManager, int id)
{
    SendCommand(CMD_HIDE_DLG, (WPARAM)pManager, id);
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::StartTrack(NSFDialogManager *pManager)
{
    SendCommand(CMD_START_TRACK, (WPARAM)pManager, 0);
}
/////////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::StartMemory(NSFDialogManager *pManager)
{
    SendCommand(CMD_START_MEMORY, (WPARAM)pManager, 0);
}
/////////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::SetInfoData(NSFDialogManager *pManager,  xgm::NSF *pNsf)
{
    SendCommand(CMD_SET_INFODATA, (WPARAM)pManager, (LPARAM)pNsf);
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbNsfplugUIThread::SaveDialogPos(NSFDialogManager *pManager)
{
    if(!s_bResetPos){
        kbnsfplug_SaveDialogPos(m_pConfig, pManager);
    }
}
//////////////////////////////////////////////////////////////////////////
void __fastcall KbNsfplugUIThread::LoadDialogPos(NSFDialogManager *pManager)
{
    kbnsfplug_Settings settings;
    kbnsfplug_GetSettings(m_pConfig, &settings);
    if(pManager->track &&
       CheckPos(settings.nTrackPosX, settings.nTrackPosY) &&
       CheckSize(settings.nTrackPosW, settings.nTrackPosH)){
        pManager->track->SetWindowPos(&CWnd::wndNoTopMost, settings.nTrackPosX, settings.nTrackPosY, settings.nTrackPosW, settings.nTrackPosH, SWP_NOZORDER);
    }
    if(pManager->memory &&
       CheckPos(settings.nMemoryPosX, settings.nMemoryPosY) &&
       CheckSize(settings.nMemoryPosW, settings.nMemoryPosH)){
        pManager->memory->SetWindowPos(&CWnd::wndNoTopMost, settings.nMemoryPosX, settings.nMemoryPosY, settings.nMemoryPosW, settings.nMemoryPosH, SWP_NOZORDER);
    }
    if(pManager->info &&
       CheckPos(settings.nInfoPosX, settings.nInfoPosY) &&
       CheckSize(settings.nInfoPosW, settings.nInfoPosH)){
        pManager->info->SetWindowPos(&CWnd::wndNoTopMost, settings.nInfoPosX, settings.nInfoPosY, settings.nInfoPosW, settings.nInfoPosH, SWP_NOZORDER);
    }
    if(pManager->config &&
       CheckPos(settings.nConfigPosX, settings.nConfigPosY)){
        pManager->config->SetWindowPos(&CWnd::wndNoTopMost, settings.nConfigPosX, settings.nConfigPosY, 0, 0, SWP_NOSIZE|SWP_NOZORDER );
    }
    if(pManager->mixer &&
       CheckPos(settings.nMixerPosX, settings.nMixerPosY)){
        pManager->mixer->SetWindowPos(&CWnd::wndNoTopMost, settings.nMixerPosX, settings.nMixerPosY, 0, 0, SWP_NOSIZE|SWP_NOZORDER);
    }
    if(pManager->mask &&
       CheckPos(settings.nMaskPosX, settings.nMaskPosY)){
        pManager->mask->SetWindowPos(&CWnd::wndNoTopMost, settings.nMaskPosX, settings.nMaskPosY, 0, 0, SWP_NOSIZE|SWP_NOZORDER);
    }
    if(pManager->panner &&
       CheckPos(settings.nPanPosX, settings.nPanPosY)){
        pManager->panner->SetWindowPos(&CWnd::wndNoTopMost, settings.nPanPosX, settings.nPanPosY, 0, 0, SWP_NOSIZE|SWP_NOZORDER);
    }
}
//////////////////////////////////////////////////////////////////////////
void KbNsfplugUIThread::ResetPos(void)
{
    s_bResetPos = true;
}
//////////////////////////////////////////////////////////////////////////

