/******************************************************************************

   Copyright (C) Microsoft Corporation 1985-1991. All rights reserved.

   Title:   avitask.c - Background task that actually manipulates AVI files.

*****************************************************************************/
#include "graphic.h"

void FAR PASCAL DebugBreak(void);

BOOL FAR PASCAL mciaviCloseFile(NPMCIGRAPHIC npMCI);
BOOL FAR PASCAL mciaviOpenFile(NPMCIGRAPHIC npMCI);

#if defined(WIN32) || !defined(DEBUG)
#define StackTop()      (void *)0
#define StackMin()      (void *)0
#define StackBot()      (void *)0
#define StackMark()
#define StackTest()     TRUE
#else
#define STACK           _based(_segname("_STACK"))
#define StackTop()      *((UINT STACK *)10)
#define StackMin()      *((UINT STACK *)12)
#define StackBot()      *((UINT STACK *)14)
#define StackMark()     *((UINT STACK*)StackBot()) = 42
#define StackTest()     *((UINT STACK*)StackBot()) == 42
#endif


/***************************************************************************
 ***************************************************************************/

#ifndef WIN32
#pragma optimize("", off)
void FAR SetPSP(UINT psp)
{
    _asm {
        mov bx,psp
        mov ah,50h
        int 21h
    }
}
#pragma optimize("", on)
#endif

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api void | mciaviTask |  This function is the background task which plays
 *      AVI files. It is called as a result of the call to mmTaskCreate()
 *      in DeviceOpen(). When this function returns, the task is destroyed.
 *
 * @parm DWORD | dwInst | instance data passed to mmCreateTask - contains
 *      a pointer to an instance data block.
 *
 ***************************************************************************/

void FAR PASCAL _LOADDS mciaviTask(DWORD dwInst)
{
    NPMCIGRAPHIC npMCI;

    npMCI = (NPMCIGRAPHIC) dwInst;

    // Set this task's error mode to the same as the parent's.
    SetErrorMode(npMCI->uErrorMode);

    DPF2(("MCIAVI: Bkgd Task hTask=%04X\n", GetCurrentTask()));
    DPF2(("MCIAVI: Stack: %04X %04X %04X\n", StackTop(), StackMin(), StackBot()));

    /* Task state is TASKBEINGCREATED at fn. entry, then goes to TASKINIT. */

    Assert(npMCI && npMCI->wTaskState == TASKBEINGCREATED);

    npMCI->wTaskState = TASKINIT;

#ifndef WIN32
    //
    // in order to make this task, more like a "thread" we want to use the
    // same PSP as our parent, so we can share file handles and things.
    //
    // when we get created hTask is a PSP
    //
    npMCI->pspTask = GetCurrentPDB();   // save our PSP
#endif

    npMCI->hTask = GetCurrentTask();
    npMCI->dwTaskError = 0;

    /* Open the file  */

    if (!mciaviOpenFile(npMCI)) {
        // NOTE: IsTask() returns FALSE when hTask==0
        // Set hTask to 0 BEFORE setting wTaskState.  Our creator is polling
        // the state of wTaskState...
        // npMCI->wTaskState = TASKABORT;
        // npMCI->hTask = 0; // This stops others using this task thread.
        DPF1(("Failed to open AVI file\n"));
        goto exit;
    }

    while (IsTask(npMCI->hTask)) {

        npMCI->wTaskState = TASKIDLE;
        DPF2(("MCIAVI: Idle\n"));
        DPF2(("MCIAVI: Stack: %04X %04X %04X\n", StackTop(), StackMin(), StackBot()));

        StackMark();

        /* Block until task is needed. The task count could */
        /* be anything at the exit of playfile or recordfile */
        /* so continue to block until the state really changes. */

        while (npMCI->wTaskState == TASKIDLE)
        {
            mmTaskBlock(npMCI->hTask);
        }

        mciaviMessage(npMCI, npMCI->wTaskState);

        AssertSz(StackTest(), "Stack overflow");

        if (npMCI->wTaskState == TASKCLOSE) {
            break;
        }

    }
exit:
    mciaviTaskCleanup(npMCI);
}

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api WORD | mciaviTaskCleanup |  called when the background task
 *      is being destroyed.  This is where critical cleanup goes.
 *
 ***************************************************************************/

void FAR PASCAL mciaviTaskCleanup(NPMCIGRAPHIC npMCI)
{

#ifndef WIN32
    //
    // restore our PSP back to normal before exit.
    //
    if (npMCI->pspTask)
    {
        SetPSP(npMCI->pspTask);
    }
#endif

#ifdef USEAVIFILE
    //
    // we must do this so COMPOBJ will shut down right.
    //
    FreeAVIFile(npMCI);
#endif

    //
    //  call a MSVideo shutdown routine.
    //

    //
    //  Signal the foreground task that we're all done.
    //  This must be absolutely the last thing we do.
    //
    npMCI->hTask = 0;
    npMCI->wTaskState = TASKCLOSED;
}

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api void | mciaviMessage | this function handles a message from the
 *      background task.
 *
 ***************************************************************************/

void NEAR PASCAL mciaviMessage(NPMCIGRAPHIC npMCI, UINT msg)
{
    UINT  wNotification;

    switch (msg) {

    case TASKREADINDEX:
        Assert(0);
        break;

    /* Check to see if we just got closed */

    case TASKCLOSE:
        DPF1(("MCIAVI: Closing\n"));

	// hold critsec during close in case someone comes in to
	// eg DeviceRealize during the close when things are half-deleted.
	EnterCrit(npMCI);
        mciaviCloseFile(npMCI);
	LeaveCrit(npMCI);

        /* The htask must be set to NULL, otherwise CloseDevice() will  */
        /* get stuck. */

        // NOTE: IsTask() returns FALSE when hTask==0
        // npMCI->hTask = 0;
        // npMCI->wTaskState = TASKABORT;
        return;

    case TASKRELOAD:
 	DPF(("MCIAVI: Loading new file....\n"));
 	mciaviCloseFile(npMCI);
 	npMCI->dwTaskError = 0;
	npMCI->wTaskState = TASKINIT;

 	if (!mciaviOpenFile(npMCI)) {
	    // !!! mciaviOpenNew() !!!!!!!!!!!!!!!!!!!!!
	    npMCI->wTaskState = TASKCLOSE;
	    // npMCI->hTask = 0;
	    return;
	}
	break;

	// We've been woken up to play....
    case TASKSTARTING:
        DPF2(("MCIAVI: Now busy\n"));

        /* Reset to no error */
        npMCI->dwTaskError = 0;

        wNotification = mciaviPlayFile(npMCI);

        if ((wNotification != MCI_NOTIFY_FAILURE) ||
                ((npMCI->dwFlags & MCIAVI_WAITING) == 0))
            GraphicDelayedNotify(npMCI, wNotification);

        break;

    default:
        DPF(("MCIAVI: Unknown task state!!!! (%d)\n", msg));
        break;
    }
}

#ifdef WIN32

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api int | GetPrioritySeparation | Find the foreground process priority
 *    boost
 *
 * @rdesc Returns 0, 1 or 2
 *
 ***************************************************************************/

 DWORD GetPrioritySeparation(void)
 {
     static DWORD Win32PrioritySeparation = 0xFFFFFFFF;

     /* If we're not initialized get the current separation */

     if (Win32PrioritySeparation == 0xFFFFFFFF) {
         HKEY hKey;
         Win32PrioritySeparation = 2;  // This is the default

         /* Code copied from shell\control\main\prictl.c */

         if (RegOpenKeyEx(
                 HKEY_LOCAL_MACHINE,
                 TEXT("SYSTEM\\CurrentControlSet\\Control\\PriorityControl"),
                 0,
                 KEY_QUERY_VALUE,
                 &hKey) == ERROR_SUCCESS) {

             DWORD Type;
             DWORD Length;

             Length = sizeof(Win32PrioritySeparation);

             /* Read the value which is the priority boost given to
                forground processes */

             if (RegQueryValueEx(
                      hKey,
                      TEXT("Win32PrioritySeparation"),
                      NULL,
                      &Type,
                      (LPBYTE)&Win32PrioritySeparation,
                      &Length
                      ) != ERROR_SUCCESS) {

                  Win32PrioritySeparation = 2;
             }

             RegCloseKey(hKey);
         }
     }

     return Win32PrioritySeparation;
 }
 #endif // WIN32

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api void| aviTaskYield |  This function yields in the picky way windows
 *      wants us to.
 *
 *      basicly we Dispatch any messages in our que that belong to a window.
 *
 *      NOTE we should not remove
 *
 ***************************************************************************/

void NEAR PASCAL aviTaskYield(void)
{
    MSG msg;

#ifdef WIN32
    DWORD PrioritySeparation;

    //
    //  Do our own kind of 'yield'.  The reason for doing the
    //  Peekmessage on Windows 3.1 was that if you didn't call
    //  it Windows would think you were spinning out of control.
    //  For Windows NT if you call PeekMessage 100 times without
    //  getting anything your priority is lowered which would mess
    //  up our tinkering with the priority here.
    //

    PrioritySeparation = GetPrioritySeparation();

    if (PrioritySeparation != 0) {
        SetThreadPriority(GetCurrentThread(),
                          PrioritySeparation == 1 ?
                              THREAD_PRIORITY_BELOW_NORMAL :  // minus 1
                              THREAD_PRIORITY_LOWEST);        // minus 2
        Sleep(0);    // Causes reschedule decision
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
    } else {
        Sleep(0);    // Let other threads in
    }

#else

    //
    // if we were MCIWAVE we would do this....
    //
    //if (PeekMessage(&msg, NULL, 0, WM_MM_RESERVED_FIRST-1, PM_REMOVE))

    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        DPF(("aviTaskYield: got message %04X to window %04X\n", msg.message, msg.hwnd));
        DispatchMessage(&msg);
    }
#endif // WIN32
}