/*  lcdgraph.cpp
    Graphic primitives driver for LCD displays

    10.08.2001: tk, do not use these function for anything serious.
                They're neither optimized nor overly bug free ;) Take
                them as a kind of reference design, and optimize them
                before use.

                I just measured some functions with the ZTimer. They're
                *really* slow. A line from (0,0) to (121,31) takes
                over 4ms, while LCD_Update() only takes 0.4ms. Talk
                about slow.. that's 32us per pixel vs. 10 pixels
                per us here :)

    30.07.2001: tk, initial implementation.

    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 <stdarg.h>
#include <assert.h>
#include <malloc.h>
#include <string.h>
#include <io.h>

#include "gfxlcd.h"
#include "sed1520.h"


static const char bitMasks[9] = {
  0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff
};


static char fillPatterns[13][8] = {
  { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },   // Empty Fill
  { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },   // Solid Fill
  { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 },   // Line Fill
  { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 },   // Lt Slash Fill
  { 0x99, 0xcc, 0x66, 0x33, 0x99, 0xcc, 0x66, 0x33 },   // Slash Fill
  { 0x33, 0x66, 0xcc, 0x99, 0x33, 0x66, 0xcc, 0x99 },   // Backslash Fill
  { 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88 },   // Lt Bkslash Fill
  { 0xff, 0x55, 0xff, 0x55, 0xff, 0x55, 0xff, 0x55 },   // Hatch Fill
  { 0x55, 0x22, 0x55, 0x88, 0x55, 0x22, 0x55, 0x88 },   // XHatch Fill
  { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa },   // Interleave Fill
  { 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44 },   // Wide Dot Fill
  { 0x11, 0x44, 0x88, 0x22, 0x11, 0x44, 0x88, 0x22 },   // Close Dot Fill
  { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }    // User defined
};

// Current graphic cursor position
static int  cursorX = 0;
static int  cursorY = 0;

// Clipping Region
static int  clipX1 = 0;
static int  clipY1 = 0;
static int  clipX2 = 0;
static int  clipY2 = 0;

// Display Resolution
static int  maxX = 0;
static int  maxY = 0;

// Current Colors
static int  backColor = 0;
static int  drawColor = 1;
static int  fillColor = 1;
static char fillPattern = SOLID_FILL;

// Current Font
static const BitmapFont *currentFont;


// Screen buffer
static char *screen;


/**
 * Checks if a point lies inside the clipping window. This
 * function returns an outcode, as used in the Cohen-Sutherland
 * algorithm for line clipping.
 *
 * \param   x x-coordinate
 * \param   y y-coordinate
 * \returns combination of 1: left, 2: right, 4: top, 8: bottom
 */
static inline int clipcode(int &x, int &y)
{
  int ret = 0;
  if (x<clipX1)  ret = 1;
  if (x>clipX2)  ret = 2;
  if (y<clipY1)  ret = 4;
  if (y>clipY2)  ret = 8;
  return ret;
}


static inline bool fastclip(int &x, int &y)
{
  if (x<clipX1) return true;
  if (x>clipX2) return true;
  if (y<clipY1) return true;
  if (y>clipY2) return true;
  return false;
}


/**
 * Swap two integers a and b.
 */
static inline void swap(int &a, int &b)
{
  int t=a;
  a=b; b=t;
}


/**
 * graphdefaults resets all graphics settings to their defaults:
 *   sets the viewport to the entire screen.
 *   moves the current position to (0,0).
 *   sets the default background color, and drawing color.
 *   sets the default text font and justification.
 * \todo  set default font
 */
void graphdefaults(void)
{
  maxX   = LCD_WIDTH-1;
  maxY   = LCD_HEIGHT-1;
  clipX1 = 0;
  clipY1 = 0;
  clipX2 = maxX;
  clipY2 = maxY;

  backColor = 0;
  drawColor = 1;
  fillColor = 1;

  fillPattern = SOLID_FILL;
}


/**
 * initgraph allocates memory for the graphics system, and initializes
 * a display mode.
 */
void initgraph()
{
  LCD_Init();
  screen = LCD_GetScreenBuffer();

  graphdefaults();
  cleardevice();
}


/**
 * closegraph deallocates all memory allocated by the graphics
 * system.
 */
void closegraph()
{
  if (screen)
    screen = NULL;
  LCD_Done();
}


/**
 * cleardevice erases (that is, fills with the current background color)
 * the entire graphics screen and moves the CP (current position) to
 * home (0,0).
 */
void cleardevice()
{
  cursorX = 0;
  cursorY = 0;
  memset( screen, backColor ? 0xff : 0x00, (maxX+1)*(maxY+1)/8 );
}


/**
 * clearviewport erases the viewport and moves the CP (current position)
 * to home (0,0), relative to the viewport.
 */
void clearviewport()
{
  cursorX = clipX1;
  cursorY = clipY1;
  bar(clipX1, clipY1, clipX2, clipY2);
}


/**
 * setbkcolor sets the background to the color specified by color.
 */
void setbkcolor(char color)
{
  if (color < 0)  color = 0;
  if (color > 1)  color = 1;
  backColor = color;
}


/**
 * setcolor sets the current drawing color to color for lines
 * and rectangles.
 */
void setcolor(char color)
{
  if (color < 0)  color = 0;
  if (color > 1)  color = 1;
  drawColor = color;
}


/**
 * setfillpattern is like setfillstyle, except that you use it to
 * set a user-defined 8x8 pattern rather than a predefined pattern.
 * upattern is a pointer to a sequence of 8 bytes, with each byte
 * corresponding to 8 pixels in the pattern. Whenever a bit in a
 * pattern byte is set to 1, the corresponding pixel is plotted.
 */
void setfillpattern(char *upattern, char color) {
  if (color <  0) color = 0;
  if (color >  1) color = 1;
  fillColor   = color;
  fillPattern = USER_FILL;

  memcpy(&fillPatterns[12], upattern, 8);
}


/**
 * getfillpattern copies the user-defined fill pattern, as set by
 * setfillpattern, into the 8-byte area pointed to by pattern.
 * pattern is a pointer to a sequence of 8 bytes, with each byte
 * corresponding to 8 pixels in the pattern. Whenever a bit in a
 * pattern byte is set to 1, the corresponding pixel will be
 * plotted.
 */
void getfillpattern(char *pattern) {
  memcpy(pattern, &fillPatterns[12], 8);
}


/**
 * setfillstyle sets the current fill pattern and fill color. To
 * set a user-defined fill pattern, do not give a pattern of 12
 * (USER_FILL) to setfillstyle; instead, call setfillpattern().
 */
void setfillstyle(int pattern, char color) {
  if (pattern <  0) pattern =  0;
  if (pattern > 11) pattern = 11;
  if (color <  0) color = 0;
  if (color >  1) color = 1;

  fillPattern = pattern;
  fillColor   = color;
}

/**
 * getfillsettings fills in the fillsettingstype structure pointed
 * to by fillinfo with information about the current fill pattern
 * and fill color.
 */
void getfillsettings(fillsettingstype *fillinfo) {
  fillinfo->pattern = fillPattern;
  fillinfo->color   = fillColor;
}


/**
 * getbkcolor returns the current background color.
 */
int getbkcolor()
{
  return backColor;
}


/**
 * getcolor returns the current drawing color. The drawing color is the
 * value to which pixels are set when lines and so on are drawn.
 */
int getcolor()
{
  return drawColor;
}


/**
 * getmaxx returns the maximum (screen-relative) x value for the current
 * graphics driver and mode. For example, on a CGA in 320*200 mode,
 * getmaxx returns 319. getmaxx is invaluable for centering, determining
 * the boundaries of a region onscreen, and so on.
 */
int getmaxx()
{
  return maxX;
}


/**
 * getmaxy returns the maximum (screen-relative) y value for the current
 * graphics driver and mode. For example, on a CGA in 320*200 mode,
 * getmaxy returns 199. getmaxy is invaluable for centering, determining
 * the boundaries of a region onscreen, and so on.
 */
int getmaxy()
{
  return maxY;
}


/**
 * getx finds the current graphics position's x-coordinate. The value is
 * viewport-relative.
 */
int getx(void)
{
  return cursorX;
}


/**
 * gety finds the current graphics position's y-coordinate. The value is
 * viewport-relative.
 */
int gety(void)
{
  return cursorY;
}


/**
 * moverel moves the current position (CP) dx pixels in the x direction
 * and dy pixels in the y direction.
 */
void moverel(int dx, int dy)
{
  cursorX += dx;
  cursorY += dy;
}


/**
 * moveto moves the current position (CP) to viewport position (x,y).
 */
void moveto(int x, int y)
{
  cursorX = x;
  cursorY = y;
}


/**
 * getpixel gets the color of the pixel located at (x,y).
 */
inline char getpixel(int x, int y)
{
  if (fastclip(x,y))
    return backColor;

  return screen[x+(y>>3)*(maxX+1)] & (1 << (y&7)) ? 1 : 0;
}


/**
 * putpixel plots a point in the color defined by color at (x,y).
 */
void putpixel(int x, int y, char color)
{
  if (fastclip(x,y))
    return;
  if (color)
    screen[x+(y>>3)*(maxX+1)] |=   1<<(y&7);
  else
    screen[x+(y>>3)*(maxX+1)] &= ~(1<<(y&7));
}


/**
 * bar draws a filled-in, rectangular, two-dimensional bar.
 * The bar is filled using the current fill pattern and fill color.
 * bar does not outline the bar; to draw an outlined two-dimensional
 * bar, use bar3d with depth equal to 0. The upper left and lower
 * right corners of the rectangle are given by (left, top) and (right,
 * bottom), respectively. The coordinates refer to pixels.
 *
 */
void bar(int left, int top, int right, int bottom)
{

  if (left>right)  swap(left, right);
  if (top >bottom) swap(top,  bottom);
  if      (left   < clipX1) left   = clipX1;
  else if (left   > clipX2) left   = clipX2;
  if      (right  < clipX1) right  = clipX1;
  else if (right  > clipX2) right  = clipX2;
  if      (top    < clipY1) top    = clipY1;
  else if (top    > clipY2) top    = clipY2;
  if      (bottom < clipY1) bottom = clipY1;
  else if (bottom > clipY2) bottom = clipY2;

  int   w = right-left + 1;
  int   h = bottom-top + 1;
  char *d = &screen[left+(top>>3)*(maxX+1)];
  char *pattern = fillPatterns[fillPattern];

  while (h > 0) {
    // calculate bitmask for given height
    char mask = bitMasks[min(h, 8)] << (top & 7);

    // draw strip
    if (fillColor)
      for (int x=0; x<w; x++)
        *d++ |=   mask & pattern[x&7];
    else
      for (int x=0; x<w; x++)
        *d++ &= ~(mask & pattern[x&7]);

    // substract drawn lines, discard the lowest three
    // bits of top (to avoid bitmask-shifting for the
    // next strip), and advance to the next strip.
    h   -= 8-(top & 7);
    top &= ~7;
    d   += (maxX+1)-w;
  }
}


/**
 * line draws a line in the current color, using the current line style
 * and thickness between the two points specified, (x1,y1) and (x2,y2),
 * without updating the current position (CP).
 *
 */
void line(int x1, int y1, int x2, int y2)
{
  int dx = abs(x2-x1);
  int dy = abs(y2-y1);

  if (dy == 0) {
    // horizontal line
    if (x1>x2)  swap(x1, x2);
    for (int x=x1; x<=x2; x++)
      putpixel(x, y1, drawColor);
  } else if (dx == 0) {
    // vertical line
    if (y1>y2)  swap(y1, y2);
    for (int y=y1; y<=y2; y++)
      putpixel(x1, y, drawColor);
  } else {
    // bresenham line
    int c1, c2, p, step;

    if (dx >= dy) {
      // step through x axis
      if (x1 > x2) {
        swap(x1, x2);
        swap(y1, y2);
      }

      c1 = dy << 1;
      c2 = (dy-dx) << 1;
      p  = (dy<<1) - dx;
      step = (y1 > y2) ? -1 : 1;

      putpixel(x1, y1, drawColor);
      while (x1 < x2) {
        if (p<0)
          p += c1;
        else {
          y1 += step;
          p  += c2;
        }
        putpixel(++x1, y1, drawColor);
      }

    } else {
      // step through y axis
      if (y1 > y2) {
        swap(x1, x2);
        swap(y1, y2);
      }
      c1 = dx << 1;
      c2 = (dx-dy) << 1;
      p  = (dx<<1) - dy;
      step = (x1>x2) ? -1 : 1;

      putpixel(x1, y1, drawColor);
      while (y1 < y2) {
        if (p<0)
          p += c1;
        else {
          x1 += step;
          p  += c2;
        }
        putpixel(x1, ++y1, drawColor);
      }
    }

  }
}


/**
 * circle draws a circle in the current drawing color with its center at (x,y)
 * and the radius given by radius. The linestyle parameter does not affect arcs,
 * circles, ellipses, or pie slices. Only the thickness parameter is used.
 * If your circles are not perfectly round, adjust the aspect ratio.
 *
 * \todo implement ellipse function, and use correct aspect ratio for circles
 */
void circle(int xc, int yc, int r)
{
  int x = 0;
  int y = r;
  int p = 3 - (r<<1);

  while (x <= y)  {
    putpixel(xc + x, yc + y, drawColor);
    putpixel(xc - x, yc + y, drawColor);
    putpixel(xc + x, yc - y, drawColor);
    putpixel(xc - x, yc - y, drawColor);
    putpixel(xc + y, yc + x, drawColor);
    putpixel(xc - y, yc + x, drawColor);
    putpixel(xc + y, yc - x, drawColor);
    putpixel(xc - y, yc - x, drawColor);
    if (p < 0)
      p += (x++ << 2) + 6;
    else
      p += ((x++ - y--)<<2) + 10;
  }
}


/**
 * linerel draws a line from the CP (current position) to a point that is
 * a relative distance (dx,dy) from the CP. The CP is advanced by
 * (dx,dy).
 */
void linerel(int dx, int dy)
{
  line(cursorX, cursorY, cursorX+dx, cursorY+dy);
  cursorX += dx;
  cursorY += dy;
}


/**
 * lineto draws a line from the CP (current position) to (x,y), then
 * moves the CP to (x,y).
 */
void lineto(int x, int y)
{
  line(cursorX, cursorY, x, y);
  cursorX = x;
  cursorY = y;
}


/**
 * rectangle draws a rectangle in the current line style, thickness, and
 * drawing color. (left,top) is the upper left corner of the rectangle,
 * and (right,bottom) is its lower right corner.
 */
void rectangle(int left, int top, int right, int bottom)
{
  line( left,   top,     right,  top    );
  line( left,   bottom,  right,  bottom );
  line( left,   top,     left,   bottom );
  line( right,  top,     right,  bottom );
}


/**
 * Imagesize determines the (maximum) size of the memory area required
 * to store a bit image. imagesize may report a larger size than is
 * actually needed, because the coordinates are not clipped to the
 * current clipping window.
 *
 * \todo Someone should check a normal BGI driver, on how to
 *       behave correctly in this case.
 */
unsigned imagesize(int left, int top, int right, int bottom)
{
  int width  = abs(right-left) + 1;
  int height = abs(bottom-top) + 1;

  return width*((height+7)/8)+4;
}

       
/**
 * getimage copies an image from the screen to memory. left, top, right,
 * and bottom define the screen area from which the rectangle is copied.
 * bitmap points to the area in memory where the bit image is to be stored.
 * The first two words of this area are used for the width and height of
 * the rectangle; the remainder holds the image itself.
 */
void getimage(int left, int top, int right, int bottom, Bitmap *bitmap)
{
  if (left>right)  swap(left, right);
  if (top >bottom) swap(top,  bottom);

  if      (left   < clipX1) left   = clipX1;
  else if (left   > clipX2) left   = clipX2;
  if      (right  < clipX1) right  = clipX1;
  else if (right  > clipX2) right  = clipX2;
  if      (top    < clipY1) top    = clipY1;
  else if (top    > clipY2) top    = clipY2;
  if      (bottom < clipY1) bottom = clipY1;
  else if (bottom > clipY2) bottom = clipY2;

  bitmap->width  = right-left + 1;
  bitmap->height = bottom-top + 1;

  int h = bitmap->height;
  int w = bitmap->width;

  char *s = &screen[left+(top>>3)*(maxX+1)];
  char *d = bitmap->bmBits;

  while (h > 0) {
    // copy strip
    unsigned t;
    for (int x=0; x<w; x++) {
       t   = (*s+(s[x+maxX+1]<<8)) >> (top & 7);
      *d++ = t;
       s++;
    }
    // advance source pointer, and decrement height
    s += (maxX+1)-w;
    h -= 8;
  }
}


/**
 * putimage puts the bit image previously saved with getimage back onto
 * the screen, with the upper left corner of the image placed at
 * (left,top). bitmap points to the area in memory where the source image
 * is stored. The op parameter to putimage specifies a combination
 * operator that controls how the color for each destination pixel
 * onscreen is computed, based on the pixel already onscreen and the
 * corresponding source pixel in memory. The enumeration putimage_ops, as
 * defined in graphics.h, gives names to these operators.
 * <pre>
 * Name       Value   Description
 *
 * COPY_PUT   0       Copy
 * XOR_PUT    1       Exclusive or
 * OR_PUT     2       Inclusive or
 * AND_PUT    3       And
 * NOT_PUT    4       Copy the inverse of the source
 * </pre>
 * In other words, COPY_PUT copies the source bitmap image onto the
 * screen, XOR_PUT XORs the source image with the image already onscreen,
 * OR_PUT ORs the source image with that onscreen, and so on.
 */
void putimage(int left, int top, Bitmap *bitmap, int op)
{

  int   w = bitmap->width;
  int   h = bitmap->height;
  char *s = bitmap->bmBits;

  int x,y;

  for (x=0; x<w; x++) {
    switch (op) {
      case COPY_PUT:
        for (y=0; y<h; y++) {
          if (s[x+(y>>3)*w] & (1<<(y&7)))
            putpixel(x+left, y+top,1);
          else
            putpixel(x+left, y+top,0);
        }
        break;

      case XOR_PUT:
        for (y=0; y<h; y++) {
          if (s[x+(y>>3)*w] & (1<<(y&7)))
            putpixel(x+left, y+top, 1 - getpixel(x+left, y+top) );
        }
        break;

      case OR_PUT:
        for (y=0; y<h; y++) {
          if (s[x+(y>>3)*w] & (1<<(y&7)))
            putpixel(x+left, y+top,1);
        }
        break;

      case AND_PUT:
        for (y=0; y<h; y++) {
          if (s[x+(y>>3)*w] & (1<<(y&7)))
            putpixel(x+left, y+top, getpixel(x+left, y+top));
          else
            putpixel(x+left, y+top,0);
        }
        break;

      case NOT_PUT:
        for (y=0; y<h; y++) {
          if (s[x+(y>>3)*w] & (1<<(y&7)))
            putpixel(x+left, y+top,0);
          else
            putpixel(x+left, y+top,1);
        }
        break;
    }
  }
}

// int registerbgifont(Font *font);
// setviewport(int left, int top, int right, int bottom, int clip);


/** 
 * Load a bitmap font from disk. Bitmap fonts can be generated
 * with the bmpfont utility, provided in the tools/bmpfont directory.
 *
 * \param   filename  Bitmap font to load
 * \return  pointer to loaded BitmapFont structure, or NULL
 *          if the file couldn't be opened or out of memory.
 *
 * \todo
 *   clean up this mess, and improve the on-disk font structure,
 *   we should probably just throw the BGI compatibility away, and
 *   do something better. freefont() for example. Not letting the
 *   user free a font by a simple free() has some advantages, because
 *   a font needn't be a continuous memory block anymore.<br>
 *   perhaps we should even prefix all the graphics functions with
 *   GFX_ .. this will add some consistency to the source. I'm not
 *   sure that the get/putimage function are really good as they are
 *   getimage should most probably malloc the memory itself, and
 *   return a ready-made bitmap. we should also have a freeBitmap()
 *   function.
 *
 */
BitmapFont *loadfont(char *filename)
{
  BitmapFont *font;
  FILE *file;

  printf("loading %s... ", filename);
  file = fopen(filename, "rb");
  if (!file) {
    perror("");
    return NULL;
  }

  font = (BitmapFont*)malloc(sizeof(BitmapFont)+filelength(fileno(file)));
  if (!font) {
    printf("out of memory.\n");
    fclose(file);
    return NULL;
  }

  memset( font, 0, sizeof(BitmapFont) );

  char c;
  fread(&c, 1, 1, file);
  font->numchars = c;
  fread(&c, 1, 1, file);
  font->offset = c;
  font->fontsize = filelength(fileno(file)) - 2;

  printf( "%d chars, %d offset, %d bytes\n",
          font->numchars, font->offset, font->fontsize);

  fread( &font->bmbits, 1, font->fontsize, file );

  int index = 0;
  for (int i=0; i< font->numchars; i++) {
    Bitmap *bm = (Bitmap*)&font->bmbits[index];
    font->bitmaps[i] = bm;
    index += imagesize(0, 0, bm->width-1, bm->height-1);
  }
  fclose(file);
  return font;
}


void freefont(BitmapFont *font)
{
  if (font)
    free(font);
}



void setfont(const BitmapFont *font)
{
  currentFont = font;
}


const BitmapFont *getfont()
{
  return currentFont;
}



/**
 * Calculate the width of a text string, using the current font.
 *
 * \param  textstring  pointer to text string
 * \return width of text in pixels
 */
int textwidth(const char *textstring)
{
  if (!currentFont)
    return 0;

  char c;
  int ret = 0;
  while (*textstring) {
    c  = *textstring++;
    c -= currentFont->offset;
    if (c >= 0  && c < currentFont->numchars) {
      Bitmap *bm = currentFont->bitmaps[c];
      ret += bm->width+1;
    }
  }

  return ret;
}


/**
 * Calculate the height of a text string, using the current font.
 *
 * \note we assume that all characters are of the same height, so
 *       textheight returns the height of the first character of the
 *       font. (change this, if you implement rotated printing!)
 *
 * \param  textstring  pointer to text string
 * \return height of text in pixels
 */
int textheight(const char *textstring)
{
  if (!currentFont)
    return 0;

  return currentFont->bitmaps[0]->height;
}


/**
 * Print a text string at the current cursor position.
 * The x-cursor position is moved to the end of the string,
 * the y position doesn't get modified, even if the right
 * edge of the screen is reached.
 *
 * \param  textstring  pointer to text string
 */
void outtext(const char *textstring)
{
  if (!currentFont)
    return;

  int  c;
  while (c = *textstring++) {
    c -= currentFont->offset;

    if (c >= 0  && c < currentFont->numchars) {
      Bitmap *bm = currentFont->bitmaps[c];

      putimage(getx(), gety(), bm, XOR_PUT);
      moverel(bm->width+1, 0);
    }
  }
}


/**
 * Print a text string. The current cursor position doesn't
 * get modified by this function.
 *
 * \param x  x-position
 * \param y  y-position
 * \param textstring  pointer to text string
 */
void outtextxy(int x, int y, const char *textstring)
{
  int oldX = getx();
  int oldY = gety();

  moveto(x, y);
  outtext(textstring);
  moveto(oldX, oldY);
}


/**
 * Print a formatted text string. The cursor
 * position is advanced to the end of the string.
 *
 * \param   format  see printf documentation.
 * \return  number of characters printed.
 */
int gprintf(const char *format, ...)
{
  static char buf[1024];

  va_list arglist;
  va_start(arglist, format);

  int ret = vsprintf(buf, format, arglist);
  assert(ret < sizeof(buf));

  outtext(buf);
  va_end(arglist);
  return ret;
}



