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

#include <exec/exec.h>
#include <dos/dos.h>
#include <graphics/gfx.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <libraries/asl.h>
#include <libraries/lowlevel.h>
#include <cybergraphics/cybergraphics.h>
#include <devices/gameport.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/layers.h>
#include <proto/intuition.h>
#include <proto/asl.h>
#include <proto/lowlevel.h>
#include <proto/cybergraphics.h>

#include "doomtype.h"
#include "doomdef.h"
#include "doomstat.h"
#include "i_system.h"
#include "i_video.h"
#include "v_video.h"
#include "m_argv.h"
#include "d_main.h"

#include "amiga_median.h"
#include "c2p_020.h"
#include "c2p_040.h"

/**********************************************************************/
struct Library *AslBase = NULL;
struct Library *CyberGfxBase = NULL;
struct Library *LowLevelBase = NULL;

extern int cpu_type;

static struct ScreenModeRequester *video_smr = NULL;
static struct Screen *video_screen = NULL;
static struct Window *video_window = NULL;
static struct BitMap video_bitmap[2] = {
  {0, 0, 0, 0, 0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}},
  {0, 0, 0, 0, 0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}}
};
static PLANEPTR video_raster[2] = {NULL, NULL};	/* contiguous bitplanes */
static UBYTE *video_compare_buffer[2] = {NULL, NULL};	/* in fastmem */
static struct ScreenBuffer *video_sb[2] = {NULL, NULL};
static int video_which = 0;
static BYTE video_sigbit1 = -1;
static BYTE video_sigbit2 = -1;
static volatile BYTE video_sigbit3 = -1;
static struct Task *video_maintask;
static struct Task *video_fliptask = NULL;
static int video_depth = 0;
static struct RastPort *video_rastport = NULL;
static ULONG video_colourtable[1 + 3*256 + 1];
static volatile WORD video_palette_changed = 0;
static BOOL video_is_ehb_mode = FALSE;
static BOOL video_is_native_mode = FALSE;
static BOOL video_is_cyber_mode = FALSE;
static BOOL video_is_using_blitter = FALSE;
static BOOL video_blit_is_in_progress = FALSE;
static struct RastPort video_temprp;
static struct BitMap video_tmp_bm = {
  0, 0, 0, 0, 0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
};
static UBYTE video_xlate[256];   /* xlate colour from 8-bit to 6-bit EHB */
static UWORD __chip emptypointer[] = {
  0x0000, 0x0000,	/* reserved, must be NULL */
  0x0000, 0x0000, 	/* 1 row of image data */
  0x0000, 0x0000	/* reserved, must be NULL */
};

/* gameport stuff */
static struct MsgPort *gameport_mp = NULL;
static struct IOStdReq *gameport_io = NULL;
static BOOL gameport_is_open = FALSE;
static BOOL gameport_io_in_progress = FALSE;
static struct InputEvent gameport_ie;
static BYTE gameport_ct;		/* controller type */
struct GamePortTrigger gameport_gpt = {
  GPTF_UPKEYS | GPTF_DOWNKEYS,	/* gpt_Keys */
  0,				/* gpt_Timeout */
  1,				/* gpt_XDelta */
  1				/* gpt_YDelta */
};

static unsigned int start_time[2];
static unsigned int blit_time = 0;
static unsigned int safe_time = 0;
static unsigned int c2p_time = 0;
static unsigned int wpa8_time = 0;
static unsigned int total_frames = 0;

/****************************************************************************/
static __inline void start_timer (void)
{
  timer (start_time);
}

/****************************************************************************/
static __inline unsigned int end_timer (void)
{
  unsigned int end_time[2];

  timer (end_time);
  if (end_time[1] >= start_time[1])
    return end_time[1] - start_time[1] + 50;
  else
    return end_time[1] + 1000000 - start_time[1];
}

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

static char *mode_name (ULONG mode)
/* Return a mode name for given mode, compatible with ASL mode requester */
/* Use name in DisplayInfo database if available */
/* else manually construct the name */
{
  APTR handle;
  char *p;
  struct NameInfo nameinfo;
  struct DimensionInfo dimsinfo;
  static char modename[DISPLAYNAMELEN + 4 + 4 + 11 + 1];

  p = modename;
  *p = '\0';
  if ((handle = FindDisplayInfo (mode & ~SPRITES)) != NULL) {
    if (GetDisplayInfoData (handle, (UBYTE *)&nameinfo,
                            sizeof(nameinfo), DTAG_NAME,
                            NULL) > sizeof(struct QueryHeader)) {
      p += sprintf (p, "%s", nameinfo.Name);
    } else if (GetDisplayInfoData (handle, (UBYTE *)&dimsinfo,
                                   sizeof(dimsinfo), DTAG_DIMS,
                                   NULL) >= 66 /* sizeof(dimsinfo)*/) {
      switch (mode & MONITOR_ID_MASK) {
        case DEFAULT_MONITOR_ID:
          p += sprintf (p, "DEFAULT:");      /* PAL or NTSC??? */
          break;
        case NTSC_MONITOR_ID:
          p += sprintf (p, "NTSC:");
          break;
        case PAL_MONITOR_ID:
          p += sprintf (p, "PAL:");
          break;
        case VGA_MONITOR_ID:
          p += sprintf (p, "MULTISCAN:");
          break;
        case A2024_MONITOR_ID:
          p += sprintf (p, "A2024:");
          break;
        case PROTO_MONITOR_ID:
          p += sprintf (p, "PROTO:");
          break;
        case EURO72_MONITOR_ID:
          p += sprintf (p, "EURO72:");
          break;
        case EURO36_MONITOR_ID:
          p += sprintf (p, "EURO36:");
          break;
        case SUPER72_MONITOR_ID:
          p += sprintf (p, "SUPER72:");
          break;
        case DBLNTSC_MONITOR_ID:
          p += sprintf (p, "DBLNTSC:");
          break;
        case DBLPAL_MONITOR_ID:
          p += sprintf (p, "DBLPAL:");
          break;
        default:
          p += sprintf (p, "(unknown):");
          break;
      }
      p += sprintf (p, "%d x %d",
                    dimsinfo.Nominal.MaxX - dimsinfo.Nominal.MinX + 1,
                    dimsinfo.Nominal.MaxY - dimsinfo.Nominal.MinY + 1);
      if ((mode & HAM_KEY) != 0)
        p += sprintf (p, " HAM");
      if ((mode & EXTRAHALFBRITE_KEY) != 0)
        p += sprintf (p, " EHB");
      if ((mode & LACE) != 0)
        p += sprintf (p, " Interlaced");
    } else {
      p += sprintf (p, "%s", "(unnamed)");
      if ((mode & HAM_KEY) != 0)
        p += sprintf (p, " HAM");
      if ((mode & EXTRAHALFBRITE_KEY) != 0)
        p += sprintf (p, " EHB");
      if ((mode & LACE) != 0)
        p += sprintf (p, " Interlaced");
    }
  } else {
    p += sprintf (p, "%s", "(unavailable)");
  }
  return (modename);
}

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

static ULONG parse_mode (char *modename)
/* Modename may be descriptive name ("PAL Lores"), or hex ("$00420001" or */
/* "0x12345678") or decimal ("32768"). */
{
  ULONG mode, reason;

  mode = INVALID_ID;
  if (modename != NULL) {
    if (modename[0] == '0' && modename[1] == 'x') {
      if (sscanf (&modename[2], "%lx", &mode) != 1)
        mode = INVALID_ID;
    } else if (modename[0] == '$') {
      if (sscanf (&modename[1], "%lx", &mode) != 1)
        mode = INVALID_ID;
    } else if (modename[0] >= '0' && modename[0] <= '9') {
      if (sscanf (modename, "%ld", &mode) != 1)
        mode = INVALID_ID;
    } else {
      while ((mode = NextDisplayInfo (mode)) != INVALID_ID) {
        if ((mode & LORESDPF_KEY) == 0) {
          /* printf ("$%08x  \"%s\"\n", mode, mode_name (mode)); */
          if (strcmp (mode_name (mode), modename) == 0)
            break;
        }
      }
    }
  }
  if (FindDisplayInfo (mode) == NULL)
    I_Error ("ScreenMode not in database: \"%s\"", modename);
  if ((reason = ModeNotAvailable (mode)) != 0)
    I_Error ("Mode $%08x is not available: %ld", mode, reason);
  return (mode);
}

/**********************************************************************/
// This asynchronous task waits in a loop for sigbit3 signals.
// Sigbit3 is signalled when the next frame is just finished.
// Sigbit3 may be signalled from the qblit() cleanup function or from the
// main task.
// On receipt of sigbit3, we load the new palette and call ChangeScreenBuffer().
// This way there is no delay calling ChangeScreenBuffer() after the last blit
// has finished.
// The main 3D rendering task may be interrupted to call ChangeScreenBuffer()
// SIGBREAKF_CTRL_F and SIGBREAKF_CTRL_C are used for synchronisation
// with the main task.

static void __saveds __interrupt video_flipscreentask (void)
{
  ULONG sig;
  struct MsgPort *video_dispport, *video_safeport;
  BOOL going, video_disp, video_safe;
  int i;

  video_sigbit3 = AllocSignal(-1);
  Signal (video_maintask, SIGBREAKF_CTRL_F);
  video_dispport = CreatePort (NULL, 0);
  video_safeport = CreatePort (NULL, 0);
  video_disp = TRUE;
  video_safe = TRUE;
  for (i = 0; i < 2; i++) {
    video_sb[i]->sb_DBufInfo->dbi_DispMessage.mn_ReplyPort = video_dispport;
    video_sb[i]->sb_DBufInfo->dbi_SafeMessage.mn_ReplyPort = video_safeport;
  }
  going = (video_sigbit3 != -1) &&
          (video_dispport != NULL) &&
          (video_safeport != NULL);
  while (going) {
    sig = Wait (1 << video_sigbit3 | SIGBREAKF_CTRL_C);
    if ((sig & (1 << video_sigbit3)) != 0) {
      if (!video_disp) {  /* wait for previous frame to be displayed */
        Wait (1 << video_dispport->mp_SigBit);
        while (GetMsg (video_dispport) != NULL) /* clear message queue */
          /* do nothing */ ;
        video_disp = TRUE;
      }
      LoadRGB32 (&video_screen->ViewPort, video_colourtable);
      if ((ChangeScreenBuffer (video_screen, video_sb[video_which])) != 0) {
        video_disp = FALSE;
        video_safe = FALSE;
      }
      if (!video_safe) {  /* wait until safe */
        Wait (1 << video_safeport->mp_SigBit);
        while (GetMsg (video_safeport) != NULL) /* clear message queue */
          /* do nothing */ ;
        video_safe = TRUE;
      }
      Signal (video_maintask, SIGBREAKF_CTRL_F);
    }
    if ((sig & SIGBREAKF_CTRL_C) != 0) {
      going = FALSE;
    }
  }
  if (!video_safe) {  /* wait for safe */
    Wait (1 << video_safeport->mp_SigBit);
    while (GetMsg (video_safeport) != NULL) /* clear message queue */
      /* do nothing */ ;
    video_safe = TRUE;
  }
  if (!video_disp) {  /* wait for last frame to be displayed */
    Wait (1 << video_dispport->mp_SigBit);
    while (GetMsg (video_dispport) != NULL) /* clear message queue */
      /* do nothing */ ;
    video_disp = TRUE;
  }
  if (video_sigbit3 != -1)
    FreeSignal (video_sigbit3);
  if (video_dispport != NULL)
    DeletePort (video_dispport);
  if (video_safeport != NULL)
    DeletePort (video_safeport);
  Signal (video_maintask, SIGBREAKF_CTRL_F);
  Wait (0);
}

/**********************************************************************/
// Called by D_DoomMain,
// determines the hardware configuration
// and sets up the video mode
void I_InitGraphics (void)
{
  int i;
  ULONG propertymask, idcmp, wflags;
  struct Rectangle rect;
  char reqtitle[32];
  int mode, depth, nbytes, p;
  DisplayInfoHandle handle;
  struct DisplayInfo dispinfo;

  video_maintask = FindTask(NULL);

  if (AslBase == NULL) {
    if ((AslBase = OpenLibrary ("asl.library", 37L)) == NULL ||
        (video_smr = AllocAslRequestTags (ASL_ScreenModeRequest, TAG_DONE)) == NULL) {
      I_Error ("OpenLibrary(""asl.library"", 37) failed");
    }
  }

  /* first check tooltypes for SCREENMODE */
  mode = -1;
  p = M_CheckParm ("-screenmode");
  if (p && p < myargc - 1) {
    mode = parse_mode (myargv[p+1]);
  }

  /* if not found in icon tooltypes, then put up a ScreenMode requester */
  if (mode == -1) {
    propertymask = /* DIPF_IS_EXTRAHALFBRITE | */ DIPF_IS_DUALPF |
                   DIPF_IS_PF2PRI | DIPF_IS_HAM;
    if (GfxBase->LibNode.lib_Version >= 39)
      mode = BestModeID (BIDTAG_NominalWidth,     SCREENWIDTH,
                         BIDTAG_NominalHeight,    SCREENHEIGHT,
                         BIDTAG_Depth,            video_depth,
                         BIDTAG_DIPFMustNotHave,  propertymask,
                         TAG_DONE);
    else
      mode = EXTRAHALFBRITE_KEY;
    if (AslBase->lib_Version >= 38) {
      sprintf (reqtitle, "ADoom %dx%d", SCREENWIDTH, SCREENHEIGHT);
      if (!AslRequestTags (video_smr,
                           ASLSM_TitleText,        (ULONG)reqtitle,
                           ASLSM_InitialDisplayID, mode,
                           ASLSM_MinWidth,         SCREENWIDTH,
                           ASLSM_MinHeight,        SCREENHEIGHT,
                           ASLSM_MinDepth,         6,
                           ASLSM_MaxDepth,         8,
                           ASLSM_PropertyMask,     propertymask,
                           ASLSM_PropertyFlags,    0,
                           TAG_DONE)) {
        I_Error ("AslRequest() failed");
      }
      mode = video_smr->sm_DisplayID;
    }
  }

  if ((handle = FindDisplayInfo (mode)) == NULL) {
    I_Error ("Can't FindDisplayInfo() for mode %08x", mode);
  }
  if ((nbytes = GetDisplayInfoData (handle, (UBYTE *)&dispinfo,
                                    sizeof(struct DisplayInfo), DTAG_DISP,
                                    NULL)) < 40 /*sizeof(struct DisplayInfo)*/) {
    I_Error ("Can't GetDisplayInfoData() for mode %08x, got %d bytes",
             mode, nbytes);
  }

  video_is_cyber_mode = 0;
  if ((CyberGfxBase = OpenLibrary ("cybergraphics.library", 0)) != NULL) {
    video_is_cyber_mode = IsCyberModeID (mode);
    CloseLibrary (CyberGfxBase);
  }

  /* this test needs improving */
  video_is_native_mode = ((GfxBase->LibNode.lib_Version < 39 ||
                           (dispinfo.PropertyFlags & DIPF_IS_EXTRAHALFBRITE) != 0 ||
                           (dispinfo.PropertyFlags & DIPF_IS_AA) != 0 ||
                           (dispinfo.PropertyFlags & DIPF_IS_ECS) != 0 ||
                           (dispinfo.PropertyFlags & DIPF_IS_DBUFFER) != 0) &&
                          !video_is_cyber_mode &&
                          (dispinfo.PropertyFlags & DIPF_IS_FOREIGN) == 0);

  video_is_ehb_mode = ((dispinfo.PropertyFlags & DIPF_IS_EXTRAHALFBRITE) != 0);

  printf ("Screen Mode $%08x is ", mode);
  if (video_is_native_mode)
    printf (" NATIVE-PLANAR");
  else
    printf (" FOREIGN");
  if (video_is_ehb_mode)
    printf (" EXTRAHALFBRITE");
  else
    printf (" 8-BIT");
  if (video_is_cyber_mode)
    printf (" CYBERGRAPHX");
  printf ("\n");

  if (video_is_ehb_mode)
    video_depth = 6;
  else
    video_depth = 8;

  rect.MinX = 0;
  rect.MinY = 0;
  rect.MaxX = SCREENWIDTH - 1;
  rect.MaxY = SCREENHEIGHT - 1;

  if (video_is_native_mode) {
    for (i = 0; i < 2; i++) {
      if ((video_raster[i] = (PLANEPTR)AllocRaster (SCREENWIDTH,
                                           video_depth * SCREENHEIGHT)) == NULL)
        I_Error ("AllocRaster() failed");
      memset (video_raster[i], 0, video_depth * RASSIZE (SCREENWIDTH, SCREENHEIGHT));
      InitBitMap (&video_bitmap[i], video_depth, SCREENWIDTH, SCREENHEIGHT);
      for (depth = 0; depth < video_depth; depth++)
        video_bitmap[i].Planes[depth] = video_raster[i] +
                                    depth * RASSIZE (SCREENWIDTH, SCREENHEIGHT);
    }
    if ((video_screen = OpenScreenTags (NULL,
          SA_Type,        CUSTOMSCREEN | CUSTOMBITMAP,
          SA_DisplayID,   mode,
          SA_DClip,       (ULONG)&rect,
          SA_Width,       SCREENWIDTH,
          SA_Height,      SCREENHEIGHT,
          SA_Depth,       video_depth,
          /* SA_Draggable,FALSE, */
          /* SA_AutoScroll,FALSE, */
          /* SA_Exclusive,TRUE, */
          SA_Quiet,       TRUE,
          SA_BitMap,      &video_bitmap[0], /* custom bitmap, contiguous planes */
          TAG_DONE)) == NULL) {
      I_Error ("OpenScreen() failed");
    }
    for (i = 0; i < 2; i++) {
      if ((video_compare_buffer[i] = malloc (SCREENWIDTH * SCREENHEIGHT)) == NULL)
        I_Error ("Out of memory allocating %d bytes", SCREENWIDTH * SCREENHEIGHT);
      memset (video_compare_buffer[i], 0, SCREENWIDTH * SCREENHEIGHT);
    }
    for (i = 0; i < 2; i++) {
      if ((video_sb[i] = AllocScreenBuffer (video_screen, &video_bitmap[i], 0)) == NULL)
        I_Error ("Can't allocate structure for double buffering");
    }
  } else {
    if ((video_screen = OpenScreenTags (NULL,
          SA_Type,        CUSTOMSCREEN,
          SA_DisplayID,   mode,
          SA_DClip,       (ULONG)&rect,
          SA_Width,       SCREENWIDTH,
          SA_Height,      SCREENHEIGHT,
          SA_Depth,       video_depth,
          /* SA_Draggable,FALSE, */
          /* SA_AutoScroll,FALSE, */
          /* SA_Exclusive,TRUE, */
          SA_Quiet,       TRUE,
          TAG_DONE)) == NULL) {
      I_Error ("OpenScreen() failed");
    }
  }

  idcmp = IDCMP_RAWKEY;
  wflags = WFLG_ACTIVATE | WFLG_BORDERLESS | WFLG_RMBTRAP;
  if (M_CheckParm("-mouse") != NULL) {
    idcmp |= IDCMP_MOUSEMOVE | IDCMP_DELTAMOVE | IDCMP_MOUSEBUTTONS;
    wflags |= WFLG_REPORTMOUSE;
  }

  if ((video_window = OpenWindowTags (NULL,
        WA_Left,         0,
        WA_Top,          0,
        WA_Width,        SCREENWIDTH,
        WA_Height,       SCREENHEIGHT,
        WA_IDCMP,        idcmp,
        WA_Flags,        wflags,
        WA_CustomScreen, video_screen,
        TAG_DONE)) == NULL) {
    I_Error ("OpenWindow() failed");
  }
  video_rastport = video_window->RPort;

  InitBitMap (&video_tmp_bm, video_depth, SCREENWIDTH, 1);
  for (depth = 0; depth < video_depth; depth++)
    if ((video_tmp_bm.Planes[depth] = (PLANEPTR)AllocRaster (SCREENWIDTH, 1)) == NULL)
      I_Error ("AllocRaster() failed");
  video_temprp = *video_rastport;
  video_temprp.Layer = NULL;
  video_temprp.BitMap = &video_tmp_bm;

  SetPointer (video_window, emptypointer, 1, 16, 0, 0);

  if (video_is_native_mode) {
    video_is_using_blitter = (cpu_type < 68040);
    if ((video_sigbit1 = AllocSignal(-1)) == -1 ||
        (video_sigbit2 = AllocSignal(-1)) == -1)
      I_Error ("Can't allocate signal!\n");
    Signal (video_maintask, (1 << video_sigbit1) | (1 << video_sigbit2));
                                                 /* initial state is finished */
    if ((video_fliptask = CreateTask ("ADoom_flipscreen", 5,
                                      video_flipscreentask, 4096)) == NULL)
      I_Error ("Can't create subtask");
    Wait (SIGBREAKF_CTRL_F);
    if (video_sigbit3 == -1)
      I_Error ("Subtask couldn't allocate sigbit");
  }

  /* joystick initialisation */

  if (M_CheckParm ("-joypad") != NULL) {

    if ((LowLevelBase = OpenLibrary ("lowlevel.library", 0)) == NULL)
      I_Error ("-joypad option specified and can't open lowlevel.library");

  } else {

    if ((gameport_mp = CreatePort (NULL, 0)) == NULL ||
        (gameport_io = (struct IOStdReq *)CreateExtIO (gameport_mp,
                                              sizeof(struct IOStdReq))) == NULL ||
        OpenDevice ("gameport.device", 1, (struct IORequest *)gameport_io, 0) != 0)
      I_Error ("Can't open gameport.device");

    gameport_is_open = TRUE;

    Forbid ();

    gameport_io->io_Command = GPD_ASKCTYPE;
    gameport_io->io_Length = 1;
    gameport_io->io_Data = &gameport_ct;
    DoIO ((struct IORequest *)gameport_io);

    if (gameport_ct != GPCT_NOCONTROLLER) {

      Permit ();
      fprintf (stderr, "Another task is using the gameport!  Joystick disabled");
      CloseDevice ((struct IORequest *)gameport_io);
      gameport_is_open = FALSE;

    } else {

      gameport_ct = GPCT_ABSJOYSTICK;
      gameport_io->io_Command = GPD_SETCTYPE;
      gameport_io->io_Length = 1;
      gameport_io->io_Data = &gameport_ct;
      DoIO ((struct IORequest *)gameport_io);

      Permit ();

      gameport_io->io_Command = GPD_SETTRIGGER;
      gameport_io->io_Length = sizeof(struct GamePortTrigger);
      gameport_io->io_Data = &gameport_gpt;
      DoIO ((struct IORequest *)gameport_io);

      gameport_io->io_Command = GPD_READEVENT;
      gameport_io->io_Length = sizeof (struct InputEvent);
      gameport_io->io_Data = &gameport_ie;
      SendIO ((struct IORequest *)gameport_io);
      gameport_io_in_progress = TRUE;
    }
  }
}

/**********************************************************************/
void I_ShutdownGraphics (void)
{
  int depth, i;

  if (LowLevelBase != NULL) {
    CloseLibrary (LowLevelBase);
    LowLevelBase = NULL;
  }
  if (gameport_io_in_progress) {
    AbortIO ((struct IORequest *)gameport_io);
    WaitIO ((struct IORequest *)gameport_io);
    gameport_io_in_progress = FALSE;
    gameport_ct = GPCT_NOCONTROLLER;
    gameport_io->io_Command = GPD_SETCTYPE;
    gameport_io->io_Length = 1;
    gameport_io->io_Data = &gameport_ct;
    DoIO ((struct IORequest *)gameport_io);
  }
  if (gameport_is_open) {
    CloseDevice ((struct IORequest *)gameport_io);
    gameport_is_open = FALSE;
  }
  if (gameport_io != NULL) {
    DeleteExtIO ((struct IORequest *)gameport_io);
    gameport_io = NULL;
  }
  if (gameport_mp != NULL) {
    DeletePort (gameport_mp);
    gameport_mp = NULL;
  }
  if (video_blit_is_in_progress) {
    Wait (SIGBREAKF_CTRL_F);
    video_blit_is_in_progress = FALSE;
  }
  if (video_fliptask != NULL) {
    Signal (video_fliptask, SIGBREAKF_CTRL_C);
    Wait (SIGBREAKF_CTRL_F);
    DeleteTask (video_fliptask);
    video_fliptask = NULL;
  }
  if (video_sigbit1 != -1) {
    Wait (1 << video_sigbit1);  // wait for last c2p8 to finish pass 3
    FreeSignal (video_sigbit1);
    video_sigbit1 = -1;
  }
  if (video_sigbit2 != -1) {
    Wait (1 << video_sigbit2);  // wait for last c2p8 to completely finish
    FreeSignal (video_sigbit2);
    video_sigbit2 = -1;
  }
  if (video_window != NULL) {
    ClearPointer (video_window);
    CloseWindow (video_window);
    video_window = NULL;
  }
  for (i = 0; i < 2; i++) {
    if (video_sb[i] != NULL) {
      FreeScreenBuffer (video_screen, video_sb[i]);
      video_sb[i] = NULL;
    }
  }
  if (video_screen != NULL) {
    CloseScreen (video_screen);
    video_screen = NULL;
  }
  for (depth = 0; depth < video_depth; depth++) {
    if (video_tmp_bm.Planes[depth] != NULL) {
      FreeRaster (video_tmp_bm.Planes[depth], SCREENWIDTH, 1);
      video_tmp_bm.Planes[depth] = NULL;
    }
  }
  for (i = 0; i < 2; i++) {
    if (video_raster[i] != NULL) {
      FreeRaster (video_raster[i], SCREENWIDTH, video_depth * SCREENHEIGHT);
      video_raster[i] = NULL;
    }
    if (video_compare_buffer[i] != NULL) {
      free (video_compare_buffer[i]);
      video_compare_buffer[i] = NULL;
    }
  }
}

/**********************************************************************/
// Takes full 8 bit values.
void I_SetPalette (byte *palette)
{
  int i;
  ULONG c, *p;
  UBYTE *g;
  static UBYTE gpalette[3*256];

  /* printf ("I_SetPalette()\n"); */
  if (video_is_ehb_mode) {
    g = gpalette;
    for (i = 0; i < 256; i++) {
      *g++ = gammatable[usegamma][*palette++];
      *g++ = gammatable[usegamma][*palette++];
      *g++ = gammatable[usegamma][*palette++];
    }
    video_colourtable[0] = (32 << 16) + 0;
    median_cut (gpalette, &video_colourtable[1], video_xlate);
    video_colourtable[33] = 0;
  } else {
    p = video_colourtable;
    *p++ = (256 << 16) + 0;
    for (i = 0; i < 256; i++) {
      c = (ULONG)gammatable[usegamma][*palette++];
      c += (c<<8);
      c += (c<<16);
      *p++ = c;
      c = (ULONG)gammatable[usegamma][*palette++];
      c += (c<<8);
      c += (c<<16);
      *p++ = c;
      c = (ULONG)gammatable[usegamma][*palette++];
      c += (c<<8);
      c += (c<<16);
      *p++ = c;
    }
    *p++ = 0;
  }
  if (video_palette_changed == 0 || !video_is_using_blitter)
    video_palette_changed = 2;
}

/**********************************************************************/
void I_UpdateNoBlit (void)
{
}

/**********************************************************************/
void I_FinishUpdate (void)
/* This needs optimising to copy just the parts that changed,
   especially if the user has shrunk the playscreen. */
{
  total_frames++;
  if (video_is_native_mode) {
    start_timer();
    Wait (1 << video_sigbit1); /* wait for prev c2p() to finish pass 3 */
    blit_time += end_timer();
    if (video_blit_is_in_progress) {
      start_timer();
      Wait (SIGBREAKF_CTRL_F); /* wait for prev ChangeScreenBuffer() safe */
      safe_time += end_timer();
      video_blit_is_in_progress = FALSE;
    }
    video_which = 1 - video_which;  /* render to the hidden bitmap */
    start_timer();
    if (video_is_using_blitter) {
      if (video_is_ehb_mode) {
        c2p_6_020 (screens[0],
                   video_bitmap[video_which].Planes,
                   1 << video_sigbit1, 1 << video_sigbit2, 1 << video_sigbit3,
                   SCREENWIDTH * SCREENHEIGHT, 0, video_xlate, video_fliptask);
      } else {
        c2p_8_020 (screens[0],
                   video_bitmap[video_which].Planes,
                   1 << video_sigbit1, 1 << video_sigbit2, 1 << video_sigbit3,
                   SCREENWIDTH * SCREENHEIGHT, 0, video_fliptask);
      }
      video_blit_is_in_progress = TRUE;
    } else {
      if (video_is_ehb_mode) {
        c2p_6_040 (screens[0], video_raster[video_which],
                   video_compare_buffer[video_which], video_xlate,
                   (SCREENWIDTH * SCREENHEIGHT) >> 3,
                   video_palette_changed);
      } else {
        c2p_8_040 (screens[0], video_raster[video_which],
                   video_compare_buffer[video_which],
                   (SCREENWIDTH * SCREENHEIGHT) >> 3);
      }
      video_blit_is_in_progress = TRUE;
      Signal (video_maintask, 1 << video_sigbit1); /* simulate c2p() pass 3 */
      Signal (video_fliptask, 1 << video_sigbit3); /* start ChangeScreenBuffer() */
    }
    c2p_time += end_timer();
    if (video_palette_changed > 0)
      video_palette_changed--;/* keep it set for 2 frames for doublebuffering */
  } else {
    if (video_palette_changed != 0)
      LoadRGB32 (&video_screen->ViewPort, video_colourtable);
    start_timer();
    WritePixelArray8 (video_rastport, 0, 0, SCREENWIDTH-1, SCREENHEIGHT-1,
                      screens[0], &video_temprp);
    wpa8_time += end_timer();
    video_palette_changed = 0;
  }
}

/**********************************************************************/
// Wait for vertical retrace or pause a bit.  Use when quit game.
void I_WaitVBL(int count)
{
  for ( ; count > 0; count--)
    WaitTOF();
}

/**********************************************************************/
void I_ReadScreen (byte* scr)
{
  memcpy (scr, screens[0], SCREENWIDTH * SCREENHEIGHT);
}

/**********************************************************************/
void I_BeginRead (void)
{
}

/**********************************************************************/
void I_EndRead (void)
{
}

/**********************************************************************/
void amiga_getevents (void)
{
  event_t event;
  ULONG class;
  UWORD code;
  WORD mousex, mousey;
  struct IntuiMessage *msg;
  static ULONG previous = 0;
  static int xlate[0x68] = {
    '`', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', '0', KEY_MINUS, KEY_EQUALS, '\\', 0, '0',
    'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
    'o', 'p', KEY_F11, KEY_F12, 0, '0', '2', '3',
    'a', 's', 'd', 'f', 'g', 'h', 'j', 'k',
    'l', ';', '\'', KEY_ENTER, 0, '4', '5', '6',
    KEY_RSHIFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm',
    ',', '.', '/', 0, '.', '7', '8', '9',
    ' ', KEY_BACKSPACE, KEY_TAB, KEY_ENTER, KEY_ENTER, KEY_ESCAPE, KEY_BACKSPACE,
    0, 0, 0, KEY_MINUS, 0, KEY_UPARROW, KEY_DOWNARROW, KEY_RIGHTARROW, KEY_LEFTARROW,
    KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8,
    KEY_F9, KEY_F10, '(', ')', '/', '*', KEY_EQUALS, KEY_PAUSE,
    KEY_RSHIFT, KEY_RSHIFT, 0, KEY_RCTRL, KEY_LALT, KEY_RALT, KEY_LALT, KEY_RALT
  };
  static event_t joyevent = {0}, mouseevent = {0};

  if (video_window != NULL) {
    while ((msg = (struct IntuiMessage *)GetMsg (video_window->UserPort)) != NULL) {
      class = msg->Class;
      code = msg->Code;
      mousex = msg->MouseX;
      mousey = msg->MouseY;
      ReplyMsg ((struct Message *)msg);
      if (class == IDCMP_RAWKEY) {
        if ((code & 0x80) != 0) {
          code &= ~0x80;
	  event.type = ev_keyup;
        } else {
          event.type = ev_keydown;
        }
        if (code < 0x68 && xlate[code] != 0) {
          event.data1 = xlate[code];
          D_PostEvent (&event);
        }
      } else if (class == IDCMP_MOUSEMOVE) {
        mouseevent.type = ev_mouse;
        mouseevent.data2 = (mousex << 3);
        mouseevent.data3 = -(mousey << 5);
        D_PostEvent (&mouseevent);
      } else if (class == IDCMP_MOUSEBUTTONS) {
        mouseevent.type = ev_mouse;
        switch (code) {
          case SELECTDOWN:
            mouseevent.data1 |= 1;
            break;
          case SELECTUP:
            mouseevent.data1 &= ~1;
            break;
          case MENUDOWN:
            mouseevent.data1 |= 2;
            break;
          case MENUUP:
            mouseevent.data1 &= ~2;
            break;
          case MIDDLEDOWN:
            mouseevent.data1 |= 4;
            break;
          case MIDDLEUP:
            mouseevent.data1 &= ~4;
            break;
          default:
            break;
        }
        D_PostEvent (&mouseevent);
      }
    }
  }

  if (gameport_is_open && gameport_io_in_progress) {
    while (GetMsg (gameport_mp) != NULL) {
      switch (gameport_ie.ie_Code) {
        case IECODE_LBUTTON:
          joyevent.data1 |= 1;
          break;
        case IECODE_LBUTTON | IECODE_UP_PREFIX:
          joyevent.data1 &= ~1;
          break;
        case IECODE_RBUTTON:
          joyevent.data1 |= 2;
          break;
        case IECODE_RBUTTON | IECODE_UP_PREFIX:
          joyevent.data1 &= ~2;
          break;
        case IECODE_MBUTTON:
          joyevent.data1 |= 4;
          break;
        case IECODE_MBUTTON | IECODE_UP_PREFIX:
          joyevent.data1 &= ~4;
          break;
        case IECODE_NOBUTTON:
          joyevent.data2 = gameport_ie.ie_X;
          joyevent.data3 = gameport_ie.ie_Y;
          break;
        default:
          break;
      }
      joyevent.type = ev_joystick;
      D_PostEvent (&joyevent);
      gameport_io->io_Command = GPD_READEVENT;
      gameport_io->io_Length = sizeof (struct InputEvent);
      gameport_io->io_Data = &gameport_ie;
      SendIO ((struct IORequest *)gameport_io);
    }
  }

  /* CD32 joypad handler code supplied by Gabry (ggreco@iol.it) */

  if (LowLevelBase != NULL) {
    event_t joyevent;
    ULONG joypos = ReadJoyPort (1);

    if (previous == joypos)
      return;

    joyevent.type = ev_joystick;
    joyevent.data1 = joyevent.data2 = joyevent.data3 = 0;

    if (joypos & JPF_BUTTON_RED)
      joyevent.data1 |= 1;
    else
      joyevent.data1 &= ~1;

    if (joypos & JP_DIRECTION_MASK) {
      if (joypos & JPF_JOY_LEFT) {
        joyevent.data2 = -1;
      } else if (joypos & JPF_JOY_RIGHT) {
        joyevent.data2 = 1;
      }
      if (joypos & JPF_JOY_UP) {
        joyevent.data3 = -1;
      } else if (joypos & JPF_JOY_DOWN) {
        joyevent.data3 = 1;
      }
    }

    if (joypos & JP_TYPE_GAMECTLR) {
      event_t event;

      // Play/Pause = ESC (Menu)
      if (joypos & JPF_BUTTON_PLAY && !(previous & JPF_BUTTON_PLAY)) {
        event.type = ev_keydown;
        event.data1 = KEY_ESCAPE;
        D_PostEvent (&event);
      } else if (previous & JPF_BUTTON_PLAY) {
        event.type = ev_keyup;
        event.data1 = KEY_ESCAPE;
        D_PostEvent (&event);
      }

      // YELLOW = SHIFT (button 2) (Run)
      if (joypos & JPF_BUTTON_YELLOW)
        joyevent.data1 |= 4;
      else
        joyevent.data1 &= ~4;

      // BLUE = SPACE (button 3) (Open/Operate)

      if (joypos & JPF_BUTTON_BLUE)
        joyevent.data1 |= 8;
      else
        joyevent.data1 &= ~8;

      // GREEN = RETURN (show msg)

      if (joypos & JPF_BUTTON_GREEN && !(previous&JPF_BUTTON_GREEN)) {
        event.type = ev_keydown;
        event.data1 = 13;
        D_PostEvent (&event);
      } else if (previous & JPF_BUTTON_GREEN) {
        event.type = ev_keyup;
        event.data1 = 13;
        D_PostEvent (&event);
      }

      // FORWARD & REVERSE - ALT (Button1) Strafe left/right

      if (joypos & JPF_BUTTON_FORWARD) {
        joyevent.data1 |= 2;
        joyevent.data2 = 1;
      } else if (joypos & JPF_BUTTON_REVERSE) {
        joyevent.data1 |=2;
        joyevent.data2=-1;
      } else
        joyevent.data1 &= ~2;
    }

    D_PostEvent (&joyevent);

    previous = joypos;
  }

}

/**********************************************************************/
void _STDvideo_cleanup (void)
{
  I_ShutdownGraphics ();
  if (video_smr != NULL) {
    FreeAslRequest (video_smr);
    video_smr = NULL;
  }
  if (total_frames > 0) {
    printf ("Total number of frames = %u\n", total_frames);
    printf ("Total blit wait time   = %u us  (%u us/frame)\n", blit_time,
            blit_time / total_frames);
    printf ("Total safe wait time   = %u us  (%u us/frame)\n", safe_time,
            safe_time / total_frames);
    printf ("Total C2P time         = %u us  (%u us/frame)\n", c2p_time,
            c2p_time / total_frames);
    printf ("Total WPA8 time        = %u us  (%u us/frame)\n", wpa8_time,
            wpa8_time / total_frames);
  }
}

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