// #define DEBUG
#define VCAI_ENABLED
#define DIVE_ENABLED
#define VCAI_MONITOR

#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_GPI
#define INCL_WIN
#define INCL_32

#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define  _MEERROR_H_
#include <mmioos2.h>                   /* It is from MMPM toolkit           */
#include <dive.h>
#include <fourcc.h>
#include <vcadd.h>
#include <vcai.h>

#include "dive.h"
#include "pmtv2rem.h"

#define WIDTH   (300)
#define HEIGHT  (240)

HWND        HwndNBFrame;            // Notebook frame handle
BOOL        NotebookOpen = FALSE;   // Notebook not open

void remote_open (MOUSEPOS mousepos);

PWINDATA DiveSetup (char *name, USHORT instance, USHORT connector);
int DiveRegister (HAB hab);
int DiveStop (void);

static void msg_box (UCHAR *txt, UCHAR *title, ULONG options);

static WINDATA      WTV_data;
static DIVE_CAPS    DiveCaps = {0};         // DIVE capabilities of system
static FOURCC       fccFormats[100] = {0};  // Color format code
static SETUP_BLITTER    SetupBlitter;       // structure for blitter ops.


/* This function will display a TV in the window using DIVE.  The basic     *
 * theory is that the WinCast board will be continually capturing into the  *
 * buffer and DIVE will blit the image onto the screen.  The blitter is     *
 * setup in the main thread of the program where window details are known.  */
VOID APIENTRY DiveTV (ULONG data_addr)
{
PWINDATA    win_data = NULL;        /* Structure holding window information */
UCHAR      *tv_image = NULL;        /* Capture buffer for TV image          */
size_t      tv_size = 0;            /* Size of capture buffer               */
ULONG       rc = 0;                 /* DIVE error return code               */
ULONG       dive_srcbuf_no = 0;     /* DIVE source buffer number            */
ULONG       line_len = 0;
ULONG       scanline_bytes = 0;
ULONG       scanlines = 0;
PBYTE       dive_buf_addr = 0;
RECTL       dive_rect;

    /* Set the window information and check it's valid.                     */
    if ((win_data = (PWINDATA)data_addr) == NULL)
    {
        DosExit (EXIT_PROCESS, 1);
        return;
    }

    /* Wait until we are permitted to proceed                               */
    while (win_data->win_blit_halt)
        DosSleep (100);

    /* Allocate a capture buffer for the image and declare it to DIVE       */
    tv_size = (size_t)(win_data->win_dive_width * win_data->win_dive_height * 4);
    rc = DosAllocMem ((PPVOID)&tv_image, tv_size, (PAG_COMMIT | PAG_WRITE));
    if (rc != NO_ERROR)
    {
        DosExit (EXIT_PROCESS, 1);
        return;
    }

#ifdef DIVE_ENABLED
    line_len = (win_data->win_dive_width << 1);
    line_len = (line_len - (line_len % 4));
    rc = DiveAllocImageBuffer (win_data->win_dive_handle,
                               &dive_srcbuf_no, FOURCC_Y422,
                               win_data->win_dive_width,
                               win_data->win_dive_height,
                               0,                       // DIVE sets line len
                               (PBYTE)tv_image);
    if (rc)
    {
        DosExit (EXIT_PROCESS, 1);
        return;
    }
    dive_rect.xLeft = 0;
    dive_rect.yTop = 0;
    dive_rect.xRight = win_data->win_dive_width;
    dive_rect.yBottom = win_data->win_dive_height;
#endif

#ifdef VCAI_ENABLED

    // Connect to the capture board and start the capture.

        VcaiVideoRectValidate (0, 0,
                           win_data->win_dive_width, win_data->win_dive_height,
                           0, 0,
                           win_data->win_dive_width, win_data->win_dive_height);
#ifdef VCAI_MONITOR
        VcaiWCastMonitor (win_data->win_dive_width, win_data->win_dive_height,
                      (ULONG)tv_image);

//      VcaiVideoRectValidate (0, 0,
//                         win_data->win_dive_width, win_data->win_dive_height,
//                         0, 0,
//                         win_data->win_dive_width, win_data->win_dive_height);

        VcaiOverlay (TRUE);
#endif
#endif

        win_data->win_bliting = TRUE;
        while (!win_data->win_blit_halt)
        {
#ifdef DIVE_ENABLED
        DosSleep (30);

        /* Blit the image to the screen.  There may be a need for the DIVE  *
         * buffer (dive_srcbuf_no) to be DiveBeginImageBufferAccess'ed      *
         * before the blit is made.                                         */
            DiveBlitImage (win_data->win_dive_handle,
                       dive_srcbuf_no, DIVE_BUFFER_SCREEN);
#endif
    }

#ifdef VCAI_ENABLED
#ifdef VCAI_MONITOR
        VcaiOverlay (FALSE);

    // This call will stop displaying the image and free the memory that is
    // stuck in the PC RAM
        VcaiWCastMonitor (0, 0, 0L);
#endif

        VcaiVideoInputSet (4);
        VcaiDeviceClose (win_data->win_vcai_handle);
#endif

    /* Free any buffers and undeclare any of the DIVE buffers used          */
#ifdef DIVE_ENABLED
        DiveFreeImageBuffer (win_data->win_dive_handle, dive_srcbuf_no);
#endif
        DosFreeMem (tv_image);

    /* Inform the main thread that all has finished okay.                   */
        win_data->win_bliting = FALSE;
}


MRESULT EXPENTRY TVWindowProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
static PWINDATA win_data = NULL;    /* Pointer to window data               */
HPS             hps;                /* Presentation Space handle            */
HRGN            hrgn;               /* Region handle                        */
RGNRECT         rgnCtl;             /* Processing control structure         */
RECTL           rcl;                /* Current widow rectangle              */
POINTL          pointl;             /* Point to offset from Desktop         */
ULONG           rc;
ULONG           width;              /* Width of the current rectangle       */
MOUSEPOS        mousepos;           /* Position of mouse in window          */

    /* Get the pointer to window data                                       */
    if (win_data == NULL)
    {
        if ((win_data = (PWINDATA)WinQueryWindowULong (hwnd, 0)) == NULL)
            return WinDefWindowProc (hwnd, msg, mp1, mp2);
    }

    /* Handle messages to this window                                       */
    switch (msg)
    {
    case WM_COMMAND:
        switch (SHORT1FROMMP (mp1))
        {
        case ID_FILE_EXIT:
            win_data->win_blit_halt = TRUE;
            DiveSetupBlitter (win_data->win_dive_handle, 0);
            WinPostMsg (hwnd, WM_QUIT, 0L, 0L);
            break;

        case ID_TV_SETUP:
            if (NotebookOpen)
            {
                WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                               (PSZ)"Settings notebook already open",
                               (PSZ)WTV_NAME, 0, MB_OK | MB_INFORMATION);
                break;
            }
            HwndNBFrame = WinLoadSecondaryWindow (HWND_DESKTOP, HWND_DESKTOP,
                                                  (PFNWP)NotebookDlgProc,
                                                  (HMODULE)NULLHANDLE,
                                                  IDD_NOTE,
                                                  NULL);
            break;

        case ID_TV_CON1:
        case ID_TV_CON2:
        case ID_TV_RF:
        case ID_TV_SVHS:
        case ID_TV_TEST:

            if (win_data->win_vcai_handle == 0)
            {
                WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                               (PSZ)"Connector not initialised",
                               (PSZ)WTV_NAME, 0, MB_OK | MB_INFORMATION);
                break;
            }
#ifdef VCAI_ENABLED
            /* Now, alter the connector to the appropriate one              */
            VcaiVideoInputSet (SHORT1FROMMP(mp1)-BASE_CONNECTOR);
#endif
            break;

        default:
            /* Let PM handle this message.                                  */
            return WinDefWindowProc (hwnd, msg, mp1, mp2);
        }
        break;

    case WM_BUTTON1CLICK :
        WinQueryWindowRect (hwnd, &rcl);
        WinMapWindowPoints (hwnd, HWND_DESKTOP, (PPOINTL)&rcl, 2);
        mousepos.mp_x = MOUSEMSG (&msg)->x + rcl.xLeft;
        mousepos.mp_y = MOUSEMSG (&msg)->y + rcl.yBottom;
        remote_open (mousepos);
        break;

    case WM_VRNDISABLED:
#ifdef DIVE_ENABLED
        /* Stop the blitter                                                 */
        DiveSetupBlitter (win_data->win_dive_handle, 0);
#endif
        break;

    case WM_VRNENABLED:
        if ((hps = WinGetPS (hwnd)) == 0)
            break;
        if ((hrgn = GpiCreateRegion (hps, 0L, NULL)) == RGN_ERROR)
        {
            WinReleasePS( hps );
            break;
        }

        /* NOTE: If mp1 is zero, then this was just a move message.     *
         * Illustrate the visible region on a WM_VRNENABLE.             */
        WinQueryVisibleRegion (hwnd, hrgn);
        rgnCtl.ircStart     = 0;
        rgnCtl.crc          = 50;
        rgnCtl.ulDirection  = RECTDIR_LFRT_TOPBOT;

        /* Get the all ORed rectangles                                  */
        rc = (ULONG)GpiQueryRegionRects (hps, hrgn, NULL,
                                         &rgnCtl, win_data->win_rcls);
        if (rc == FALSE)
        {
#ifdef DIVE_ENABLED
            /* Reset the blitter and close                              */
            DiveSetupBlitter (win_data->win_dive_handle, 0);
#endif
            GpiDestroyRegion (hps, hrgn);
            WinReleasePS (hps);
            break;
        }
        win_data->win_rcl_cnt = rgnCtl.crcReturned;

        /* Now find the window position and size, relative 2 parent         *
         * Convert the point to offset from desktop lower left.             */
        WinQueryWindowPos (win_data->win_hwnd_client, &win_data->win_swp);
        pointl.x = win_data->win_swp.x;
        pointl.y = win_data->win_swp.y;

        WinMapWindowPoints (win_data->win_hwnd_frame,
                            HWND_DESKTOP, &pointl, 1);

        /* Tell DIVE about the new settings.                                */
        SetupBlitter.lScreenPosX       = pointl.x;
        SetupBlitter.lScreenPosY       = pointl.y;
        SetupBlitter.ulDstWidth        = win_data->win_swp.cx;
        SetupBlitter.ulDstHeight       = win_data->win_swp.cy;
        SetupBlitter.ulNumDstRects     = win_data->win_rcl_cnt;
        SetupBlitter.pVisDstRects      = win_data->win_rcls;

#ifdef DIVE_ENABLED
        DiveSetupBlitter (win_data->win_dive_handle, &SetupBlitter);
#endif
        win_data->win_blit_halt = FALSE;

        GpiDestroyRegion (hps, hrgn);
        WinReleasePS (hps);
        break;


    case WM_REALIZEPALETTE:
#ifdef DIVE_ENABLED
        /* This tells DIVE that the physical palette may have changed.      */
        DiveSetDestinationPalette (win_data->win_dive_handle, 0, 0, 0);
#endif
        break;

    case WM_CLOSE:
        /* Post to quit the dispatch message loop.                          */
        win_data->win_blit_halt = TRUE;
        DiveSetupBlitter (win_data->win_dive_handle, 0);
        WinPostMsg (hwnd, WM_QUIT, 0L, 0L);
        break;

    default:
        /* Let PM handle this message.                                      */
        return WinDefWindowProc (hwnd, msg, mp1, mp2);
    }
    return ( FALSE );
}


PWINDATA DiveSetup (char *name, USHORT instance, USHORT connector)
{
    DRIVERHANDLE vhandle;
    char txt[400];

        WTV_data.win_vcai_connector = connector;
        strcpy  (WTV_data.win_vcai_name, name);
        WTV_data.win_vcai_no        = instance;
        vhandle = VcaiDeviceOpen (name, instance);
        if (vhandle <= 0)
        {
            sprintf (txt, "Cannot Open Device %s %u !", name, instance);
            WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
                       txt,
                       (PSZ)WTV_NAME,
                       0, MB_OK | MB_INFORMATION);
            return FALSE;
        }
        WTV_data.win_vcai_handle = vhandle;
        VcaiDeviceConnect (vhandle);
        VcaiVideoInputSet (connector);

#ifdef DIVE_ENABLED
    // Get the DIVE capabilities and if the system is 16 coloured then exit

        DiveCaps.pFormatData    = fccFormats;
        DiveCaps.ulFormatLength = 120;
        DiveCaps.ulStructLen    = sizeof (DIVE_CAPS);
        if (DiveQueryCaps (&DiveCaps, DIVE_BUFFER_SCREEN))
        {
            msg_box ("Unable to query the Dive environment",
                 "Error", MB_OK | MB_ERROR);
            return FALSE;
        }

        if (DiveCaps.ulDepth < 8)
        {
            msg_box ("Unable to execute in a 16-colour environment",
                  "Error", MB_OK | MB_ERROR);
            return FALSE;
        }

    // Calculate the bytes per pixel
        WTV_data.win_sizebytes = DiveCaps.ulScanLineBytes /
                                 DiveCaps.ulHorizontalResolution;

    // Open the DIVE device and the VCAI capture device
        if (DiveOpen (&(WTV_data.win_dive_handle), FALSE, 0))
        {
            msg_box ("Unable to open the DIVE device",
                  "Error", MB_OK | MB_ERROR);
            return FALSE;
        }
#endif

    // Set the window size
        WTV_data.win_width  = WIDTH;          // 640  400
        WTV_data.win_height = HEIGHT;          // 320  300

#if 0
        WTV_data.win_dive_width  = WTV_data.win_width - 4;
        WTV_data.win_dive_height = (3 * WTV_data.win_height) >> 2;
#else
        WTV_data.win_dive_width  = WIDTH;     // 636
        WTV_data.win_dive_height = HEIGHT;     // 320
#endif
        return &WTV_data;
}

int DiveRegister (HAB hab)
{
ULONG   flCreate;           // Window creation control flags
ULONG   rc;

    // Register a window class, and create a standard window.
        WinRegisterClass (hab, (PSZ)"WTVWindow", TVWindowProc, 0, sizeof (ULONG));
        flCreate = FCF_TASKLIST | FCF_SYSMENU  |
               FCF_TITLEBAR | FCF_ICON |
               FCF_SIZEBORDER | FCF_MINMAX | FCF_MENU | FCF_SHELLPOSITION;

    // Window
        WTV_data.win_hwnd_frame = WinCreateStdWindow (HWND_DESKTOP,
                                                  WS_VISIBLE, &flCreate,
                                                  (PSZ)"WTVWindow",
                                                  (PSZ)"DIVE Window",
                                                  WS_SYNCPAINT | WS_VISIBLE,
                                                  0, ID_MAINWND,
                                                  &(WTV_data.win_hwnd_client));

        WinSetWindowULong (WTV_data.win_hwnd_client, 0, (ULONG)&WTV_data);


    // Query some information about the window, i.e. size and position
        WTV_data.win_cxWidthBorder =
            (LONG)WinQuerySysValue (HWND_DESKTOP, SV_CXSIZEBORDER);
        WTV_data.win_cyWidthBorder =
            (LONG)WinQuerySysValue (HWND_DESKTOP, SV_CYSIZEBORDER);
        WTV_data.win_cyTitleBar    =
            (LONG)WinQuerySysValue (HWND_DESKTOP, SV_CYTITLEBAR);
        WTV_data.win_cyMenu        =
            (LONG)WinQuerySysValue (HWND_DESKTOP, SV_CYMENU);

//    WTV_data.win_cxWidthWindow   = (WTV_data.win_width / 2) +
        WTV_data.win_cxWidthWindow   = WTV_data.win_width +
                                       (WTV_data.win_cxWidthBorder * 2);
        WTV_data.win_cyHeightWindow  = (WTV_data.win_cyWidthBorder * 2) +
                                       WTV_data.win_cyTitleBar +
                                       WTV_data.win_cyMenu +
                                       WTV_data.win_height;

        WTV_data.win_cxWindowPos   =
                     ((LONG)WinQuerySysValue (HWND_DESKTOP, SV_CXSCREEN)
                                        - WTV_data.win_cxWidthWindow ) / 2;
        WTV_data.win_cyWindowPos   =
                     ((LONG)WinQuerySysValue (HWND_DESKTOP, SV_CYSCREEN)
                                        - WTV_data.win_cyHeightWindow ) / 2;


    // Set the window position and size.
        WinSetWindowPos (WTV_data.win_hwnd_frame, HWND_TOP,
                     WTV_data.win_cxWindowPos, WTV_data.win_cyWindowPos,
                     WTV_data.win_cxWidthWindow, WTV_data.win_cyHeightWindow,
                     SWP_SIZE | SWP_MOVE | SWP_SHOW | SWP_ACTIVATE);

    // Turn on visible region notification.
        WinSetVisibleRegionNotify (WTV_data.win_hwnd_client, TRUE);

    // Send an invlaidation message to the client.
        SetupBlitter.ulStructLen        = sizeof (SETUP_BLITTER);
        SetupBlitter.fccSrcColorFormat  = FOURCC_Y422;
//    SetupBlitter.ulSrcWidth         = (WTV_data.win_dive_width / 2);
        SetupBlitter.ulSrcWidth         = WTV_data.win_dive_width;
        SetupBlitter.ulSrcHeight        = WTV_data.win_dive_height;
        SetupBlitter.ulSrcPosX          = 0;
        SetupBlitter.ulSrcPosY          = 0;
        SetupBlitter.ulDitherType       = 1;        // Dither please!
        SetupBlitter.fInvert            = FALSE;

        SetupBlitter.lDstPosX = 0;
        SetupBlitter.lDstPosY = 0;

        SetupBlitter.fccDstColorFormat = FOURCC_SCRN;

        WinPostMsg (WTV_data.win_hwnd_frame, WM_VRNENABLED, 0L, 0L);

    // Start up the blitting thread.
        WTV_data.win_bliting = FALSE;
        WTV_data.win_blit_halt = TRUE;
        rc = DosCreateThread (&(WTV_data.win_blit_tid),
                              (PFNTHREAD)DiveTV,
                              (ULONG)&WTV_data, 0L, 8192L);
        if (rc)
        {
            WinSetVisibleRegionNotify (WTV_data.win_hwnd_client, FALSE);

#ifdef DIVE_ENABLED
            DiveClose (WTV_data.win_dive_handle);
#endif
            WinDestroyWindow (WTV_data.win_hwnd_frame);
            return FALSE;
        }

    // Set the proiroty of the blitting thread
        DosSetPriority (PRTYS_THREAD, PRTYC_IDLETIME, 0, WTV_data.win_blit_tid);
        WTV_data.win_blit_halt = FALSE;
        return TRUE;
}

int DiveStop ()
{
#ifdef DIVE_ENABLED
    // Set the variable to end the running thread, and wait for it to end.
        WTV_data.win_blit_halt = TRUE;
        DosWaitThread (&(WTV_data.win_blit_tid), DCWW_WAIT);
#endif

#ifdef DEBUG
        msg_box ("DiveTV thread has been closed okay",
                     "Info", MB_OK | MB_INFORMATION);
#endif

    // Turn off visible region notificationm tidy up, and terminate DIVE
        WinSetVisibleRegionNotify (WTV_data.win_hwnd_client, FALSE);
#ifdef DIVE_ENABLED
        DiveClose (WTV_data.win_dive_handle);
#endif

    // Process for termination
        WinDestroyWindow (WTV_data.win_hwnd_frame);

        return TRUE;
}

static void msg_box (UCHAR *txt, UCHAR *title, ULONG options)
{
        WinMessageBox (HWND_DESKTOP,
                       HWND_DESKTOP,
                       txt,
                       title,
                       0,
                       options);
}

