#include <stdio.h>
#include <stdlib.h>
#include <string.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 <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/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_040.h"

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

static struct ScreenModeRequester *video_smr = NULL;
static struct Screen *video_screen = NULL;
static struct Window *video_window = NULL;
static struct BitMap video_bitmap = {
  0, 0, 0, 0, 0, {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
};
static PLANEPTR video_raster = NULL;		/* contiguous bitplanes */
static UBYTE *video_compare_buffer = NULL;	/* in fastmem */
static int video_depth = 0;
static struct RastPort *video_rastport = NULL;
static ULONG video_colourtable[1 + 3*256 + 1];
static BOOL video_palette_changed = FALSE;
static BOOL video_is_ehb_mode = FALSE;
static BOOL video_is_native_mode = FALSE;
static BOOL video_is_cyber_mode = 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 */
};

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

  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");
    }
  }

  sprintf (reqtitle, "DOOM %dx%d", SCREENWIDTH, SCREENHEIGHT);
  propertymask = /* DIPF_IS_EXTRAHALFBRITE | */ DIPF_IS_DUALPF |
                 DIPF_IS_PF2PRI | DIPF_IS_HAM;
  mode = BestModeID (BIDTAG_NominalWidth,     SCREENWIDTH,
                     BIDTAG_NominalHeight,    SCREENHEIGHT,
                     BIDTAG_Depth,            video_depth,
                     BIDTAG_DIPFMustNotHave,  propertymask);
  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");
  }

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

  video_is_cyber_mode = 0;
  if ((CyberGfxBase = OpenLibrary ("cybergraphics.library", 0)) != NULL) {
    video_is_cyber_mode = IsCyberModeID (video_smr->sm_DisplayID);
    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 is ");
  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) {
    if ((video_raster = (PLANEPTR)AllocRaster (SCREENWIDTH,
                                           video_depth * SCREENHEIGHT)) == NULL)
      I_Error ("AllocRaster() failed");
    InitBitMap (&video_bitmap, video_depth, SCREENWIDTH, SCREENHEIGHT);
    for (depth = 0; depth < video_depth; depth++)
      video_bitmap.Planes[depth] = video_raster +
                                    depth * RASSIZE (SCREENWIDTH, SCREENHEIGHT);
    if ((video_screen = OpenScreenTags (NULL,
          SA_Type,        CUSTOMSCREEN | CUSTOMBITMAP,
          SA_DisplayID,   video_smr->sm_DisplayID,
          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, /* custom bitmap, contiguous planes */
          TAG_DONE)) == NULL) {
      I_Error ("OpenScreen() failed");
    }
    if ((video_compare_buffer = malloc (SCREENWIDTH * SCREENHEIGHT)) == NULL)
      I_Error ("Out of memory allocating %d bytes", SCREENWIDTH * SCREENHEIGHT);
    memset (video_compare_buffer, 0, SCREENWIDTH * SCREENHEIGHT);
  } else {
    if ((video_screen = OpenScreenTags (NULL,
          SA_Type,        CUSTOMSCREEN,
          SA_DisplayID,   video_smr->sm_DisplayID,
          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");
    }
  }

  if ((video_window = OpenWindowTags (NULL,
        WA_Left,         0,
        WA_Top,          0,
        WA_Width,        SCREENWIDTH,
        WA_Height,       SCREENHEIGHT,
        WA_IDCMP,        IDCMP_RAWKEY,
        WA_Flags,        WFLG_ACTIVATE | WFLG_BORDERLESS,
        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);


  /* joystick initialisation */

  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 ();
    I_Error ("Another task is using the gameport!");
  }

  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;

  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;
  }
  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;
    }
  if (video_window != NULL) {
    CloseWindow (video_window);
    video_window = NULL;
  }
  if (video_screen != NULL) {
    CloseScreen (video_screen);
    video_screen = NULL;
  }
  if (video_raster != NULL) {
    FreeRaster (video_raster, SCREENWIDTH, video_depth * SCREENHEIGHT);
    video_raster = NULL;
  }
  if (video_compare_buffer != NULL) {
    free (video_compare_buffer);
    video_compare_buffer = NULL;
  }
}

/**********************************************************************/
// Takes full 8 bit values.
void I_SetPalette (byte* palette)
{
  int i;
  ULONG r, g, b, *p;

  /* printf ("I_SetPalette()\n"); */
  if (video_is_ehb_mode) {
    video_colourtable[0] = (32 << 16) + 0;
    median_cut (palette, &video_colourtable[1], video_xlate);
    video_colourtable[33] = 0;
  } else {
    p = video_colourtable;
    *p++ = (256 << 16) + 0;
    for (i = 0; i < 256; i++) {
      r = (unsigned int)gammatable[usegamma][*palette++];
      r += (r<<8);
      r += (r<<16);
      *p++ = r;
      g = (unsigned int)gammatable[usegamma][*palette++];
      g += (g<<8);
      g += (g<<16);
      *p++ = g;
      b = (unsigned int)gammatable[usegamma][*palette++];
      b += (b<<8);
      b += (b<<16);
      *p++ = b;
    }
    *p++ = 0;
  }
  video_palette_changed = TRUE;
}

/**********************************************************************/
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. */
{
  if (video_palette_changed)
    LoadRGB32 (&video_screen->ViewPort, video_colourtable);
  if (video_is_native_mode) {
    if (video_is_ehb_mode) {
      c2p_6_040 (screens[0], video_raster, video_compare_buffer, video_xlate,
                 (SCREENWIDTH * SCREENHEIGHT) >> 3,
                 video_palette_changed);
    } else {
      c2p_8_040 (screens[0], video_raster, video_compare_buffer,
                 (SCREENWIDTH * SCREENHEIGHT) >> 3);
    }
  } else {
    WritePixelArray8 (video_rastport, 0, 0, SCREENWIDTH-1, SCREENHEIGHT-1,
                      screens[0], &video_temprp);
  }
  video_palette_changed = FALSE;
}

/**********************************************************************/
// Wait for vertical retrace or pause a bit.
void I_WaitVBL(int count)
{
}

/**********************************************************************/
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;
  struct IntuiMessage *msg;
  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};

  if (video_window != NULL) {
    while ((msg = (struct IntuiMessage *)GetMsg (video_window->UserPort)) != NULL) {
      class = msg->Class;
      code = msg->Code;
      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);
        }
      }
    }
  }

  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);
    }
  }
}

/**********************************************************************/
void _STDvideo_cleanup (void)
{
  I_ShutdownGraphics ();
  if (video_smr != NULL) {
    FreeAslRequest (video_smr);
    video_smr = NULL;
  }
}

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