#include <windows.h>
#include <wchar.h>
#include <process.h>
#include "kbmov_player_base.h"
#include "kbmov_config.h"

static const wchar_t g_cszKbMovWindowClassName[] = L"kbmov_window";

/////////////////////////////////////////////////////////////////////////
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 KbMovPlayerBase::s_bMaximized = false;
///////////////////////////////////////////////////////////////////////////////
KbMovPlayerBase::KbMovPlayerBase(void)
{
    m_Command.nCommand = 0;
    m_Command.wParam = 0;
    m_Command.lParam = 0;
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_pConfig = NULL;
    m_nStatus = KPI_STATUS_STOP;
    m_hThread = NULL;
    m_dwThreadId = 0;
    m_hEvent = NULL;
    m_hEventDone = NULL;
    m_hWnd = NULL;
    m_hWndDraw = NULL;
    m_szClassName[0] = 0;
    m_nLeft = 0;
    m_nTop = 0;
    m_nWidth = 0;
    m_nHeight = 0;
    m_nStatus = 0;
    m_nTempo = 100;
    m_nVolume = 100;
    m_nRestoredLeft = 0;
    m_nRestoredTop = 0;
    m_nRestoredWidth = 0;
    m_nRestoredHeight = 0;
    m_bRestoredTopMost = false;
    m_bFullScreen = false;
    m_bVisible = true;
    m_bWindowInitialized = false;
}
///////////////////////////////////////////////////////////////////////////////
KbMovPlayerBase::~KbMovPlayerBase(void)
{
    //if (m_hThread) {//派生クラスで処理しなければならない
    //    SendCommand(COMMAND_QUIT, 0, 0);
    //    WaitForSingleObject(m_hThread, INFINITE);
    //    CloseHandle(m_hThread);
    //}
    if(m_pFile)m_pFile->Release();
    if(m_pConfig)m_pConfig->Release();
    if(m_hEvent)CloseHandle(m_hEvent);
    if(m_hEventDone)CloseHandle(m_hEventDone);
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbMovPlayerBase::Open(IKpiConfig *pConfig, IKpiFile *pFile, IKpiFolder *pFolder)
{
    m_pConfig = pConfig;
    m_pFile = pFile;
    pConfig->AddRef();
    pFile->AddRef();
    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, (unsigned*)&m_dwThreadId);
    HANDLE hHandles[] = { m_hThread, m_hEventDone };
    WaitForMultipleObjects(2, hHandles, FALSE, INFINITE);
    return m_MediaInfo.dwCount;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMovPlayerBase::Select(DWORD dwNumber,
                                 const KPI_MEDIAINFO **ppMediaInfo,
                                 IKpiTagInfo *pTagInfo,
                                 DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if(dwNumber > m_MediaInfo.dwCount){
        return 0;
    }
    if(!ppMediaInfo){
        return 1;
    }
    *ppMediaInfo = &m_MediaInfo;
    return 1;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMovPlayerBase::GetPosition(void)
{
    if(m_nStatus == KPI_STATUS_STOP){
        return 0;
    }
    DWORD dwPos = 0;
    SendCommand(COMMAND_GET_POSITION, (WPARAM)&dwPos, 0);
    return dwPos;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbMovPlayerBase::Play(DWORD dwStartPos, BOOL bVisible)
{
    CreateVideoWindow();
    SendCommand(COMMAND_RUN, dwStartPos, bVisible);
    m_bVisible = bVisible != 0;
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbMovPlayerBase::Pause(void)
{
    SendCommand(COMMAND_PAUSE, 0, 0);
    return true;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbMovPlayerBase::Restart(void)
{
    SendCommand(COMMAND_RESTART, 0, 0);
    return true;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbMovPlayerBase::Stop(void)
{
    SendCommand(COMMAND_STOP, 0, 0);
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbMovPlayerBase::GetStatus(void)
{
    SendCommand(COMMAND_GET_STATUS, 0, 0);
    return m_nStatus;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbMovPlayerBase::SetVisible(BOOL bVisible)
{
    if(GetCurrentThreadId() == m_dwThreadId){
        if(m_hWnd){
            int nCmdShow;
            if(bVisible){
                if(s_bMaximized && !m_bFullScreen){
                    nCmdShow = SW_MAXIMIZE;
                }
                else{
                    nCmdShow = SW_SHOWNOACTIVATE;
                }
            }
            else{
                nCmdShow = SW_HIDE;
            }
            ShowWindow(m_hWnd, nCmdShow);
        }
    }
    else{
        SendCommand(COMMAND_SET_VISIBLE, bVisible, 0);
    }
    m_bVisible = bVisible != 0;
}
///////////////////////////////////////////////////////////////////////////////
void  WINAPI KbMovPlayerBase::SetTempo(int nTempo)
{
    if(nTempo < 50)nTempo = 50;
    else if(nTempo > 200)nTempo = 200;
    SendCommand(COMMAND_SET_TEMPO, nTempo, 0);
    m_nTempo = nTempo;
}
///////////////////////////////////////////////////////////////////////////////
void  WINAPI KbMovPlayerBase::SetVolume(int nVolume)
{
    if(nVolume < 0)nVolume = 0;
    else if(nVolume > 100)nVolume = 100;
    SendCommand(COMMAND_SET_VOLUME, nVolume, 0);
    m_nVolume = nVolume;
}
///////////////////////////////////////////////////////////////////////////////
void  WINAPI KbMovPlayerBase::SetTopMost(BOOL bTopMost)
{
    if(GetCurrentThreadId() == m_dwThreadId){
        HWND hTopMost;
        if(bTopMost){
            if(s_bMaximized && m_bFullScreen){
                hTopMost = HWND_NOTOPMOST;//フルスクリーン時はあえて常に手前に表示しない(操作不能になるのを回避)
            }
            else{
                hTopMost = HWND_TOPMOST;
            }
        }
        else{
            hTopMost = HWND_NOTOPMOST;
        }
        if (m_hWnd)SetWindowPos(m_hWnd, hTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    }
    else{
        SendCommand(COMMAND_SET_TOPMOST, bTopMost, 0);
    }
    m_bRestoredTopMost = bTopMost != 0;
}
///////////////////////////////////////////////////////////////////////////////
void  WINAPI KbMovPlayerBase::GetWindowPosition(int *pLeft,  int *pTop,
                                            int *pVideoWidth, int *pVideoHeight)
{
    RECT rc = {0};
    if(m_hWnd){
        if(m_bFullScreen && s_bMaximized){
            *pLeft = m_nRestoredLeft;
            *pTop = m_nRestoredTop;
            *pVideoWidth = m_nRestoredWidth;
            *pVideoHeight = m_nRestoredHeight;
        }
        else{
            SendCommand(COMMAND_GET_VIDEOPOS, (WPARAM)&rc, 0);
            *pLeft = rc.left;
            *pTop = rc.top;
            *pVideoWidth = rc.right - rc.left;
            *pVideoHeight = rc.bottom - rc.top;
        }
    }
    else{
        *pLeft = m_nLeft;
        *pTop = m_nTop;
        *pVideoWidth = m_nWidth;
        *pVideoHeight = m_nHeight;
    }
}
///////////////////////////////////////////////////////////////////////////////
void  WINAPI KbMovPlayerBase::SetWindowPosition(int nLeft, int nTop,
                                            int nVideoWidth, int nVideoHeight)
{
    RECT rc;
    rc.left = nLeft;
    rc.top = nTop;
    rc.right = nLeft + nVideoWidth;
    rc.bottom = nTop + nVideoHeight;
    CreateVideoWindow();
    SendCommand(COMMAND_SET_VIDEOPOS, (WPARAM)&rc, 0);
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbMovPlayerBase::RegisterWndClass(void)
{
    if (!m_szClassName[0]) {
        LARGE_INTEGER counter = { 0 };
        QueryPerformanceCounter(&counter);
        _snwprintf_s(m_szClassName, _countof(m_szClassName),
            _TRUNCATE, L"kbmov_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 KbMovPlayerBase::UnregisterWndClass(void)
{
    if (m_szClassName[0]) {
        UnregisterClass(m_szClassName, GetModuleHandle(NULL));
        m_szClassName[0] = 0;
    }
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbMovPlayerBase::UpdateWindowPosition(int nLeft, int nTop, int nWidth, int nHeight, bool bTopMost)
{//ThreadProc と同じスレッド内で呼び出す
    KbMov_Settings settings;
    kbmov_GetSettings(m_pConfig, &settings);
    m_bRestoredTopMost = bTopMost;
    bool bFullScreenChanged = settings.bFullScreen != m_bFullScreen;
    m_bFullScreen = settings.bFullScreen;
    int rx = m_MediaInfo.dwVideoWidth;
    int ry = m_MediaInfo.dwVideoHeight;
    nHeight = nWidth * (double)ry / (double)rx;
    HWND hTopMost = bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST;

    if(s_bMaximized){
        if(m_bFullScreen){
            if(m_nRestoredWidth > 0 && m_nRestoredHeight > 0 && !bFullScreenChanged){
                ShowWindow(m_hWnd, m_bVisible ? SW_SHOW : SW_HIDE);
            }
            else{
                m_nRestoredLeft = nLeft;
                m_nRestoredTop = nTop;
                m_nRestoredWidth = nWidth;
                m_nRestoredHeight = nHeight;
                SetFullScreen(true);
            }
            m_nLeft = nLeft;
            m_nTop = nTop;
            m_nWidth = nWidth;
            m_nHeight = nHeight;
            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);
            }
        }
    }
    m_nLeft = nLeft;
    m_nTop = nTop;
    m_nWidth = nWidth;
    m_nHeight = nHeight;

    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);
    //if(!m_bVisible){
        wp.showCmd = SW_HIDE;//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 KbMovPlayerBase::GetVideoPos(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 KbMovPlayerBase::CreateVideoWindow(void)
{
    if(m_bWindowInitialized){
        return;
    }
    m_bWindowInitialized = true;
    if(m_MediaInfo.dwVideoWidth >  0 && m_MediaInfo.dwVideoHeight > 0){
        SendCommand(COMMAND_CREATEWINDOW, 0, 0);
    }
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbMovPlayerBase::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);
}
/////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbMovPlayerBase::VideoWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    KbMovPlayerBase* pThis = (KbMovPlayerBase*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (!pThis) {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return pThis->VideoWindowProcInternal(hWnd, message, wParam, lParam);
}
/////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbMovPlayerBase::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){
            m_nLeft = m_nRestoredLeft;
            m_nTop = m_nRestoredTop;
            m_nWidth = m_nRestoredWidth;
            m_nHeight = m_nRestoredHeight;
        }
        else{
            GetVideoPos(&m_nLeft, &m_nTop, &m_nWidth, &m_nHeight);
        }
        ShowWindow(hWnd, SW_HIDE);
        return 0;
    }
    case WM_SIZING:{
        CheckSize(wParam, lParam,
            m_MediaInfo.dwVideoWidth, m_MediaInfo.dwVideoHeight);
        break;
    }
    case WM_SIZE:{
        if(wParam == SIZE_MAXIMIZED){
            s_bMaximized = true;
            if(m_bFullScreen){//フルスクリーン化
                GetVideoPos(&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);
}
/////////////////////////////////////////////////////////////////////////
unsigned __stdcall KbMovPlayerBase::ThreadProc(void* pvParam)
{
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ((KbMovPlayerBase*)pvParam)->ThreadProc();
    ((KbMovPlayerBase*)pvParam)->UnregisterWndClass();
    CoUninitialize();
    return 0;
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbMovPlayerBase::SendCommand(int nCommand, WPARAM wParam, LPARAM lParam)
{
    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 KbMovPlayerBase::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, m_bVisible ? SW_SHOW : SW_HIDE);
}
/////////////////////////////////////////////////////////////////////////
