#include "ctlspriv.h"
#include "rlefile.h"

#define RectWid(_rc)    ((_rc).right-(_rc).left)
#define RectHgt(_rc)    ((_rc).bottom-(_rc).top)

typedef struct {
    HWND        hwnd;                   // my window
    int         id;                     // my id
    HWND        hwndP;                  // my owner (get notify messages)
    DWORD       style;

    BOOL        fFirstPaint;            // TRUE until first paint.
    RLEFILE     *prle;

#ifdef WIN32
    CRITICAL_SECTION    crit;
#endif

    RECT        rc;
    int         NumFrames;
    int         Rate;

    int         iFrame;
    int         PlayCount;
    int         PlayFrom;
    int         PlayTo;
    HANDLE      PaintThread;
    HANDLE      hStopEvent;

}   ANIMATE;

#ifdef WIN32
#define Enter(p)    EnterCriticalSection(&p->crit)
#define Leave(p)    LeaveCriticalSection(&p->crit)
#else
#define Enter(p)
#define Leave(p)
#endif

#define OPEN_WINDOW_TEXT 42
#define Ani_UseThread(p) (!((p)->style & ACS_TIMER))

LRESULT CALLBACK AnimateWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

BOOL HandleOpen(ANIMATE *p, HINSTANCE hInst, LPCTSTR pszName, UINT flags);
BOOL HandleStop(ANIMATE *p);
BOOL HandlePlay(ANIMATE *p, int from, int to, int count);
void HandlePaint(ANIMATE *p, HDC hdc);
int  HandleTick(ANIMATE *p);

#pragma code_seg(CODESEG_INIT)

TCHAR c_szAnimateClass[] = ANIMATE_CLASS;

BOOL FAR PASCAL InitAnimateClass(HINSTANCE hInstance)
{
    WNDCLASS wc;

    if (!GetClassInfo(hInstance, c_szAnimateClass, &wc)) {
#ifndef WIN32
        extern LRESULT CALLBACK _AnimateWndProc(HWND, UINT, WPARAM, LPARAM);
        wc.lpfnWndProc   = _AnimateWndProc;
#else
        wc.lpfnWndProc   = AnimateWndProc;
#endif
        wc.lpszClassName = c_szAnimateClass;
        wc.style         = CS_DBLCLKS | CS_GLOBALCLASS;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = sizeof(LPVOID);
        wc.hInstance     = hInstance;       // use DLL instance if in DLL
        wc.hIcon         = NULL;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
        wc.lpszMenuName  = NULL;

        if (!RegisterClass(&wc))
            return FALSE;
    }

    return TRUE;
}
#pragma code_seg()

BOOL HandleOpen(ANIMATE *p, HINSTANCE hInst, LPCTSTR pszName, UINT flags)
{
    TCHAR ach[MAX_PATH];

    //
    // use window text as file name
    //
    if (flags == OPEN_WINDOW_TEXT)
    {
        GetWindowText(p->hwnd, ach, ARRAYSIZE(ach));
        pszName = ach;
    }

    if (hInst == NULL)
        hInst = (HINSTANCE)GetWindowLongPtr(p->hwnd, GWLP_HINSTANCE);

    HandleStop(p);              // stop a play first

    if (p->prle)
    {
        RleFile_Free(p->prle);
        p->prle = NULL;
    }

    p->iFrame = 0;
    p->NumFrames = 0;

    if (pszName == NULL || (!IS_INTRESOURCE(pszName) && *pszName == 0))
        return FALSE;
    //
    //  now open the file/resource we got.
    //
    p->prle = RleFile_New();

    if (p->prle == NULL)
        return FALSE;

    if (!RleFile_OpenFromResource(p->prle, hInst, pszName, TEXT("AVI")) &&
        !RleFile_OpenFromFile(p->prle, pszName))
    {
        RleFile_Free(p->prle);
        p->prle = NULL;
        return FALSE;
    }
    else
    {
        p->NumFrames = RleFile_NumFrames(p->prle);
        p->Rate = (int)RleFile_Rate(p->prle);
        SetRect(&p->rc, 0, 0, RleFile_Width(p->prle), RleFile_Height(p->prle));
    }

    //
    // handle a transparent color
    //
    if ((p->style & ACS_TRANSPARENT) && p->hwndP)
    {
        HDC hdc;
        HDC hdcM;
        HBITMAP hbm;
        COLORREF rgbS, rgbD;

        hdc = GetDC(p->hwnd);

        //
        //  create a bitmap and draw image into it.
        //  get upper left pixel and make that transparent.
        //
        hdcM= CreateCompatibleDC(hdc);
        hbm = CreateCompatibleBitmap(hdc, 1, 1);
        SelectObject(hdcM, hbm);
        HandlePaint(p, hdcM);
        rgbS = GetPixel(hdcM, 0, 0);
        DeleteDC(hdcM);
        DeleteObject(hbm);

        SendMessage(p->hwndP, GET_WM_CTLCOLOR_MSG(CTLCOLOR_STATIC),
            GET_WM_CTLCOLOR_MPS(hdc, p->hwnd, CTLCOLOR_STATIC));

        rgbD = GetBkColor(hdc);


        ReleaseDC(p->hwnd, hdc);

        //
        // now replace the color
        //
        RleFile_ChangeColor(p->prle, rgbS, rgbD);
    }

    //
    //  ok it worked, resize window.
    //
    if (p->style & ACS_CENTER)
    {
        RECT rc;
        GetClientRect(p->hwnd, &rc);
        OffsetRect(&p->rc, (rc.right-p->rc.right)/2,(rc.bottom-p->rc.bottom)/2);
    }
    else
    {
        RECT rc;
        rc = p->rc;
        AdjustWindowRectEx(&rc, GetWindowStyle(p->hwnd), FALSE, GetWindowExStyle(p->hwnd));
        SetWindowPos(p->hwnd, NULL, 0, 0, RectWid(rc), RectHgt(rc),
            SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
    }

    if (p->style & ACS_AUTOPLAY)
    {
        PostMessage(p->hwnd, ACM_PLAY, (UINT_PTR)-1, MAKELONG(0, -1));
    }
    else
    {
        InvalidateRect(p->hwnd, NULL, TRUE);
    }

    return TRUE;
}

void DoNotify(ANIMATE *p, int cmd)
{
    if (p->hwndP)
        PostMessage(p->hwndP, WM_COMMAND, GET_WM_COMMAND_MPS(p->id, p->hwnd, cmd));
}

BOOL HandleStop(ANIMATE *p)
{
    if (p == NULL || !p->PaintThread)
        return FALSE;

    if (Ani_UseThread(p)) {
        // set thread up to terminate between frames
        Enter( p );
        p->PlayCount = 0;
        Leave( p );
        if (p->hStopEvent)
            SetEvent(p->hStopEvent);
        WaitForSingleObject(p->PaintThread, INFINITE);
        // PORT QSY  RAID 4167
        // Under certain situations, both the CloseHandle()
        // and ExitThread() call try to remove MainWin internal
        // objects.
        // This is a work-around for preview 1.
        // I've raised another bug RAID  4250 for OE RTW 
#ifndef UNIX
        CloseHandle(p->PaintThread);
#endif
        // PORT QSY
        p->PaintThread = NULL;
        if (p->hStopEvent)
            CloseHandle(p->hStopEvent);
        p->hStopEvent = NULL;
    } else {
        KillTimer(p->hwnd, HandleToUlong(p->PaintThread)); // really was a UINT
        p->PaintThread = 0;
        DoNotify(p, ACN_STOP);
    }
    return TRUE;
}

int PlayThread(ANIMATE *p)
{
    int result;
    
    DoNotify(p, ACN_START);

    while (result = HandleTick(p))
    {
        // Sleep for a bit (4 seconds) longer if we are hidden
        //
        // Old code here slept, which can block the UI thread
        // if the app tries to stop/shutdown/change the animation
        // right near the beginning of the sleep.
        //        Sleep((result < 0 ? p->Rate+4000 : p->Rate));
        // Do a timed wait for the stop event instead
        //
        if (p->hStopEvent)
            WaitForSingleObject(p->hStopEvent, (result < 0 ? p->Rate+4000 : p->Rate));
        else
            Sleep((result < 0 ? p->Rate+4000 : p->Rate));
    }

    DoNotify(p, ACN_STOP);
    return 0;
}

BOOL HandlePlay(ANIMATE *p, int from, int to, int count)
{
    if (p == NULL || p->prle == NULL)
        return FALSE;

    HandleStop(p);

    if (from >= p->NumFrames)
        from = p->NumFrames-1;

    if (to == -1)
        to = p->NumFrames-1;

    if (to < 0)
        to = 0;

    if (to >= p->NumFrames)
        to = p->NumFrames-1;

    p->PlayCount = count;
    p->PlayTo    = to;
    if (from >= 0) {
        p->iFrame = from;
        p->PlayFrom  = from;
    } else
        from = p->PlayFrom;

    if ( (from == to) || !count )
    {
        InvalidateRect(p->hwnd, NULL, TRUE);
        return TRUE;
    }

    InvalidateRect(p->hwnd, NULL, FALSE);
    UpdateWindow(p->hwnd);

    if (Ani_UseThread(p))
    {
        DWORD dw;
        p->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        p->PaintThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlayThread, (void*)p, 0, &dw);
    }
    else
    {
        DoNotify(p, ACN_START);
        p->PaintThread = (HANDLE)SetTimer(p->hwnd, 42, (UINT)p->Rate, NULL);
    }
    return TRUE;
}

void HandleFirstPaint(ANIMATE *p)
{
    if (p->fFirstPaint)
    {
        p->fFirstPaint = FALSE;

        if (p->NumFrames == 0 &&
            (p->style & WS_CHILD))
        {
            HandleOpen(p, NULL, NULL, OPEN_WINDOW_TEXT);
        }
    }
}

void HandlePaint(ANIMATE *p, HDC hdc)
{
    if( p && p->prle )
    {
        Enter( p );
        RleFile_Paint( p->prle, hdc, p->iFrame, p->rc.left, p->rc.top );
        Leave( p );
    }
}

void HandleErase(ANIMATE * p, HDC hdc)
{
    HBRUSH hbr;
    RECT rc;

    hbr = (HBRUSH)SendMessage(p->hwndP, GET_WM_CTLCOLOR_MSG(CTLCOLOR_STATIC),
        GET_WM_CTLCOLOR_MPS(hdc, p->hwnd, CTLCOLOR_STATIC));
    GetClientRect(p->hwnd, &rc);
    FillRect(hdc, &rc, hbr);
}

void HandlePrint(ANIMATE *p, HDC hdc)
{
    HandleFirstPaint(p);
    HandlePaint(p, hdc);
}

int HandleTick(ANIMATE *p)
// - if something to do but we are hidden
// returns 0 if nothing left
// + if something to do
{
    int result = 0;

    if( p && p->prle )
    {
        HDC hdc;
        RECT dummy;

        Enter( p );
        hdc = GetDC( p->hwnd );

        if( GetClipBox( hdc, &dummy ) != NULLREGION )
        {
            // do a full repaint on first frame
            if( p->iFrame == p->PlayFrom )
                HandlePaint( p, hdc );
            else
                RleFile_Draw( p->prle, hdc, p->iFrame, p->rc.left, p->rc.top );

            if( p->iFrame >= p->PlayTo )
            {
                if( p->PlayCount > 0 )
                    p->PlayCount--;

                if( p->PlayCount != 0 )
                    p->iFrame = p->PlayFrom;
            }
            else
                p->iFrame++;


            // Something to do? and visible, return + value
            result = ( p->PlayCount != 0 );
        }
        else
        {
            // Something to do? but hidden, so return - value
            p->iFrame = p->PlayFrom;

            result = -( p->PlayCount != 0 );
        }

        ReleaseDC( p->hwnd, hdc );
        Leave( p );
    }

    return result;
}

void NEAR Ani_OnStyleChanged(ANIMATE* p, WPARAM gwl, LPSTYLESTRUCT pinfo)
{
    if (gwl == GWL_STYLE) {
        p->style = pinfo->styleNew;
    }
}

LRESULT CALLBACK AnimateWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    ANIMATE *p = (ANIMATE *)GetWindowPtr(hwnd, 0);
    HDC hdc;
    PAINTSTRUCT ps;

    // First, the messages that can handle p == NULL.
    // All these handlers must end with a "return" or a "goto DoDefault".

    switch (msg) {
    case WM_NCCREATE:

        #define lpcs ((LPCREATESTRUCT)lParam)

        p = (ANIMATE *)LocalAlloc(LPTR, sizeof(ANIMATE));

        if (!p)
            return 0;       // WM_NCCREATE failure is 0

        // note, zero init memory from above
        p->hwnd = hwnd;
        p->hwndP = lpcs->hwndParent;
        p->id = PtrToUlong(lpcs->hMenu);        // really was an int
        p->fFirstPaint = TRUE;
        p->style = lpcs->style;

        // Must do this before SetWindowBits because that will recursively
        // cause us to receive WM_STYLECHANGED and possibly even WM_SIZE
        // messages.
#ifdef WIN32
        InitializeCriticalSection(&p->crit);
#endif

        SetWindowPtr(hwnd, 0, p);

        //
        // UnMirror the control, if it is mirrored. We shouldn't mirror
        // a movie! [samera]
        //
        SetWindowBits(hwnd, GWL_EXSTYLE, RTL_MIRRORED_WINDOW, 0);

        goto DoDefault;

    case WM_CLOSE:
        Animate_Stop(hwnd);
        goto DoDefault;

    case WM_NCHITTEST:
        return HTTRANSPARENT;

    case WM_GETOBJECT:
        if( lParam == OBJID_QUERYCLASSNAMEIDX )
            return MSAA_CLASSNAMEIDX_ANIMATE;
        goto DoDefault;
    }

    // Okay, now the messages that cannot handle p == NULL.
    // We check p == NULL once and for all.

    if (!p) goto DoDefault;

    switch (msg) {
    case WM_DESTROY:
        Animate_Close(hwnd);
        DeleteCriticalSection(&p->crit);
        LocalFree((HLOCAL)p);
        SetWindowPtr(hwnd, 0, 0);
        break;

    case WM_ERASEBKGND:
        HandleErase(p, (HDC)wParam);
        return(1);

    case WM_PAINT:
        HandleFirstPaint(p);
        hdc = BeginPaint(hwnd, &ps);
        HandlePaint(p, hdc);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_PRINTCLIENT:
        HandlePrint(p, (HDC)wParam);
        return 0;

    case WM_STYLECHANGED:
        Ani_OnStyleChanged(p, wParam, (LPSTYLESTRUCT)lParam);
        return 0L;
        
    case WM_SIZE:
        if (p->style & ACS_CENTER)
        {
            OffsetRect(&p->rc, (LOWORD(lParam)-RectWid(p->rc))/2-p->rc.left,
                       (HIWORD(lParam)-RectHgt(p->rc))/2-p->rc.top);
            InvalidateRect(hwnd, NULL, TRUE);
        }
        break;

    case WM_TIMER:
    {
        int result;
        result = HandleTick(p);
        if (!result)
        {
            HandleStop(p);
        }
        else if (result < 0)
        {
            p->PaintThread = (HANDLE)SetTimer(p->hwnd, 42, (UINT)p->Rate+4000, NULL);
        } else
        {
            p->PaintThread = (HANDLE)SetTimer(p->hwnd, 42, (UINT)p->Rate, NULL);
        }
    }
        break;

#ifdef UNICODE
    case ACM_OPENA:
    {
        WCHAR szFileNameW[MAX_PATH];
        LPTSTR lpFileName = szFileNameW;

        if (!IS_INTRESOURCE(lParam)) {
            MultiByteToWideChar (CP_ACP, 0, (LPCSTR)lParam, -1,
                                 szFileNameW, MAX_PATH);
        } else {
            lpFileName = (LPTSTR) lParam;
        }
        
        return HandleOpen(p, (HINSTANCE)wParam, lpFileName, 0);
    }
#endif

    case ACM_OPEN:
        return HandleOpen(p, (HINSTANCE)wParam, (LPCTSTR)lParam, 0);

    case ACM_STOP:
        return HandleStop(p);

    case ACM_PLAY:
        return HandlePlay(p, (int)(SHORT)LOWORD(lParam), (int)(SHORT)HIWORD(lParam), (int)wParam);

    }

DoDefault:
    return DefWindowProc(hwnd, msg, wParam, lParam);
}
