/*  gfxcore.cpp
    Display independent graphic primitives

    Copyright (c)2001 by Thomas Kindler, thomas.kindler@gmx.de

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files -----
//
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>

#include "gfxcore.h"
#include "ztimer.h"
#include "misc.h"


#include "sysfont.cpp"


static struct {
  Bitmap  bitmap;
  char    bmBits[GFX_SCRATCH_SIZE];
} scratch = {0, 0, GFX_SCRATCH_SIZE};

Bitmap *_gfx_scratch = (Bitmap*)&scratch;
Font   *_gfx_defaultFont = NULL;

// some fillStyles
//
char emptyFill[]     = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
char solidFill[]     = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
char lineFill[]      = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };
char ltSlashFill[]   = { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 };
char slashFill[]     = { 0x99, 0xcc, 0x66, 0x33, 0x99, 0xcc, 0x66, 0x33 };
char bkSlashFill[]   = { 0x33, 0x66, 0xcc, 0x99, 0x33, 0x66, 0xcc, 0x99 };
char ltBkSlashFill[] = { 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88 };
char hatchFill[]     = { 0xff, 0x55, 0xff, 0x55, 0xff, 0x55, 0xff, 0x55 };
char xHatchFill[]    = { 0x55, 0x22, 0x55, 0x88, 0x55, 0x22, 0x55, 0x88 };
char xLeaveFill[]    = { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa };
char wDotFill[]      = { 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44 };
char cDotFill[]      = { 0x11, 0x44, 0x88, 0x22, 0x11, 0x44, 0x88, 0x22 };

/**
 * Initialize a graphics structure. All settings
 * of the graphics contexts are reset to their default
 * values.
 *
 * \param  gfx  Pointer to Graphics structure.
 * \param  bm   Pointer to Bitmap to work on.
 *
 */
void GFX_Init(Graphics *gfx, Bitmap *bm)
{
  memset(gfx, 0, sizeof(Graphics));
  gfx->bitmap    = bm;
  gfx->font      = _font_sysfont;
  gfx->fillStyle = solidFill;
  gfx->color     = 1;
  gfx->blitMode  = BLT_OR;
}


/**
 * Copy a graphics context structure. This function
 * is quite useful for creating a temporary context
 * inside a drawing function. So You can change drawing
 * modes without modifying the original context.
 *
 * \param  gfx  Pointer to Graphics structure.
 * \param  bm   Pointer to Bitmap to work on.
 */
void GFX_Init(Graphics *gfx, Graphics *gfx2)
{
  memcpy(gfx, gfx2, sizeof(Graphics));
}


/**
 * Set the current font.
 * If font is NULL, the default font is set.
 *
 */
void GFX_SetFont(Graphics *gfx, Font *font)
{
  if (!font)
    font = _font_sysfont;
  gfx->font = font;
}


/**
 * Set the current fillStyle.
 *
 */
void GFX_SetFillStyle(Graphics *gfx, char *style)
{
  gfx->fillStyle = style;
}


void GFX_SetColor(Graphics *gfx, int color)
{
  gfx->color = color ? 1 : 0;
}


void GFX_SetBlitMode(Graphics *gfx, int mode)
{
  gfx->blitMode = mode & 15;
}


/**
 * Set the current position to (x,y)
 *
 */
void GFX_MoveTo(Graphics *gfx, int x, int y)
{
  gfx->x = x;
  gfx->y = y;
}


/**
 * Move the current position by (dx,dy)
 *
 */
void GFX_MoveRel(Graphics *gfx, int dx, int dy)
{
  gfx->x += dx;
  gfx->y += dy;
}


/**
 * Draw a horizontal line.
 * \note
 *   use this function directly instead of calling line
 *   whenever possible!
 */
void GFX_HLine(Graphics *gfx, int x, int y, int w)
{
  // clip coordinates
  //
  if (y<0 || y>= gfx->bitmap->height)
    return;
  if (x<0)
    { w -= -x; x = 0; }
  if (x+w >= gfx->bitmap->width)
    w -= (x+w)-gfx->bitmap->width;
  if (w<=0)
    return;

  char *dst   = &gfx->bitmap->bmBits[x+(y>>3)*gfx->bitmap->width];
  char  mask  = 1 << (y & 7);
  int   color = gfx->color;

  asm {
    push  bx
    push  cx
    push  di
    push  es

    les   di, dst
    mov   cx, w
    mov   bl, mask
    mov   bh, bl
    cmp   color, 0
    je    clr

set:
    shr   cx, 1
    lahf          // save carry flag
    jz    set_onepix
set_loop:
    or    word ptr [es:di], bx
    add   di, 2
    loop  set_loop
    sahf          // restore carry flag
    jnc   done
set_onepix:
    or    byte ptr [es:di], bl
    stosb
    jmp   done

clr:
    not   bx
    shr   cx, 1
    lahf          // save carry flag
    jz    clr_onepix
clr_loop:
    and   word ptr [es:di], ax
    add   di, 2
    stosw
    loop  clr_loop
    sahf          // restore carry flag
    jnc   done
clr_onepix:
    and   byte ptr [es:di], bl
    stosb

done:
    pop   es
    pop   di
    pop   cx
    pop   bx
  }
}



/**
 * Draw a vertical line.
 * \note
 *   use this function directly instead of calling line
 *   whenever possible!
 */
void GFX_VLine(Graphics *gfx, int x, int y, int h)
{
  // clip coordinates
  //
  if (x<0 || x>= gfx->bitmap->width)
    return;
  if (y<0)
    { h -= -y;  y = 0; }
  if (y+h >= gfx->bitmap->height)
    h -= (y+h)-gfx->bitmap->height;
  if (h<=0)
    return;

  char *dst = &gfx->bitmap->bmBits[x+(y>>3)*gfx->bitmap->width];

  // calculate destination masks
  //
  unsigned char fbm, lbm;
  fbm = 0xff << (   y & 7);
  lbm = 0xff >> (-y-h & 7);

  // calculate number of pages
  //
  int psize = gfx->bitmap->width;
  int pages = ((y+h+7)>>3) - (y>>3);
  if (pages == 1)
    fbm &= lbm;

  int color = gfx->color;

  asm {
    push  bx
    push  cx
    push  di
    push  es

    les   di, dst
    mov   al, fbm
    mov   cx, pages
    mov   bx, psize

    cmp   color, 0
    je    clr

set_loop:
    or    byte ptr [es:di], al
    add   di, bx
    mov   al, 0xff
    dec   cx
    cmp   cx, 1
    jg    set_loop
    mov   al, lbm
    je    set_loop
    jmp   done

clr:
    not   al
clr_loop:
    and   byte ptr [es:di], al
    add   di, bx
    xor   al, al
    dec   cx
    cmp   cx, 1
    jg    clr_loop
    mov   al, lbm
    not   al
    je    clr_loop

done:
    pop   es
    pop   di
    pop   cx
    pop   bx
  }

}


/**
 * Draw a line using the bresenham algorithm.
 * \param  gfx  Graphics context to use.
 * \param  x1, y1, x2, y2  line coordinates.
 * \note
 *   this function is quite slow, and optimizing it would
 *   take a somewhat great effort. (subpixel-accurate cohen-
 *   sutherland clipping should be implemented, assembler
 *   versions for 4 octants * 2 drawing colors..)
 *   If you really need a faster line implementation, drop
 *   a mail to thomas.kindler@gmx.de (a nice crate of beer
 *   might be helpful ;)
 */
void GFX_Line(Graphics *gfx, int x1, int y1, int x2, int y2)
{
  int dy = y2 - y1;
  int dx = x2 - x1;
  int stepx, stepy;

  if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; }
  if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }

  if (dx == 0)  GFX_VLine(gfx, min(x1, x2), min(y1, y2), dy);
  if (dy == 0)  GFX_HLine(gfx, min(x1, x2), min(y1, y2), dx);

  dy <<= 1;                                    // dy is now 2*dy
  dx <<= 1;                                    // dx is now 2*dx

  BM_PutPixel(gfx->bitmap, x1, y1, gfx->color);
  if (dx > dy) {
    int fraction = dy - (dx >> 1);             // same as 2*dy - dx
    while (x1 != x2) {
      if (fraction >= 0) {
        y1 += stepy;
        fraction -= dx;                        // same as fraction -= 2*dx
      }
      x1 += stepx;
      fraction += dy;                          // same as fraction -= 2*dy
      BM_PutPixel(gfx->bitmap, x1, y1, gfx->color);
    }
  } else {
    int fraction = dx - (dy >> 1);
    while (y1 != y2) {
      if (fraction >= 0) {
        x1 += stepx;
        fraction -= dy;
      }
      y1 += stepy;
      fraction += dx;
      BM_PutPixel(gfx->bitmap, x1, y1, gfx->color);
    }
  }
}


/**
 * Draw a line relative from the current position.
 *
 * \param  gfx     Graphics context to use.
 * \param  dx, dy  distance from current position.
 */
void GFX_LineRel(Graphics *gfx, int dx, int dy)
{
  GFX_Line(gfx, gfx->x, gfx->y, gfx->x+dx, gfx->y+dy);
  gfx->x += dx;
  gfx->y += dy;
}


/**
 * Draw a line from the current position to (x,y)
 * and set (x,y) to be the new current position.
 *
 * \param  gfx   Graphics context to use.
 * \param  x, y  end coordinates.
 */
void GFX_LineTo (Graphics *gfx, int x, int y)
{
  GFX_Line(gfx, gfx->x, gfx->y, x, y);
  gfx->x = x;
  gfx->y = y;
}


/**
 * Draws a rectangle with upper left corner at (x,y)
 * and with width w and height h.
 *
 * \param  gfx   Graphics context to use.
 * \param  x, y  coordinates for top-left corner
 * \param  w, h  width and height
 *
 */
void GFX_Rectangle(Graphics *gfx, int x, int y, int w, int h)
{
  if (w<=0 || h<=0)
    return;
  GFX_HLine(gfx, x    , y    , w);
  GFX_HLine(gfx, x    , y+h-1, w);
  GFX_VLine(gfx, x    , y+1  , h-2);
  GFX_VLine(gfx, x+w-1, y+1  , h-2);
}


/**
 * Draw a circle.
 *
 * \param  gfx    Graphics context to use.
 * \param  xc,yc  coordinates of center point.
 * \param  r      radius (in pixels)
 */
void GFX_Circle(Graphics *gfx, int xc, int yc, int r)
{
  int x = 0;
  int y = r;
  int p = 3 - (r<<1);

  while (x <= y)  {
    BM_PutPixel(gfx->bitmap, xc+x, yc+y, gfx->color);
    BM_PutPixel(gfx->bitmap, xc+x, yc-y, gfx->color);
    BM_PutPixel(gfx->bitmap, xc-x, yc+y, gfx->color);
    BM_PutPixel(gfx->bitmap, xc-x, yc-y, gfx->color);
    BM_PutPixel(gfx->bitmap, xc+y, yc+x, gfx->color);
    BM_PutPixel(gfx->bitmap, xc+y, yc-x, gfx->color);
    BM_PutPixel(gfx->bitmap, xc-y, yc+x, gfx->color);
    BM_PutPixel(gfx->bitmap, xc-y, yc-x, gfx->color);
    if (p < 0)
      p += (x++ << 2) + 6;
    else
      p += ((x++ - y--)<<2) + 10;
  }
}


/**
 * Draw a filled rectangle.
 *
 * \param  gfx   Graphics context to use.
 * \param  x, y  coordinates for top-left corner
 * \param  w, h  width and height
 *
 * \bug  the fillpattern does not move correctly, when
 *       clipped at the left or top border.
 */
void GFX_FillRect(Graphics *gfx, int x, int y, int w, int h)
{
  if (x<0) {
    w -= -x;
    x  =  0;
  }
  if (x+w >= gfx->bitmap->width)
    w -= (x+w) - gfx->bitmap->width;
  if (w<=0)
    return;

  if (y<0) {
    h -= -y;
    y  =  0;
  }
  if (y+h >= gfx->bitmap->height)
    h -= (y+h) - gfx->bitmap->height;
  if (h<=0)
    return;

  assert(w <= _gfx_scratch->bmBitsSize);

  // copy pattern to scratch memory and blit
  //
  char *dst = _gfx_scratch->bmBits;
  char *src = gfx->fillStyle;
  asm {
    pushf
    push  cx
    push  si
    push  di
    push  ds
    push  es
    cld
    lds   si, dword ptr src
    les   di, dword ptr dst
    mov   cx, w
    shr   cx, 3
    inc   cx

fill_loop:
    movsw
    movsw
    movsw
    movsw
    sub   si, 8
    loop  fill_loop

    pop   es
    pop   ds
    pop   di
    pop   si
    pop   cx
    popf
  }

  _gfx_scratch->width  = 0; // always blit the same page..
  BM_FastBlit(gfx->bitmap, x, y, _gfx_scratch, 0, 0, w, h, gfx->blitMode);
}
