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

#include "kbcstr_player.h"
#include "kbcstr_module.h"
///////////////////////////////////////////////////////////////////////////////
static const wchar_t g_cszKbCstrWindowClassName[] = L"kbcstr_window";
wchar_t KbCstrPlayer::s_szClassName[128];
///////////////////////////////////////////////////////////////////////////////
static void CheckSize(WPARAM wParam, LPARAM lParam,
                      double RX, double RY)
{
    if(RX == 0 || RY == 0){
        return;
    }
    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;
    }
}
///////////////////////////////////////////////////////////////////////////////
// KbCstrPlayer
///////////////////////////////////////////////////////////////////////////////
KbCstrPlayer::KbCstrPlayer(void)
{
    kpi_InitMediaInfo(&m_MediaInfo);
    m_hWnd      = NULL;         // 表示ウインドウのハンドル
    m_hPlayer   = NULL;         // PsxMCのプレイヤー
    m_pFile = NULL;
}
///////////////////////////////////////////////////////////////////////////////
KbCstrPlayer::~KbCstrPlayer(void)
{
    if(m_hPlayer){
        _PlbClosePlayer(m_hPlayer);
    }
    if(m_hWnd){
        DestroyWindow(m_hWnd);
    }
    if(m_pFile){
        m_pFile->Release();
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbCstrPlayer::Open(IKpiFile *pFile, IKpiFolder *pFolder)
{
    wchar_t szFileName[MAX_PATH];
    const char *cszFileNameA;
    if(!pFile->GetRealFileA(&cszFileNameA)){
        return 0;
    }
    pFile->GetFileName(szFileName, sizeof(szFileName));
    // 表示ウインドウを生成します
    m_hWnd = ::CreateWindowExW(0,
                             s_szClassName, szFileName,
                             WS_OVERLAPPEDWINDOW,
                             0, 0,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             NULL, NULL, GetModuleHandle(NULL), NULL);

    CHAR    szPlugin[MaxPLUGINFILENAME+4];
    // 指定ファイルを処理可能なプラグインを検索します
    memset(&m_strInfo, 0, sizeof(m_strInfo));   //2004.04.30 add
    if(!_PlbAnalyzeFile(HWND_DESKTOP, cszFileNameA, &m_strInfo, szPlugin)) {
        return 0;   // 処理可能なプラグインを発見できなかった場合!
    }

    // 2004.04.30 ふぃろ
    //  m_strInfo に szDisplay メンバーが追加されました。
    //  szDisplayには、アスペクト比を意識した表示サイズがセットされます。
    //  ただし、旧バージョンのPsxMCはこれをセットしないのでここで補完します。
#if 1
    if(!m_strInfo.szDisplay.cx || !m_strInfo.szDisplay.cy) {
        m_strInfo.szDisplay = m_strInfo.szFrame;
    }
#endif

    // PsxMCのプレイヤーをオープンします
    //  最後のパラメタは、拡大/縮小表示時の品質を指定しています。
    //  LBR_STRETCHLEVEL_HQは、最高品質ですが処理が重いです。
    //  LBR_STRETCHLEVEL_NORMALにすると、軽くなりますが品質は低いです。
#if 1
    SetWindowLongPtrW(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
    SetMovieSize(m_strInfo.szDisplay.cx, m_strInfo.szDisplay.cy);   //2004.04.30 changed
#else
    SetMovieSize(m_strInfo.szFrame.cx, m_strInfo.szFrame.cy);
#endif
    m_hPlayer = _PlbOpenPlayer(szPlugin, cszFileNameA, &m_strInfo,
                                m_hWnd, NULL, 1000, LBR_STRETCHLEVEL_HQ);
    if(!m_hPlayer)  return 0;   // エラー!
    m_MediaInfo.dwSampleRate = m_strInfo.nSamplesPerSec;
    m_MediaInfo.nBitsPerSample = m_strInfo.wBitsPerSample;
    m_MediaInfo.dwChannels      = m_strInfo.wChannels;
    m_MediaInfo.dwVideoWidth    = m_strInfo.szDisplay.cx;  //2004.04.30 changed
    m_MediaInfo.dwVideoHeight   = m_strInfo.szDisplay.cy;  //2004.04.30 changed
    DWORD dwLength;
    _PlbGetPlayPosition(m_hPlayer, &dwLength);      // メディア長取得
    m_MediaInfo.qwLength = (UINT64)dwLength * 10000;
    m_pFile = pFile;
    pFile->AddRef();
    m_MediaInfo.dwCount = 1;
    m_MediaInfo.dwNumber = 1;
    m_MediaInfo.dwFormatType = KPI_MEDIAINFO::FORMAT_PLAYER;
    return 1;
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbCstrPlayer::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;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbCstrPlayer::Play(DWORD dwStartpos, BOOL bVisible)
{
    SetVisible(bVisible);
    _PlbSeekPlayer(m_hPlayer, dwStartpos);  // 再生開始位置にシーク
    _PlbPlayPlayer(m_hPlayer);       // 再生開始
    return TRUE;
 }
//////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbCstrPlayer::Pause(void)
{
    return _PlbPausePlayer(m_hPlayer);  // 一時停止
}
//////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbCstrPlayer::Restart(void)
{
    return _PlbResumPlayer(m_hPlayer);  // 再開
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::Stop(void)
{
    _PlbStopPlayer(m_hPlayer);          // 停止
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbCstrPlayer::GetPosition(void)
{
    return _PlbGetPlayPosition(m_hPlayer, NULL);    // 再生位置取得
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbCstrPlayer::GetStatus(void)
{
    DWORD dwStatus = _PlbGetPlayerStatus(m_hPlayer);
    switch(dwStatus) {
        case PLAYSTAT_CLOSE:
        case PLAYSTAT_STOP:
        case PLAYSTAT_EOD:
        case PLAYSTAT_EXIT:
            return KPI_STATUS_STOP;
        case PLAYSTAT_PAUSE:
            return KPI_STATUS_PAUSE;
        case PLAYSTAT_PLAY:
            return KPI_STATUS_PLAYING;
    }
    return KPI_STATUS_STOP;
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::SetTempo(int Tempo)
{
    _PlbSetTempo(m_hPlayer, Tempo);
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::SetVolume(int Volume)
{
    _PlbSetVolume(m_hPlayer, Volume);
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::SetVisible(BOOL bVisible)
{
    if(m_hWnd){
        ShowWindow(m_hWnd, bVisible ? SW_SHOWNORMAL : SW_HIDE);
    }
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall KbCstrPlayer::SetMovieSize(int nWidth, int nHeight)
{
    nWidth   += ::GetSystemMetrics(SM_CXSIZEFRAME) * 2;
    nHeight  += ::GetSystemMetrics(SM_CYSIZEFRAME) * 2;
    nHeight  += ::GetSystemMetrics(SM_CYCAPTION);
    SetWindowPos(m_hWnd, HWND_TOP, 0, 0, nWidth, nHeight, SWP_NOMOVE);
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::SetTopMost(BOOL bTopMost)
{
    SetWindowPos(m_hWnd, bTopMost ? HWND_TOPMOST : HWND_NOTOPMOST,
                    0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::GetWindowPosition(int *pLeft, int *pTop,
                                            int *pVideoWidth, int *pVideoHeight)
{
    HWND hWnd = m_hWnd;
    if(hWnd) {
        WINDOWPLACEMENT wp;//最大化していないときのウィンドウサイズ
        wp.length = sizeof(WINDOWPLACEMENT);
        GetWindowPlacement(hWnd, &wp);
        RECT rect;
        // デスクトップ作業領域を取得
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
        OffsetRect(&wp.rcNormalPosition, rect.left, rect.top);
        *pLeft = wp.rcNormalPosition.left;
        *pTop =  wp.rcNormalPosition.top;
        *pVideoWidth = wp.rcNormalPosition.right - wp.rcNormalPosition.left - GetSystemMetrics(SM_CXFRAME) * 2;
        *pVideoHeight = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top - (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME) * 2);
    }
    else{
        *pLeft = *pTop = 0;
        *pVideoWidth = *pVideoHeight = 0;
    }
}
//////////////////////////////////////////////////////////////////////////////
void WINAPI KbCstrPlayer::SetWindowPosition(int nLeft, int nTop,
                                            int nVideoWidth, int nVideoHeight)
{
    HWND hWnd = m_hWnd;
    if(hWnd) {
        nVideoWidth   += ::GetSystemMetrics(SM_CXSIZEFRAME) * 2;
        nVideoHeight  += ::GetSystemMetrics(SM_CYSIZEFRAME) * 2;
        nVideoHeight  += ::GetSystemMetrics(SM_CYCAPTION);
        SetWindowPos(hWnd, HWND_TOP, nLeft, nTop, nVideoWidth, nVideoHeight, 0);
    }
}
//////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbCstrPlayer::CstrWindowProc(HWND hWnd, UINT msg,
                                              WPARAM wp, LPARAM lp)
{
    KbCstrPlayer *pThis = (KbCstrPlayer*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if(!pThis){
        return DefWindowProcW(hWnd, msg, wp, lp);
    }
    return pThis->CstrWindowProcInternal(hWnd, msg, wp, lp);
}
//////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK KbCstrPlayer::CstrWindowProcInternal(HWND hwnd, UINT msg,
                                                      WPARAM wp, LPARAM lp)
{
    HPLAYER hPlayer = m_hPlayer;
    RECT        rc;

    switch(msg) {
        case WM_CLOSE:
            ShowWindow(hwnd, SW_HIDE);
            return 0;
        case WM_SIZING:{
            CheckSize(wp, lp, m_MediaInfo.dwVideoWidth, m_MediaInfo.dwVideoHeight);
            break;
        }
        case WM_SIZE:
            if(hPlayer){
                SIZE        szSrc, szView, szDisp;
                POINT       pt;
                szSrc.cx = m_MediaInfo.dwVideoWidth;//rc.right - rc.left;
                szSrc.cy = m_MediaInfo.dwVideoHeight;//rc.bottom - rc.top;
                szView.cx = LOWORD(lp);
                szView.cy = HIWORD(lp);
                if(szSrc.cx < 1 || szSrc.cy < 1) {
                    break;
                }
                if(szView.cx < 1 || szView.cy < 1){
                    break;
                }
                /*
                if((double)szView.cx / (double)szSrc.cx >
                   (double)szView.cy / (double)szSrc.cy) {
                    szDisp.cx = szSrc.cx * szView.cy / szSrc.cy;
                    szDisp.cy = szView.cy;
                } else {
                    szDisp.cx = szView.cx;
                    szDisp.cy = szSrc.cy * szView.cx / szSrc.cx;
                }*/
                if(szView.cx * szSrc.cy > szView.cy * szSrc.cx) {
                    szDisp.cx = szSrc.cx * szView.cy / szSrc.cy;
                    szDisp.cy = szView.cy;
                } else {
                    szDisp.cx = szView.cx;
                    szDisp.cy = szSrc.cy * szView.cx / szSrc.cx;
                }
                if(szDisp.cx < 1 || szDisp.cy < 1){
                    break;
                }
                pt.x = (LOWORD(lp) - szDisp.cx) / 2;
                pt.y = (HIWORD(lp) - szDisp.cy) / 2;
                rc.left = pt.x;
                rc.top = pt.y;
                rc.right = szDisp.cx + pt.x;
                rc.bottom = szDisp.cy + pt.y;
                _PlbSetPlayRect(hPlayer, &rc, -1);
            }
            break;//return 0;
        case WM_PAINT:
            {
                LPBITMAPINFO    pbmi;
                PAINTSTRUCT     ps;
                HDC             hdc;
                if(pbmi = _PlbLockFrameDIB(hPlayer, NULL)) {
                    hdc = BeginPaint(hwnd, &ps);
                    SetStretchBltMode(hdc, COLORONCOLOR);
                    _PlbGetPlayRect(hPlayer, &rc);
                    StretchDIBits(hdc, rc.left, rc.top,
                            rc.right - rc.left, rc.bottom - rc.top,
                            0, 0,
                            pbmi->bmiHeader.biWidth,
                            pbmi->bmiHeader.biHeight,
                            pbmi->bmiColors, pbmi,
                            DIB_RGB_COLORS, SRCCOPY);
                    EndPaint(hwnd, &ps);
                    _PlbUnlockFrameDIB(hPlayer);
                    return 0;
                }
            }
            break;
    }
    return DefWindowProcW(hwnd, msg, wp, lp);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall KbCstrPlayer::Init(void)
{
    if(!s_szClassName[0]){
        LARGE_INTEGER counter = {0};
        QueryPerformanceCounter(&counter);
        _snwprintf_s(s_szClassName, sizeof(s_szClassName) / sizeof(s_szClassName[0]),
                     _TRUNCATE, L"%s%08x:%08x:%016llx",
                     g_cszKbCstrWindowClassName, GetCurrentThreadId(), GetTickCount(), counter.QuadPart);
        WNDCLASSEXW  wc;
        wc.cbSize       = sizeof(wc);
        wc.style        = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc  = CstrWindowProc;
        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= s_szClassName;
        wc.hIconSm      = ::LoadIcon(NULL, IDI_WINLOGO);
        RegisterClassExW(&wc);
    }
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall KbCstrPlayer::Deinit(void)
{
    if(s_szClassName[0]){
        UnregisterClassW(s_szClassName, GetModuleHandle(NULL));
        s_szClassName[0] = 0;
    }
}
