/*----------------------------------------------------------------------------*
 | 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_GFXBGI.C - Graphical routines for DOS (BGI or GRX using BCC2GRX)

*/

/* the includes */
#include "deu.h"
#include <math.h>
#if defined(__TURBOC__)
#include <dos.h>
#include <bios.h>
#include <graphics.h>
#elif defined(__GO32__)
#include <pc.h>
#include <libbcc.h>
#endif
#include "d_misc.h"
#include "d_config.h"
#include "d_wads.h"
#include "g_mouse.h"
#include "g_colcnv.h"
#include "g_gfx.h"

/* if your graphics driver doesn't like circles, draw squares instead */
#ifdef NO_CIRCLES
#define circle(x, y, r)        line(x - r, y - r, x - r, y + r); \
                                line(x - r, y + r, x + r, y + r); \
                                line(x + r, y + r, x + r, y - r); \
                                line(x + r, y - r, x - r, y - r)
#endif /* NO_CIRCLES */

#define BGI_PATH "."

/* 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_color;   /* current color */
/* almost private: shared with g_colcnv.c */
int        num_colors = -1; /* number of colors available */

/*
   Initialise the graphics display.
*/

void InitGfx(void)
{
  static Bool first_time = TRUE;
  static int  gdriver;
  static int  gmode;
  int         errorcode;
#ifdef __GO32__
  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 */
  };
#endif

  printf("Switching to graphics mode...\n");
  if (Config.videoMode < 0)
    Config.videoMode = 0;
  gmode = Config.videoMode;
  if (first_time)
    {
      gdriver = VGA;
#ifdef __TURBOC__
      if (Config.BGIDriver != NULL)
        {
          gdriver = installuserdriver(Config.BGIDriver, NULL);
          errorcode = graphresult();
          if (errorcode != grOk)
            ProgError("%s", grapherrormsg(errorcode));
        }
#endif
      first_time = FALSE;
    }
#ifdef __GO32__
  if (Config.videoMode > 5)
    Config.videoMode = 5;
  set_BGI_mode_whc(&gdriver, &gmode,
                   res[Config.videoMode][0], res[Config.videoMode][1],
                   res[Config.videoMode][2]);
#endif
  initgraph(&gdriver, &gmode, BGI_PATH);
  errorcode = graphresult();
  if (errorcode != grOk)
    ProgError("%s", grapherrormsg(errorcode));
  num_colors = getmaxcolor() + 1;
  if (num_colors >= 256)
    SetDoomPalette(0);
  SetColor(WHITE);
  setbkcolor(TranslateToDoomColor(BLACK));
  setlinestyle(0, 0, 1);
  settextstyle(0, 0, 1);
  ScrMaxX = getmaxx();
  ScrMaxY = getmaxy();
  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();
      closegraph();
      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)
{
  cleardevice();
}


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

void SetLineDrawingMode(int mode)
{
  switch (mode)
    {
    case DRAW_NORMAL:
      setwritemode(COPY_PUT);
      break;
    case DRAW_XOR:
      setwritemode(XOR_PUT);
      break;
    default:
      ProgError("BUG: Invalid line drawing mode: %d", mode);
      break;
    }
}



/*
   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:
      setlinestyle(SOLID_LINE, 0, width);
      break;
    case DOTTED_PATTERN:
      setlinestyle(DOTTED_LINE, 0, width);
      break;
    case DASHED_PATTERN:
      setlinestyle(DASHED_LINE, 0, width);
      break;
    default:
      ProgError("BUG: Invalid line pattern: %d", pattern);
      break;
    }
}



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

void DrawMapLine(Int16 mapXstart, Int16 mapYstart, Int16 mapXend, Int16 mapYend)
{
  line(SCREENX(mapXstart), SCREENY(mapYstart),
       SCREENX(mapXend),   SCREENY(mapYend));
}



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

void DrawMapCircle(Int16 mapXcenter, Int16 mapYcenter, Int16 mapRadius)
{
  circle(SCREENX(mapXcenter), SCREENY(mapYcenter),
         (int) (mapRadius * Scale));
}



/*
   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;

  line(scrXstart, scrYstart, scrXend, scrYend);
  scrXstart = scrXend + 2 * scrXoff;
  scrYstart = scrYend + 2 * scrYoff;
  line(scrXstart - scrYoff, scrYstart + scrXoff, scrXend, scrYend);
  line(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;

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



/*
   Draw a pixel on the screen.
*/

void DrawScreenPixel(Int16 X, Int16 Y)
{
  putpixel(X, Y, current_color);
}



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

void DrawScreenLine(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  line(Xstart, Ystart, Xend, Yend);
}



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

void DrawScreenRectangle(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  rectangle(Xstart, Ystart, Xend, Yend);
}



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

void DrawScreenBox(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  setfillstyle(1, current_color);
  bar(Xstart, Ystart, Xend, Yend);
}



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

void DrawScreenBox3D(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  setfillstyle(1, TranslateToDoomColor(LIGHTGRAY));
  bar(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1);
  SetColor(DARKGRAY);
  line(Xstart, Yend, Xend, Yend);
  line(Xend, Ystart, Xend, Yend);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      line(Xstart + 1, Yend - 1, Xend - 1, Yend - 1);
      line(Xend - 1, Ystart + 1, Xend - 1, Yend - 1);
      SetColor(WHITE);
      line(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1);
      line(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1);
    }
  SetColor(WHITE);
  line(Xstart, Ystart, Xend, Ystart);
  line(Xstart, Ystart, Xstart, Yend);
  SetColor(BLACK);
}



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

void DrawScreenBoxSunken(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  setfillstyle(1, TranslateToDoomColor(LIGHTGRAY));
  bar(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1);
  SetColor(WHITE);
  line(Xstart, Yend, Xend, Yend);
  line(Xend, Ystart, Xend, Yend);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      line(Xstart + 1, Yend - 1, Xend - 1, Yend - 1);
      line(Xend - 1, Ystart + 1, Xend - 1, Yend - 1);
      SetColor(DARKGRAY);
      line(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1);
      line(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1);
    }
  SetColor(DARKGRAY);
  line(Xstart, Ystart, Xend, Ystart);
  line(Xstart, Ystart, Xstart, Yend);
  SetColor(WHITE);
}



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

void DrawScreenBoxHollow(Int16 Xstart, Int16 Ystart, Int16 Xend, Int16 Yend)
{
  setfillstyle(1, TranslateToDoomColor(BLACK));
  bar(Xstart + 1, Ystart + 1, Xend - 1, Yend - 1);
  SetColor(WHITE);
  line(Xstart, Yend, Xend, Yend);
  line(Xend, Ystart, Xend, Yend);
  if (Xend - Xstart > 20 && Yend - Ystart > 20)
    {
      line(Xstart + 1, Yend - 1, Xend - 1, Yend - 1);
      line(Xend - 1, Ystart + 1, Xend - 1, Yend - 1);
      SetColor(DARKGRAY);
      line(Xstart + 1, Ystart + 1, Xstart + 1, Yend - 1);
      line(Xstart + 1, Ystart + 1, Xend - 1, Ystart + 1);
    }
  SetColor(DARKGRAY);
  line(Xstart, Ystart, Xend, Ystart);
  line(Xstart, Ystart, Xstart, Yend);
  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;
  setfillstyle(1, TranslateToDoomColor(BLACK));
  bar(Xstart + 1 + (Int16) ((Xend - Xstart - 2) * value), Ystart + 1, Xend - 1, Yend - 1);
  setfillstyle(1, TranslateToDoomColor(LIGHTGREEN));
  bar(Xstart + 1, Ystart + 1, Xstart + 1 + (Int16) ((Xend - Xstart - 2) * value), Yend - 1);
}



/*
   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;
  outtextxy(Xstart, Ystart, temp);
  lastX = Xstart;
  lastY = Ystart + 10;  /* or textheight("W") ? */
}


/*
   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)
    {
      UInt8 far *pixies;
      Int16      memsize;

      memsize = (Xend - Xstart + 1) * (Yend - Ystart + 1);
#if defined(__TURBOC__)
      pixies = (UInt8 far *) GetFarMemory(4 + memsize);
      ((Int16 far *)pixies)[0] = Xend - Xstart + 1; /* May need to add kludge for VESA bgi's here ... */
      ((Int16 far *)pixies)[1] = Yend - Ystart + 1; /*  The kludge is: remove +1... */
      memcpy(&(pixies[4]), pixels, memsize);
#elif defined(__GNUC__)
      /* BCC2GRX's putimage() requires more info than the BGI version,
         so it is not possible to "hack" the width and height in the
         structure.  Thus the following kludge: use getimage() first */
      pixies = (Int8 far *) GetFarMemory(memsize + sizeof(GrContext));
      getimage(Xstart, Ystart, Xend, Yend, pixies);
      memcpy(pixies + sizeof(GrContext), pixels, memsize);
#endif
      putimage(Xstart, Ystart, pixies, COPY_PUT);
      FreeFarMemory(pixies);
    }
  else
    {
      for (i = Ystart; i <= Yend; i++)
        for (j = Xstart; j <= Xend; j++)
          {
            putpixel(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 */
  setwritemode(XOR_PUT);
  /* draw the pointer */
  if (rulers)
    {
      SetColor(MAGENTA);
      r = (UInt16) (512 * Scale);
      circle(PointerX, PointerY, r);
      r >>= 1;
      circle(PointerX, PointerY, r);
      r >>= 1;
      circle(PointerX, PointerY, r);
      r >>= 1;
      circle(PointerX, PointerY, r);
      r = (UInt16) (1024 * Scale);
      line(PointerX - r, PointerY, PointerX + r, PointerY);
      line(PointerX, PointerY - r, PointerX, PointerY + r);
    }
  else
    {
      SetColor(YELLOW);
      line(PointerX - 15, PointerY - 13, PointerX + 15, PointerY + 13);
      line(PointerX - 15, PointerY + 13, PointerX + 15, PointerY - 13);
    }
   /* restore normal write mode */
  setwritemode(COPY_PUT);
}



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

#ifdef __GO32__
      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]);
#endif
#ifdef __TURBOC__
      for (n = 0; n < 768; n++)
        dpal[n] /= 4;

      _AX = 0x1012;
      _BX = 0;
      _CX = 256;
      _ES = FP_SEG(dpal);
      _DX = FP_OFF(dpal);
      __int__(0x10);
#endif
      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);
  setcolor(color16);
  /* update the global variable */
  current_color = color16;
}



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

void SetDoomColor(int color256)
{
  if (num_colors < 256)
    color256 = TranslateTo16Color(color256);
  setcolor(color256);
  /* update the global variable */
  current_color = 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 */
