#define	LIBQSYS_DRIVER
#define	LIBQTOOLS_CORE
#include "../../../include/libqsys.h"
/*
 * needed for tmalloc/.../Error
 * maybe move them to this one (generic?)
 */
#include "../../../include/libqtools.h"

/* display */
static struct DisplayDimension dim;

extern struct ExecBase *SysBase;
extern struct DOSBase *DOSBase;
static struct GfxBase *GfxBase;
static struct IntuitionBase *IntuitionBase;
static struct Library *AslBase;
static struct Library *CyberGfxBase;

static short int ScreenLeft = 0;
static short int ScreenTop = 0;
static short int ScreenWidth = 0;
static short int ScreenHeight = 0;
static char ScreenDepth = 0;
static char ScreenLog = 0;

static void *FrameBuffer = NULL;				/* actual framebufferaddress */
static struct BitMap *FrameBitMap = NULL;			/* actual framebufferbitmap */
static void *FrameHandle = 0;

#ifdef	USE_ZBUFFER
static unsigned short int *ZBuffer = NULL;

#endif
static int FrameSize = 0;					/* size of updateregion in bytes */
static unsigned char FrameFormat = 0;				/* default RECTFMT_LUT8   ? */
static short int BytesPerRow = 0;				/* default            8/8 ? */
static short int BytesPerPel = 0;				/* default            8/8 ? */

static struct Screen *Screen = NULL;
static struct Window *Window = NULL;
static struct ViewPort *ViewPort = NULL;
static struct RastPort *RastPort = NULL;

#define SAVEBACK_COLORS (256 * 3)
static unsigned short int oldNFree, oldShareable;
static long unsigned int OldColorMap[1 + (256 * 3) + SAVEBACK_COLORS];
static long unsigned int NewColorMap[1 + (256 * 3) + SAVEBACK_COLORS];	/* LoadRGB32-Palette */

static void *InitDisplay(void)
{
  /* Works with Windows and Screens!!! */
  if (ScreenDepth <= 8) {
    BytesPerPel = 1;
    FrameFormat = RECTFMT_LUT8;
  }
  else if (ScreenDepth <= 16) {
    BytesPerPel = 2;
    FrameFormat = NULL;
  }
  else if (ScreenDepth <= 24) {
    BytesPerPel = 3;
    FrameFormat = RECTFMT_RGB;
  }
  else if (ScreenDepth <= 32) {
    BytesPerPel = 4;
    FrameFormat = RECTFMT_ARGB;
  }
  BytesPerRow = ScreenWidth * BytesPerPel;
  FrameSize = BytesPerRow * ScreenHeight;

  if ((ScreenDepth <= 8) || (ScreenDepth >= 24)) {
    if (FrameBuffer != NULL)
      tfree(FrameBuffer);
    if ((FrameBuffer = (void *)tmalloc(FrameSize)) == NULL)	/* BitMap allocation failed, what now?? */
      Error(failed_memoryunsize, "FrameBuffer");
  }
  else {
    if (FrameHandle != 0)
      UnLockBitMap(FrameHandle);
    if (FrameBitMap != NULL)
      FreeBitMap(FrameBitMap);
    if ((FrameBitMap = AllocBitMap(ScreenWidth, ScreenHeight, ScreenDepth,
				   BMF_MINPLANES | BMF_SPECIALFMT | SHIFT_PIXFMT(PIXFMT_RGB16), RastPort->BitMap)) == NULL)	/* BitMap allocation failed, what now?? */
      Error(failed_memoryunsize, "FrameBuffer");
    if (!(FrameHandle = LockBitMapTags(FrameBitMap,
			    LBMI_BASEADDRESS, (unsigned long int)&FrameBuffer,
				       TAG_DONE, 0)))
      Error("Cannot lock FrameBuffer\n");
  }

  return FrameBuffer;
}

static void ExitDisplay(void)
{
  if ((ScreenDepth <= 8) && (ScreenDepth >= 24)) {
    if (FrameBuffer != NULL)
      tfree(FrameBuffer);
    FrameBuffer = NULL;
  }
  else {
    if (FrameHandle != 0)
      UnLockBitMap(FrameHandle);
    FrameHandle = 0;
    if (FrameBitMap != NULL)
      FreeBitMap(FrameBitMap);
    FrameBitMap = NULL;
  }
}

#ifdef	USE_ZBUFFER
static void *InitDisplayZ(void)
{
  if (ZBuffer != NULL)
    tfree(ZBuffer);
  if ((ZBuffer = (unsigned short int *)tmalloc(ScreenWidth * ScreenHeight * sizeof(unsigned short int))) == NULL)	/* BitMap allocation failed, what now?? */
      Error(failed_memoryunsize, "ZBuffer");

  return ZBuffer;
}
#endif

struct DisplayDimension *OpenDisplay(short int width, short int height, char depth, struct rgb *Palette)
{
  atexit(CloseDisplay);

  GfxBase = (struct GfxBase *)OpenLibrary((unsigned char *)"graphics.library", (unsigned long)39);
  IntuitionBase = (struct IntuitionBase *)OpenLibrary((unsigned char *)"intuition.library", (unsigned long)39);
  AslBase = OpenLibrary((unsigned char *)"asl.library", (unsigned long)37);
  CyberGfxBase = OpenLibrary((unsigned char *)"cybergraphics.library", (unsigned long)39);

  if ((GfxBase == NULL) || (IntuitionBase == NULL) || (AslBase == NULL))
    Error("Cannot open one or more of the required libraries\n");

  ScreenDepth = depth;

  Screen = LockPubScreen(NULL);
  if ((ScreenDepth > 8) && (GetBitMapAttr(Screen->RastPort.BitMap, BMA_DEPTH) <= 8))
    Error("Screen must have more than 8bits\n");
  else if ((ScreenDepth == 8) && (GetBitMapAttr(Screen->RastPort.BitMap, BMA_DEPTH) < 8))
    Error("Screen must have at least 8bits\n");
  else {
    if ((Window = OpenWindowTags(NULL,
				 WA_InnerWidth, width,
				 WA_InnerHeight, height,
				 WA_MinWidth, BASEWIDTH,
				 WA_MinHeight, BASEHEIGHT,
				 WA_MaxWidth, MAXWIDTH,
				 WA_MaxHeight, MAXHEIGHT,
				 WA_Left, (Screen->Width - ScreenWidth) / 2,
				 WA_Top, (Screen->Height - ScreenHeight) / 2,
				 WA_Flags, WFLG_GIMMEZEROZERO | WFLG_SIMPLE_REFRESH | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET | WFLG_SIZEGADGET | WFLG_SIZEBBOTTOM | WFLG_ACTIVATE,
				 WA_PubScreen, (long unsigned int)Screen,
				 WA_RMBTrap, TRUE,
				 WA_IDCMP, IDCMP_RAWKEY | IDCMP_NEWSIZE | IDCMP_CLOSEWINDOW | IDCMP_CHANGEWINDOW,
				 WA_Title, (long unsigned int)"Waiting ...",
				 WA_ScreenTitle, (long unsigned int)"aQView (by Niels Fröhling)",
				 TAG_DONE, NULL)) == NULL)	/* can't open window, what now?? */
      Error("Cannot open window\n");

    RastPort = Window->RPort;
    ViewPort = &Window->WScreen->ViewPort;
    if (ScreenDepth == 8) {
      GetRGB32(ViewPort->ColorMap, 0, 256, &OldColorMap[1]);
      oldNFree = ViewPort->ColorMap->PalExtra->pe_NFree;
      ViewPort->ColorMap->PalExtra->pe_NFree = 0;
      oldShareable = ViewPort->ColorMap->PalExtra->pe_SharableColors;
      ViewPort->ColorMap->PalExtra->pe_SharableColors = 0;
      OldColorMap[0] = 0x01000000;
    }
  }

  ChangeDisplay(width, height, depth, Palette, "Waiting ...");

  dim.X = ScreenLeft = Window->LeftEdge;
  dim.Y = ScreenTop = Window->TopEdge;
  dim.changedOffset = TRUE;
  dim.Width = ScreenWidth = Window->GZZWidth;
  dim.Height = ScreenHeight = Window->GZZHeight;
  dim.changedSize = TRUE;
  dim.dtX = Screen->LeftEdge;
  dim.dtY = Screen->TopEdge;
  dim.changedDesktopOffset = TRUE;
  dim.dtWidth = Screen->Width;
  dim.dtHeight = Screen->Height;
  dim.changedDesktopSize = TRUE;
  dim.frameBuffer = InitDisplay();
#ifdef	USE_ZBUFFER
  dim.zBuffer = InitDisplayZ();
#endif
  dim.frameDepth = ScreenDepth;
  dim.frameBPP = BytesPerPel;
  dim.frameSize = ScreenWidth * ScreenHeight;
  dim.changedBuffer = TRUE;

  return &dim;
}

void *SwapDisplay(void *oldBuffer)
{
  if (RastPort) {
    if (CyberGfxBase) {
      /*WaitBOVP(ViewPort); */
      if (FrameFormat != 0)
	WritePixelArray(oldBuffer, 0, 0, BytesPerRow, RastPort, 0, 0, ScreenWidth, ScreenHeight, FrameFormat);
      else
	BltBitMapRastPort(FrameBitMap, 0, 0, RastPort, 0, 0, ScreenWidth, ScreenHeight, 0x00C0);
    }
    else
      WriteChunkyPixels(RastPort, 0, 0, ScreenWidth, ScreenHeight, oldBuffer, BytesPerRow);
  }
  return oldBuffer;
}

void *UpdateDisplay(void *oldBuffer, short int x, short int y, short int width, short int height)
{
  if ((x < ScreenWidth) && (y < ScreenHeight) && (RastPort)) {
    if (CyberGfxBase) {
      /*WaitBOVP(ViewPort); */
      if (FrameFormat != 0)
	WritePixelArray(oldBuffer, x, y, BytesPerRow, RastPort, 0, 0, width, height, FrameFormat);
      else
	BltBitMapRastPort(FrameBitMap, x, y, RastPort, 0, 0, width, height, 0x00C0);
    }
    else
      WriteChunkyPixels(RastPort, x, y, width, height, oldBuffer, BytesPerRow);
  }
  return oldBuffer;
}

struct DisplayDimension *ChangeDisplay(short int width, short int height, char depth, struct rgb *Palette, char *Title)
{
  if (Window && ((ScreenWidth != width) || (ScreenHeight != height))) {
    ChangeWindowBox(Window,
		    (Screen->Width - width) / 2,
		    (Screen->Height - height) / 2,
		    width + Window->BorderLeft + Window->BorderRight,
		    height + Window->BorderTop + Window->BorderBottom);

    dim.X = ScreenLeft = Window->LeftEdge;
    dim.Y = ScreenTop = Window->TopEdge;
    dim.changedOffset = TRUE;
    dim.Width = ScreenWidth = Window->GZZWidth;
    dim.Height = ScreenHeight = Window->GZZHeight;
    dim.changedSize = TRUE;
    dim.frameBuffer = InitDisplay();
#ifdef	USE_ZBUFFER
    dim.zBuffer = InitDisplayZ();
#endif
    dim.frameDepth = ScreenDepth;
    dim.frameBPP = BytesPerPel;
    dim.frameSize = ScreenWidth * ScreenHeight;
    dim.changedBuffer = TRUE;
  }

  if(Window && Title)
    SetWindowTitles(Window, Title, -1);

  if (ViewPort && Palette && (ScreenDepth == 8)) {
    long unsigned int *ColorMap = NewColorMap;
    int c = 256 - 1;

    *ColorMap++ = 0x01000000;
    for (; c >= 0; c--) {
      *ColorMap++ = ((long unsigned int)(*((unsigned char *)Palette)++)) << 24;		/* red   */
      *ColorMap++ = ((long unsigned int)(*((unsigned char *)Palette)++)) << 24;		/* green */
      *ColorMap++ = ((long unsigned int)(*((unsigned char *)Palette)++)) << 24;		/* blue  */
    }
    LoadRGB32(ViewPort, NewColorMap);
  }

  SetDisplay(&dim);
  return &dim;
}

void CloseDisplay(void)
{
  if (Window) {
    if (ScreenDepth == 8) {
      LoadRGB32(ViewPort, &OldColorMap[0]);
      ViewPort->ColorMap->PalExtra->pe_NFree = oldNFree;
      ViewPort->ColorMap->PalExtra->pe_SharableColors = oldShareable;
    }
    CloseWindow(Window);
    Window = NULL;
    UnlockPubScreen(NULL, Screen);
    Screen = NULL;
    ExitDisplay();
#ifdef	USE_ZBUFFER
    tfree(ZBuffer);
    ZBuffer = NULL;
#endif
  }

  if (CyberGfxBase != NULL) {
    CloseLibrary(CyberGfxBase);
    CyberGfxBase = NULL;
  }
  if (IntuitionBase != NULL) {
    CloseLibrary((struct Library *)IntuitionBase);
    IntuitionBase = NULL;
  }
  if (GfxBase != NULL) {
    CloseLibrary((struct Library *)GfxBase);
    GfxBase = NULL;
  }
  if (AslBase != NULL) {
    CloseLibrary(AslBase);
    AslBase = NULL;
  }
}

/*
 * input
 */

static struct MsgPort *inputPort = NULL;

void OpenKeys(void)
{
  if (Window)
    inputPort = Window->UserPort;
}

bool GetKeys(struct keyEvent *eventBuffer)
{
  struct IntuiMessage *intuiMsg = NULL;

  eventBuffer->pressed = RAWKEY_NOTHING;

  if (inputPort) {
    Forbid();
/*  while ((intuiMsg = (struct IntuiMessage *)GetMsg(inputPort)) != NULL) { */
    if ((intuiMsg = (struct IntuiMessage *)GetMsg(inputPort)) != NULL) {
      long unsigned int Class = intuiMsg->Class;
      unsigned short int Code = intuiMsg->Code;
      unsigned short int Qual = intuiMsg->Qualifier;
      struct Window *IDCMPWindow = intuiMsg->IDCMPWindow;

      ReplyMsg((struct Message *)intuiMsg);

      switch (Class) {
	case IDCMP_RAWKEY:
	  eventBuffer->pressed = (unsigned char)Code;
	  eventBuffer->qualifier = (unsigned char)Qual;
	  break;
	case IDCMP_CLOSEWINDOW:
	  eventBuffer->pressed = RAWKEY_ESCAPE;
	  break;
	case IDCMP_NEWSIZE:{
	    dim.Width = ScreenWidth = IDCMPWindow->GZZWidth;
	    dim.Height = ScreenHeight = IDCMPWindow->GZZHeight;
	    dim.changedSize = TRUE;
	    dim.frameBuffer = InitDisplay();
#ifdef	USE_ZBUFFER
	    dim.zBuffer = InitDisplayZ();
#endif
	    dim.frameDepth = ScreenDepth;
	    dim.frameBPP = BytesPerPel;
	    dim.frameSize = ScreenWidth * ScreenHeight;
	    dim.changedBuffer = TRUE;

	    SetDisplay(&dim);
	  }
	  break;
	case IDCMP_CHANGEWINDOW:{
	    dim.X = ScreenLeft = IDCMPWindow->LeftEdge;
	    dim.Y = ScreenTop = IDCMPWindow->TopEdge;
	    dim.changedOffset = TRUE;

	    SetDisplay(&dim);
	  }
	  break;
      }
    }
    Permit();
    intuiMsg = (struct IntuiMessage *)-1;
  }

  return (intuiMsg != NULL);
}

void CloseKeys(void)
{
  if (inputPort) {
    struct IntuiMessage *intuiMsg;

    Forbid();
    while ((intuiMsg = (struct IntuiMessage *)GetMsg(inputPort)) != NULL)
      ReplyMsg((struct Message *)intuiMsg);
    Permit();
  }
}
