#include <windows.h>
#include <process.h>
#include "kbdshow_decoder_base.h"
#include "kbdshow_config.h"
#include "kpi_player.h" //KPI_STATUS_STOP/PLAYING/PAUSE
#include "kpifile_source_filter.h"

extern HINSTANCE g_hKpi;
/////////////////////////////////////////////////////////////////////////
static void CheckSize(WPARAM wParam, LPARAM lParam,
    double RX, double RY)
{
    RECT* rc;
    double  width, height;
    rc = (RECT*)lParam;

    width = rc->right - rc->left;
    height = rc->bottom - rc->top;
    int nHeightOffset = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2;
    int nWidthOffset = GetSystemMetrics(SM_CXFRAME) * 2;
    width -= nWidthOffset;
    height -= nHeightOffset;

    switch (wParam) {
    case WMSZ_TOP:
    case WMSZ_BOTTOM:
        rc->right = rc->left + (int)(height * (RX / RY)) + nWidthOffset;
        break;
    case WMSZ_LEFT:
    case WMSZ_RIGHT:
        rc->bottom = rc->top + (int)(width * (RY / RX)) + nHeightOffset;
        break;
    case WMSZ_TOPLEFT:
        if ((width / height) < (RX / RY)) {
            rc->left = rc->right - (int)(height * (RX / RY)) - nWidthOffset;
        }
        else {
            rc->top = rc->bottom - (int)(width * (RY / RX)) - nHeightOffset;
        }
        break;
    case WMSZ_TOPRIGHT:
        if ((width / height) < (RX / RY)) {
            rc->right = rc->left + (int)(height * (RX / RY)) + nWidthOffset;
        }
        else {
            rc->top = rc->bottom - (int)(width * (RY / RX)) - nHeightOffset;
        }
        break;
    case WMSZ_BOTTOMLEFT:
        if ((width / height) < (RX / RY)) {
            rc->left = rc->right - (int)(height * (RX / RY)) - nWidthOffset;
        }
        else {
            rc->bottom = rc->top + (int)(width * (RY / RX)) + nHeightOffset;
        }
        break;
    case WMSZ_BOTTOMRIGHT:
        if ((width / height) < (RX / RY)) {
            rc->right = rc->left + (int)(height * (RX / RY)) + nWidthOffset;
        }
        else {
            rc->bottom = rc->top + (int)(width * (RY / RX)) + nHeightOffset;
        }
        break;
    }
}
bool KbDShowDecoderBase::s_bMaximized = false;
/////////////////////////////////////////////////////////////////////////
KbDShowDecoderBase::KbDShowDecoderBase(void)
{
    m_Command.nCommand = m_Command.wParam = m_Command.lParam = 0;
    m_hFileMap = NULL;
    m_pStatus = NULL;
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_pConfig = NULL;
    m_hWnd = NULL;
    m_hWndDraw = NULL;
    m_szClassName[0] = 0;
    m_hThread = NULL;
    m_dwThreadId = 0;
    m_hEvent = NULL;
    m_hEventDone = NULL;
    m_rx = m_ry = 0;
    m_nStopMode = 0;
    m_nStatus = KPI_STATUS_STOP;
    m_qwCurSample = (UINT64)-1;
    m_nRestoredLeft = 0;
    m_nRestoredTop = 0;
    m_nRestoredWidth = 0;
    m_nRestoredHeight = 0;
    m_bRestoredTopMost = false;
    m_bFullScreen = false;
}
/////////////////////////////////////////////////////////////////////////
KbDShowDecoderBase::~KbDShowDecoderBase(void)
{
    //if(m_hThread){//派生クラスのデストラクタで処理しなければならない
    //    SendCommand(COMMAND_QUIT, 0, 0);
    //    WaitForSingleObject(m_hThread, INFINITE);
    //    CloseHandle(m_hThread);
    //}
    if(m_pStatus){
        UnmapViewOfFile(m_pStatus);
    }
    if(m_hFileMap){
        CloseHandle(m_hFileMap);
    }
    if(m_pFile)m_pFile->Release();
    if(m_pConfig)m_pConfig->Release();
    if(m_hEvent)CloseHandle(m_hEvent);
    if(m_hEventDone)CloseHandle(m_hEventDone);
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::RegisterWndClass(void)
{
    if (!m_szClassName[0]) {
        LARGE_INTEGER counter = { 0 };
        QueryPerformanceCounter(&counter);
        _snwprintf_s(m_szClassName, _countof(m_szClassName),
            _TRUNCATE, L"kbdshow_window_%08x:%08x:%016llx",
            GetCurrentThreadId(), GetCurrentProcessId(), counter.QuadPart);
        WNDCLASSEX  wc;
        wc.cbSize = sizeof(wc);
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = VideoWindowProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = GetModuleHandle(NULL);
        wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = m_szClassName;
        wc.hIconSm = NULL;
        RegisterClassEx(&wc);
    }
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::UnregisterWndClass(void)
{
    if (m_szClassName[0]) {
        UnregisterClass(m_szClassName, GetModuleHandle(NULL));
        m_szClassName[0] = 0;
    }
}
/////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbDShowDecoderBase::VideoWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    KbDShowDecoderBase* pThis = (KbDShowDecoderBase*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (!pThis) {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return pThis->VideoWindowProcInternal(hWnd, message, wParam, lParam);
}
/////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbDShowDecoderBase::VideoWindowProcInternal(HWND hWnd,
                        UINT message,  WPARAM wParam,  LPARAM lParam)
{
    if(hWnd == m_hWndDraw){
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    switch (message) {
    case WM_CLOSE:{
        int nLeft = 0, nTop = 0, nWidth = 0, nHeight = 0;
        if(m_bFullScreen && s_bMaximized){
            nLeft = m_nRestoredLeft;
            nTop = m_nRestoredTop;
            nWidth = m_nRestoredWidth;
            nHeight = m_nRestoredHeight;
        }
        else{
            GetWindowPosition(&nLeft, &nTop, &nWidth, &nHeight);
        }
        if(nWidth > 0 && nHeight > 0){
        kbdshow_SetVideoPosition(m_pConfig, nLeft, nTop, nWidth, nHeight);
        }
        ShowWindow(hWnd, SW_HIDE);
        return 0;
        break;
    }
    case WM_SIZING:{
        int rx = m_rx;
        int ry = m_ry;
        if(rx == 0 || ry == 0){
            rx = m_MediaInfo.dwVideoWidth;
            ry = m_MediaInfo.dwVideoHeight;
        }
        CheckSize(wParam, lParam, rx, ry);
        break;
    }
    case WM_SIZE:{
        if(wParam == SIZE_MAXIMIZED){
            s_bMaximized = true;
            if(m_bFullScreen){//フルスクリーン化
                GetWindowPosition(&m_nRestoredLeft, &m_nRestoredTop, &m_nRestoredWidth, &m_nRestoredHeight);//現在の位置と大きさを保存
                SetFullScreen(true);
            }
        }
        else if(wParam == SIZE_RESTORED){
            if(!m_bFullScreen){
                s_bMaximized = false;
            }
        }
        break;
    }
    case WM_PARENTNOTIFY://左クリック(m_hWndDraw の領域)
    case WM_LBUTTONDOWN: //左クリック
    case WM_KEYDOWN:{    //キーボード押下
        //フルスクリーン解除
        if(!m_bFullScreen || !s_bMaximized){//フルスクリーン状態でない
            break;
        }
        if(message == WM_PARENTNOTIFY){
            if(LOWORD(wParam) != WM_LBUTTONDOWN){
                break;//左クリック以外
            }
        }
        //else if(message == WM_KEYDOWN){
        //}
        SetFullScreen(false);
        s_bMaximized = false;
        break;
    }
    default:
        break;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}
/////////////////////////////////////////////////////////////////////////
void  WINAPI KbDShowDecoderBase::GetWindowPosition(int* pnLeft, int* pnTop,
    int* pnVideoWidth, int* pnVideoHeight)
{
    if (m_hWnd) {
        WINDOWPLACEMENT wp = {0};//最大化していないときのウィンドウサイズ
        wp.length = sizeof(WINDOWPLACEMENT);
        GetWindowPlacement(m_hWnd, &wp);
        RECT rect = { 0 };
        // デスクトップ作業領域を取得
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
        OffsetRect(&wp.rcNormalPosition, rect.left, rect.top);
        *pnLeft = wp.rcNormalPosition.left;
        *pnTop = wp.rcNormalPosition.top;
        *pnVideoWidth = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
        *pnVideoHeight = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
        *pnVideoWidth -= GetSystemMetrics(SM_CXFRAME) * 2;
        *pnVideoHeight -= (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2);
    }
    else {
        *pnLeft = *pnTop = *pnVideoWidth = *pnVideoHeight = 0;
    }
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::SendCommand(int nCommand, WPARAM wParam, LPARAM lParam)
{
    if(!m_hThread)return;
    HANDLE hHandles[] = { m_hThread, m_hEventDone };
    m_Command.nCommand = nCommand;
    m_Command.wParam = wParam;
    m_Command.lParam = lParam;
    SetEvent(m_hEvent);
    WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::UpdateWindowPosition(void)
{//ThreadProc と同じスレッド内で呼び出す
    KbDShow_Settings settings;
    kbdshow_GetSettings(m_pConfig, &settings);
    m_rx = settings.rx;
    m_ry = settings.ry;
    m_nStopMode = settings.nStop;
    m_bRestoredTopMost = settings.bTopMost;
    bool bFullScreenChanged = settings.bFullScreen != m_bFullScreen;
    m_bFullScreen = settings.bFullScreen;
    int nLeft = 0;
    int nTop = 0;
    int nWidth = 0;
    //int nHeight = 0;//nWidth とオリジナルの幅と高さで決まる
    if (settings.nVideoSize < 0) {//%
        int r = -settings.nVideoSize;
        int w = m_MediaInfo.dwVideoWidth * r / 100;
        int h = m_MediaInfo.dwVideoHeight * r / 100;
        // % 以外のときの nWidth はクライアント領域の幅だが
        // % のときは映像の幅になるのでクライアント領域の幅を計算
        double rx = settings.rx;
        double ry = settings.ry;
        if (rx == 0 || ry == 0) {
            rx = m_MediaInfo.dwVideoWidth;
            ry = m_MediaInfo.dwVideoHeight;
        }
        int ch = w * ry / rx;
        if(ch > h){
            nWidth = w;
        }
        else{
            nWidth = h * rx / ry;
        }
        GetWindowPosition(&nLeft, &nTop, &w, &h);
    }
    else if (settings.nVideoSize > 0) {//幅指定
        nWidth = settings.nVideoSize;
        int w, h;
        GetWindowPosition(&nLeft, &nTop, &w, &h);
    }
    else {//前回と同じ幅
        int h;
        GetWindowPosition(&nLeft, &nTop, &nWidth, &h);
    }
    if (settings.bResetPos) {//位置を 0, 0
        nLeft = nTop = 0;
    }
    int rx = m_rx;
    int ry = m_ry;
    if (rx == 0 || ry == 0) {
        rx = m_MediaInfo.dwVideoWidth;
        ry = m_MediaInfo.dwVideoHeight;
    }
    int nHeight = nWidth * (double)ry / (double)rx;
    HWND hTopMost = settings.bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST;

    if(s_bMaximized){
        if(m_bFullScreen){
            if(m_nRestoredWidth > 0 && m_nRestoredHeight > 0 && !bFullScreenChanged){
                ShowWindow(m_hWnd, SW_SHOW);
            }
            else{
                m_nRestoredLeft = nLeft;
                m_nRestoredTop = nTop;
                m_nRestoredWidth = nWidth;
                m_nRestoredHeight = nHeight;
                SetFullScreen(true);
            }
            return;
        }
        else{
            if(m_nRestoredWidth > 0 && m_nRestoredHeight > 0){
                nLeft = m_nRestoredLeft;
                nTop = m_nRestoredTop;
                nWidth = m_nRestoredWidth;
                nHeight = m_nRestoredHeight;
                SetFullScreen(false);//フルスクリーンを元に戻す
            }
            if(!IsZoomed(m_hWnd)){
                ShowWindow(m_hWnd, SW_MAXIMIZE);
            }
        }
    }
    nWidth += GetSystemMetrics(SM_CXFRAME) * 2;
    nHeight += GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2;

    SetWindowPos(m_hWnd, hTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
    WINDOWPLACEMENT wp = {0};
    wp.length = sizeof(wp);
    GetWindowPlacement(m_hWnd, &wp);
    //wp.showCmd = SW_SHOWNOACTIVATE;
    wp.rcNormalPosition.left = nLeft;
    wp.rcNormalPosition.top = nTop;
    wp.rcNormalPosition.right = nLeft + nWidth;
    wp.rcNormalPosition.bottom = nTop + nHeight;
    SetWindowPlacement(m_hWnd, &wp);
    UpdateWindow(m_hWnd);
    UpdateWindow(m_hWndDraw);
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::CreateKmpStatus(void)
{
    //本体から再生位置情報を得るための構造体へのポインタ取得
    wchar_t szFileMapName[128];
    //IKpiModule::Open から戻った時点で以下の名前のFileMapオブジェクトを作成しておくと本体が再生位置情報を書き込むようになる
    //kmp_status_filemapping_(プロセスID)_(プラグインインスタンスハンドル)_(IKpiDecoder インスタンスポインタ)
    //IKpiModule::Select の時点で作成しても遅いことに注意
    IKpiDecoder* pThis = this;
#ifdef _WIN64 //64bit 版
    _snwprintf_s(szFileMapName, _TRUNCATE,
        L"kmp_status_filemapping_%lu_%llu_%llu",
        GetCurrentProcessId(),
        (UINT64)g_hKpi,
        (UINT64)pThis);
#else //32bit 版
    _snwprintf_s(szFileMapName, _TRUNCATE,
        L"kmp_status_filemapping_%lu_%lu_%lu",
        GetCurrentProcessId(),
        (DWORD)g_hKpi,
        (DWORD)pThis);
#endif
    HANDLE hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
        PAGE_READWRITE, 0, sizeof(KMP_STATUS), szFileMapName);
    if (!hFileMap) {
        return;
    }
    void* pData = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(KMP_STATUS));
    if (!pData) {
        CloseHandle(hFileMap);
        return;
    }
    m_hFileMap = hFileMap;
    m_pStatus = (KMP_STATUS*)pData;
    m_pStatus->dwStructSize = sizeof(KMP_STATUS);//プラグイン側でセットしておかなければならない
    m_pStatus->dwAccepted = 0;//本体によって1がセットされる(されなければ m_pStatus は使用不可)
    m_pStatus->dwPosition = 0;//本体によって再生位置(ms)がセットされる
    m_pStatus->dwStatus = 0;  //本体によって再生状態(KPI_STATUS_STOP/PLAYING/PAUSE)がセットされる
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::CreateThread(void)
{
    if(m_hThread)return;
    unsigned thread_id = 0;
    m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hEventDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, NULL, &thread_id);
    m_dwThreadId = thread_id;
    HANDLE hHandles[] = { m_hThread, m_hEventDone };
    WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
}
/////////////////////////////////////////////////////////////////////////
unsigned __stdcall KbDShowDecoderBase::ThreadProc(void* pvParam)
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
    ((KbDShowDecoderBase*)pvParam)->ThreadProc();
    ((KbDShowDecoderBase*)pvParam)->UnregisterWndClass();
    CoUninitialize();
    return 0;
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::CreateVideoWindow(int X, int Y, int nWidth, int nHeight)
{
    if(m_hWnd)return;
    RegisterWndClass();
    m_hWnd = CreateWindowEx(0,
        m_szClassName,
        L"",
        WS_OVERLAPPEDWINDOW,
        X, Y,  nWidth, nHeight,
        NULL,
        NULL,
        GetModuleHandle(NULL),
        NULL);
    m_hWndDraw = CreateWindowEx(0,
        m_szClassName,
        L"",
        WS_VISIBLE | WS_CHILD,
        0, 0, 0, 0,
        m_hWnd,
        NULL,
        GetModuleHandle(NULL),
        NULL);
    SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
    SetWindowLongPtr(m_hWndDraw, GWLP_USERDATA, (LONG_PTR)this);
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbDShowDecoderBase::SetFullScreen(bool bFullScreen)
{
    //呼び出し前に m_nRestoreLeft, m_nRestoreTop, m_nRestoreWidth, m_nRestoreHeight を設定する
    ShowWindow(m_hWnd, SW_HIDE);
    if(bFullScreen){//フルスクリーン化
        //GetWindowPosition(&m_nRestoredLeft, &m_nRestoredTop, &m_nRestoredWidth, &m_nRestoredHeight);//現在の位置と大きさを保存
        POINT pt = {m_nRestoredLeft, m_nRestoredTop};
        
        HMONITOR hMoni = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
        MONITORINFOEX mi;
        mi.cbSize = sizeof(MONITORINFOEX);
        GetMonitorInfo(hMoni, &mi);
        //常に手前に表示を解除(万が一のときに強制終了も出来なくなる状況を避ける為)
        HWND hWndTopMost = HWND_NOTOPMOST;//m_bRestoredTopMost ? HWND_TOPMOST : HWND_NOTOPMOST;
        SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_POPUP);
        SetWindowPos(m_hWnd, hWndTopMost, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right-mi.rcMonitor.left, mi.rcMonitor.bottom-mi.rcMonitor.top, NULL);
    }
    else{//フルスクリーン解除
        int nLeft = m_nRestoredLeft;
        int nTop = m_nRestoredTop;
        int nWidth = m_nRestoredWidth + GetSystemMetrics(SM_CXFRAME) * 2;
        int nHeight = m_nRestoredHeight + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2;
        HWND hWndTopMost = m_bRestoredTopMost ? HWND_TOPMOST : HWND_NOTOPMOST;
        SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
        SetWindowPos(m_hWnd, hWndTopMost, nLeft, nTop, nWidth, nHeight, NULL);
        m_nRestoredLeft = m_nRestoredTop = m_nRestoredWidth = m_nRestoredHeight = 0;
    }
    SetWindowPos(m_hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
    ShowWindow(m_hWnd, SW_SHOW);
}
/////////////////////////////////////////////////////////////////////////

