/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 G_GFXGRX.C - Graphical routines for GRX

 Functions written by Per Allansson.
*/

/* the includes */
#include "deu.h"
#include <math.h>
#include <pc.h>
#include <grx.h>
#include <bios.h>
#include "d_misc.h"
#include "d_config.h"
#include "d_wads.h"
#include "g_mouse.h"
#include "g_colcnv.h"
#include "g_gfx.h"


/* the global variables */
Int16  OrigX;         /* the X origin */
Int16  OrigY;         /* the Y origin */
float  Scale;         /* the scale value */
UInt16 PointerX;      /* X position of pointer */
UInt16 PointerY;      /* Y position of pointer */
UInt16 ScrMaxX;       /* maximum X screen coord */
UInt16 ScrMaxY;       /* maximum Y screen coord */
UInt16 ScrCenterX;    /* X coord of screen center */
UInt16 ScrCenterY;    /* Y coord of screen center */

/* private */
static int current_bk_color;   /* current background color */
static GrLineOption linestyle; /* current line style */
static int writemode;          /* current write mode */
static GrTextOption textopt;   /* text options */

/* almost private: shared with g_colcnv.c */
int num_colors = -1; /* number of colors available */


/*
   Initialise the graphics display.
*/

void InitGfx(void)
{
  static firsttime = 1;
  static int  res[6][3] = /* width, height and number of colors for GRX */
  {
    { 640,  480,  16}, /* 0 */
    { 320,  200, 256}, /* 1 */
    { 640,  480, 256}, /* 2 */
    { 800,  600, 256}, /* 3 */
    {1024,  768, 256}, /* 4 */
    {1280, 1024, 256}  /* 5 */
  };

  printf("Switching to graphics mode...\n");
  if (Config.videoMode < 0)
    Config.videoMode = 0;
  if (Config.videoMode > 5)
    Config.videoMode = 5;
  GrSetMode(GR_width_height_color_graphics,
            res[Config.videoMode][0], res[Config.videoMode][1],
            res[Config.videoMode][2]);
  num_colors = res[Config.videoMode][2];
  if (num_colors >= 256)
    SetDoomPalette(0);
  SetColor(WHITE);
  if (firsttime)
    {
       firsttime = 0;
       if (!(textopt.txo_font = GrLoadFont("@:pc8x8.fnt")))
          ProgError("BUG: Can't load pc8x8 font.");
       textopt.txo_bgcolor = GrNOCOLOR;;
       textopt.txo_xmag = 1;
       textopt.txo_ymag = 1;
       textopt.txo_direct = GR_TEXT_RIGHT;
       textopt.txo_xalign = GR_ALIGN_LEFT;
       textopt.txo_yalign = GR_ALIGN_TOP;
       textopt.txo_chrtype = GR_BYTE_TEXT;
    }
  current_bk_color = TranslateToDoomColor(BLACK);
  linestyle.lno_pattlen = 0;
  linestyle.lno_dashpat = NULL;
  linestyle.lno_width = 1;
  ScrMaxX = GrSizeX();
  ScrMaxY = GrSizeY();
  ScrCenterX = ScrMaxX / 2;
  ScrCenterY = ScrMaxY / 2;
  /* Initialise the mouse driver */
  InitMouseDriver();
  if (UseMouse)
    {
      ResetMouseLimits();
      SetMouseCoords(ScrCenterX, ScrCenterY);
      ShowMousePointer();
    }
}



/*
   Terminate the graphics display.
*/

void TermGfx(void)
{
  if (num_colors > 0)
    {
      if (UseMouse)
        HideMousePointer();
      GrSetMode(GR_default_text);
      num_colors = -1;
    }
}



/*
   Returns TRUE if DEU is in graphics mode, FALSE if it is in text mode.
*/

Bool InGfxMode(void)
{
  return (num_colors > 0);
}



/*
   Clear the screen.
*/

void ClearScreen(void)
{
  GrClearScreen(GrBlack());
}


/*
   Set the line drawing mode: normal or XOR.
*/

void SetLineDrawingMode(int mode)
{
  switch (mode)
    {
    case DRAW_NORMAL:
      writemode = GrWRITE;
      break;
    case DRAW_XOR:
      writemode = GrXOR;
      break;
    default:
      ProgError("BUG: Invalid line drawing mode: %d", mode);
      break;
    }
  linestyle.lno_color = writemode | (linestyle.lno_color & 0x0FFFFFF);
}



/*
   Set the line style: width and pattern (solid, dotted, dashed).
*/

void SetLinePatternAndWidth(int pattern, int width)
{
  if (width != 1 && width != 3)
    ProgError("BUG: Invalid line width: %d", width);
  switch (pattern)
    {
    case SOLID_PATTERN:
      linestyle.lno_pattlen = 0;
      linestyle.lno_dashpat = NULL;
      break;
    case DOTTED_PATTERN:
      linestyle.lno_pattlen = 3;
      linestyle.lno_dashpat = "\0\2\2";
      break;
    case DASHED_PATTERN:
      linestyle.lno_pattlen = 5;
      linestyle.lno_dashpat = "\0\3\5\3\5";
      break;
    default:
      ProgError("BUG: Invalid line pattern: %d", pattern);
      break;
    }
  linestyle.lno_width = width;
}



/*
   Draw a line on the screen from map coords.
*/

void DrawMapLine(Int16 mapXstart, Int16 mapYstart, Int16 mapXend, Int16 mapYend)
{
  GrCustomLine(SCREENX(mapXstart), SCREENY(mapYstart),
               SCREENX(mapXend), SCREENY(mapYend), &linestyle);
}



/*
   Draw a circle on the screen from map coords.
*/

void DrawMapCircle(Int16 mapXcenter, Int16 mapYcenter, Int16 mapRadius)
{
  GrCircle(SCREENX(mapXcenter), SCREENY(mapYcenter), (int) (mapRadius * Scale),
           linestyle.lno_color);
}



/*
   Draw an arrow on the screen from map coords.
*/

void DrawMapVector(Int16 mapXstart, Int16 mapYstart, Int16 mapXend, Int16 mapYend)
{
  Int16  scrXstart = SCREENX(mapXstart);
  Int16  scrYstart = SCREENY(mapYstart);
  Int16  scrXend   = SCREENX(mapXend);
  Int16  scrYend   = SCREENY(mapYend);
  double r         = hypot((double) (scrXstart - scrXend),
                           (double) (scrYstart - scrYend));
  Int16  scrXoff   = (r >= 1.0) ? (Int16) ((scrXstart - scrXend) * 8.0
                                           / r * Scale) : 0;
  Int16  scrYoff   = (r >= 1.0) ? (Int16) ((scrYstart - scrYend) * 8.0
                                           / r * Scale) : 0;

  DrawScreenLine(scrXstart, scrYstart, scrXend, scrYend);
  scrXstart = scrXend + 2 * scrXoff;
  scrYstart = scrYend + 2 * scrYoff;
  DrawScreenLine(scrXstart - scrYoff, scrYstart + scrXoff, scrXend, scrYend);
  DrawScreenLine(scrXstart + scrYoff, scrYstart - scrXoff, scrXend, scrYend);
}



/*
   Draw an arrow on the screen from map coords and angle (0 - 65535).
*/

void DrawMapArrow(Int16 mapXstart, Int16 mapYstart, UInt16 angle)
{
  Int16  mapXend   = mapXstart + (Int16) (50 * cos(angle / 10430.37835));
  Int16  mapYend   = mapYstart + (Int16) (50 * sin(angle / 10430.37835));
  Int16  scrXstart = SCREENX(mapXstart);
  Int16  scrYstart = SCREENY(mapYstart);
  Int16  scrXend   = SCREENX(mapXend);
  Int16  scrYend   = SCREENY(mapYend);
  double r         = hypot(scrXstart - scrXend, scrYstart - scrYend);
  Int16  scrXoff   = (r >= 1.0) ? (Int16) ((scrXstart - scrXend) * 8.0
                                           / r * Scale) : 0;
  Int16  scrYoff   = (r >= 1.0) ? (Int16) ((scrYstart - scrYend) * 8.0
                                           / r * Scale) : 0;

  GrLine(scrXstart, scrYstart, scrXend, scrYend, linestyle.lno_color);
  scrXstart = scrXend + 2 * scrXoff;
  scrYstart = scrYend + 2 * scrYoff;
  GrLine(scrXstart - scrYoff, scrYstart + scrXoff, scrXend, scrYend, linestyle.lno_color);
  GrLine(scrXstart + scrYoff, scrYstart - scrXoff, scrXend, scrYend, linestyle.lno_color);
}



/*
   Draw a pixel on the screen.
*/

void DrawScreenPixel(Int16 X, Int16 Y)
{
  GrPlot(X, Y, linestyle.lno_color);
}



/*
   Draw a line on the screen from screen coords.
*/

void DrawScreenLine(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrCustomLine(Xstart, Ystart, Xend, Yend, &linestyle);
}



/*
   Draw a rectangle on the screen from screen coords.
*/

void DrawScreenRectangle(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrBox(Xstart, Ystart, Xend, Yend, linestyle.lno_color);
}



/*
   Draw a filled in box on the screen from screen coords.
*/

void DrawScreenBox(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrFilledBox(Xstart, Ystart, Xend, Yend, linestyle.lno_color);
}



/*
   Draw a filled-in 3D-box on the screen from screen coords.
*/

void DrawScreenBox3D(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrFilledBox(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1, TranslateToDoomColor(LIGHTGRAY));
  SetColor(DARKGRAY);
  GrLine(Xstart, Yend, Xend, Yend, linestyle.lno_color);
  GrLine(Xend, Ystart, Xend, Yend, linestyle.lno_color);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      GrLine(Xstart + 1, Yend - 1, Xend - 1, Yend - 1, linestyle.lno_color);
      GrLine(Xend - 1, Ystart + 1, Xend - 1, Yend - 1, linestyle.lno_color);
      SetColor(WHITE);
      GrLine(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1, linestyle.lno_color);
      GrLine(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1, linestyle.lno_color);
    }
  SetColor(WHITE);
  GrLine(Xstart, Ystart, Xend, Ystart, linestyle.lno_color);
  GrLine(Xstart, Ystart, Xstart, Yend, linestyle.lno_color);
  SetColor(BLACK);
}



/*
   Draw a sunken 3D-box on the screen from screen coords.
*/

void DrawScreenBoxSunken(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrFilledBox(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1, TranslateToDoomColor(LIGHTGRAY));
  SetColor(WHITE);
  GrLine(Xstart, Yend, Xend, Yend, linestyle.lno_color);
  GrLine(Xend, Ystart, Xend, Yend, linestyle.lno_color);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      GrLine(Xstart + 1, Yend - 1, Xend - 1, Yend - 1, linestyle.lno_color);
      GrLine(Xend - 1, Ystart + 1, Xend - 1, Yend - 1, linestyle.lno_color);
      SetColor(DARKGRAY);
      GrLine(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1, linestyle.lno_color);
      GrLine(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1, linestyle.lno_color);
    }
  SetColor(DARKGRAY);
  GrLine(Xstart, Ystart, Xend, Ystart, linestyle.lno_color);
  GrLine(Xstart, Ystart, Xstart, Yend, linestyle.lno_color);
  SetColor(WHITE);
}



/*
   Draw a hollow 3D-box on the screen from screen coords.
*/

void DrawScreenBoxHollow(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  GrFilledBox(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1, TranslateToDoomColor(BLACK));
  SetColor(WHITE);
  GrLine(Xstart, Yend, Xend, Yend, linestyle.lno_color);
  GrLine(Xend, Ystart, Xend, Yend, linestyle.lno_color);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      GrLine(Xstart + 1, Yend - 1, Xend - 1, Yend - 1, linestyle.lno_color);
      GrLine(Xend - 1, Ystart + 1, Xend - 1, Yend - 1, linestyle.lno_color);
      SetColor(DARKGRAY);
      GrLine(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1, linestyle.lno_color);
      GrLine(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1, linestyle.lno_color);
    }
  SetColor(DARKGRAY);
  GrLine(Xstart, Ystart, Xend, Ystart, linestyle.lno_color);
  GrLine(Xstart, Ystart, Xstart, Yend, linestyle.lno_color);
  SetColor(WHITE);
}



/*
   Draw a meter bar on the screen from screen coords (in a hollow box); max. value = 1.0
*/

void DrawScreenMeter(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend, float value)
{
  if (value < 0.0)
    value = 0.0;
  if (value > 1.0)
    value = 1.0;
  GrFilledBox(Xstart + 1 + (Int16) ((Xend - Xstart - 2) * value),
              Ystart + 1, Xend - 1, Yend - 1, TranslateToDoomColor(BLACK));
  GrFilledBox(Xstart + 1, Ystart + 1, Xstart + 1 + (Int16) ((Xend - Xstart - 2) * value),
              Yend - 1, TranslateToDoomColor(LIGHTGREEN));
}



/*
   Write text to the screen.
*/

void DrawScreenText(Int16 Xstart, Int16 Ystart, char *msg, ...)
{
  static Int16 lastX;
  static Int16 lastY;
  char temp[120];
  va_list args;

  va_start(args, msg);
  vsprintf(temp, msg, args);
  va_end(args);
  if (Xstart < 0)
    Xstart = lastX;
  if (Ystart < 0)
    Ystart = lastY;
  textopt.txo_fgcolor = linestyle.lno_color;
  GrDrawString(temp, strlen(temp), Xstart, Ystart, &textopt);
  lastX = Xstart;
  lastY = Ystart + 10; /* or, more correct, .. + GrStringHeight(temp, strlen(temp), &textopt); */
}


/*
   Draw a bitmap on the screen (from data in "flat" format).
*/

void DrawScreenBitmap(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend, UInt8 far *pixels)
{
  Int16 i, j;

  if (num_colors == 256)
    {
#ifdef THIS_CODE_WORKS_NOW
      GrContext *gc;
      Int16 pixsize;

      pixsize = GrContextSize(Xend - Xstart + 1, Yend - Ystart + 1);
      gc = (GrContext *) GetFarMemory(pixsize + sizeof(GrContext));
      GrSetContext(NULL);
      GrResetClipBox();
      GrCreateContext(Xend - Xstart + 1, Yend - Ystart + 1, (char *)(gc + sizeof(GrContext)), gc);
      GrBitBlt(gc, 0, 0, NULL, Xstart, Ystart, Xend, Yend, GrWRITE);
      memcpy(gc + sizeof(GrContext), pixels, pixsize);
      GrBitBlt(NULL, Xstart, Ystart, gc, 0, 0, gc->gc_xmax, gc->gc_ymax, GrWRITE);
      FreeFarMemory(gc);
#else      
      for (i = Ystart; i <= Yend; i++)
        for (j = Xstart; j <= Xend; j++)
          {
            GrPlot(j, i, *pixels);
            pixels++;
          }
#endif
    }
  else
    {
      for (i = Ystart; i <= Yend; i++)
        for (j = Xstart; j <= Xend; j++)
          {
            GrPlot(j, i, TranslateTo16Color(*pixels));
            pixels++;
          }
    }
}



/*
   Draw (or erase) the pointer if we aren't using the mouse.
*/

void DrawPointer(Bool rulers)
{
  UInt16 r;

  /* use XOR mode : drawing the pointer twice erases it */
  SetLineDrawingMode(DRAW_XOR);
  /* draw the pointer */
  if (rulers)
    {
      SetColor(MAGENTA);
      r = (UInt16) (512 * Scale);
      GrCircle(PointerX, PointerY, r, linestyle.lno_color);
      r >>= 1;
      GrCircle(PointerX, PointerY, r, linestyle.lno_color);
      r >>= 1;
      GrCircle(PointerX, PointerY, r, linestyle.lno_color);
      r >>= 1;
      GrCircle(PointerX, PointerY, r, linestyle.lno_color);
      r = (UInt16) (1024 * Scale);
      GrLine(PointerX - r, PointerY, PointerX + r, PointerY, linestyle.lno_color);
      GrLine(PointerX, PointerY - r, PointerX, PointerY + r, linestyle.lno_color);
    }
  else
    {
      SetColor(YELLOW);
      GrLine(PointerX - 15, PointerY - 13, PointerX + 15, PointerY + 13, linestyle.lno_color);
      GrLine(PointerX - 15, PointerY + 13, PointerX + 15, PointerY - 13, linestyle.lno_color);
    }
   /* restore normal write mode */
  SetLineDrawingMode(DRAW_NORMAL);
}



/*
   Load one "playpal" palette and change all palette colours.
*/

void SetDoomPalette(int playpalnum)
{
  MDirPtr     dir;
  UInt8 huge *dpal;
  int         n;

  if (playpalnum < 0 && playpalnum > 13)
    return;
  dir = FindMasterDir(MasterDir, "PLAYPAL");
  if (dir)
    {
      dpal = (UInt8 huge *)GetFarMemory(768 * sizeof(UInt8));
      BasicWadSeek(dir->wadfile, dir->dir.start);
      for (n = 0; n <= playpalnum; n++)
        BasicWadRead(dir->wadfile, dpal, 768L);

      /* gamma correction */
      if (Config.gamma > 0 && Config.gamma <= 4)
        {
          float gmult[5] = {1.0, 0.75, 0.55, 0.4, 0.3};

          for (n = 0; n < 768; n++)
            dpal[n] = (UInt8) (pow(((double) dpal[n] / 255.0), gmult[Config.gamma]) 
                               * 255.0);
        }

      if (Config.forceWhite == TRUE)
        {
          dpal[3 * WHITE] = 255;
          dpal[3 * WHITE + 1] = 255;
          dpal[3 * WHITE + 2] = 255;
        }

      GrResetColors();
      for (n = 0; n < 254; n++)
        GrAllocCell();
      for (n = 0; n < 256; n++)
        GrSetColor(n, dpal[3 * n], dpal[3 * n + 1], dpal[3 * n + 2]);
      GrRefreshColors();
      FreeFarMemory(dpal);
    }
}



/*
   Set the current drawing color (from one of the 16 color constants).
*/

void SetColor(int color16)
{
  if (num_colors >= 256)
    color16 = TranslateToDoomColor(color16);
  linestyle.lno_color = color16 | writemode;
}



/*
   Set the current drawing color (from Doom palette #0).
*/

void SetDoomColor(int color256)
{
  if (num_colors < 256)
    color256 = TranslateTo16Color(color256);
  linestyle.lno_color = writemode | color256;
}



/*
   Wait for a key.
*/

UInt16 WaitForKey(void)
{
  UInt16 key;

  key = bioskey(0);
  if ((key & 0x00FF) != 0)
    return key & 0x00FF;
  else
    {
      key = key & 0xFF00;
      if (key == PCKEY_INS && (bioskey(2) & 0x03) != 0)
         return PCKEY_SHIFT_INS;
      else if (key == PCKEY_DEL && (bioskey(2) & 0x03) != 0)
         return PCKEY_SHIFT_DEL;
      else
         return key;
    }
}


/*
   Test is a key has been pressed (non-blocking).
*/

Bool IsKeyPressed(void)
{
  if (bioskey(1) != 0)
    return TRUE;
  else
    return FALSE;
}


/*
   Get the status of the modifiers: Shift, Alt, Ctrl,... (non-blocking).
*/

UInt16 GetAltKeys(void)
{
  return bioskey(2);
}



/* end of file */
