/*
 * NAME
 *   gdevretina.c
 *
 * DESCRIPTION
 *   GhostScript device-driver for the Macro-Systems
 *   RETINA graphics board. Currently this driver does not work
 *   with 16-Bit Color-Lookup-Tables
 *
 * LEGAL STATUS
 *   GNUware
 *
 * AUTHOR
 *   Andreas Heitmann
 *
 * EMAIL
 *   andreas@gotcha.swb.de
 *   ah@hrz.uni-kassel.de
 *   ah@rbg.informatik.th-darmstadt.de
 *
 */
#include <stdio.h>
#include <string.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <graphics/gfx.h>
#include <retina/retina.h>
#include <retina/clib/retina_protos.h>
#include "gs.h"
#include "gxdevice.h"
#include "gdevretina.h"
#include "gsmatrix.h"
#include <stdlib.h>

#define X_DPI 72
#define Y_DPI 72

#define RETINA_LIB_VERSION 3

#if RETINA_LIB_VERSION<4
#define FILLRECTHACK
#endif

#define COPYMONOHACK

#ifdef COPYMONOHACK
#define RETINA_COPY_MONO r_copy_mono
#else
#define RETINA_COPY_MONO r_fast_copy_mono
#endif

void TEST(char *x)
{
 char test[3];
 eprintf(x);
 eprintf("\n");
 fflush(stderr);
 fgets(test,2,stdin);
}

/* forward declarations */
dev_proc_open_device(r_open_device);
dev_proc_close_device(r_close_device);
dev_proc_map_rgb_color(r_map_rgb_color_8_16);
dev_proc_map_color_rgb(r_map_color_rgb_8_16);
dev_proc_map_rgb_color(r_map_rgb_color_24);
dev_proc_map_color_rgb(r_map_color_rgb_24);
dev_proc_fill_rectangle(r_fill_rectangle);
dev_proc_copy_mono(RETINA_COPY_MONO);
dev_proc_copy_color(r_copy_color);
dev_proc_draw_line(r_draw_line);
dev_proc_get_initial_matrix(r_get_initial_matrix);

#define retina_procs_24 { r_open_device, r_get_initial_matrix, gx_default_sync_output, gx_default_output_page, r_close_device, r_map_rgb_color_24, r_map_color_rgb_24, r_fill_rectangle, gx_default_tile_rectangle, RETINA_COPY_MONO, r_copy_color, r_draw_line, gx_default_get_bits, gx_default_get_props, gx_default_put_props }
#define retina_procs_16 { r_open_device, r_get_initial_matrix, gx_default_sync_output, gx_default_output_page, r_close_device, r_map_rgb_color_8_16, r_map_color_rgb_8_16, r_fill_rectangle, gx_default_tile_rectangle, RETINA_COPY_MONO, r_copy_color, r_draw_line, gx_default_get_bits, gx_default_get_props, gx_default_put_props }
#define retina_procs_8  { r_open_device, r_get_initial_matrix, gx_default_sync_output, gx_default_output_page, r_close_device, r_map_rgb_color_8_16, r_map_color_rgb_8_16 , r_fill_rectangle, gx_default_tile_rectangle, RETINA_COPY_MONO, r_copy_color, r_draw_line, gx_default_get_bits, gx_default_get_props, gx_default_put_props }

private gx_device_procs gs_retina_procs_8  = retina_procs_8;
private gx_device_procs gs_retina_procs_16 = retina_procs_16;
private gx_device_procs gs_retina_procs_24 = retina_procs_24;

#define gs_retina_skel(procs,name,colors,mode) {sizeof(retina_device),&procs,name,0,0,XPPI,YPPI,no_margins,colors,FALSE,NULL,mode,0,NULL,NULL,NULL}

retina_device gs_retina8_device  = gs_retina_skel(gs_retina_procs_8, "retina8" ,dci_color( 8, 31,  4),MID_DEFAULT_08);
retina_device gs_retina16_device = gs_retina_skel(gs_retina_procs_16,"retina16",dci_color(16,255,256),MID_DEFAULT_16);
retina_device gs_retina24_device = gs_retina_skel(gs_retina_procs_24,"retina24",dci_color(24,255,256),MID_DEFAULT_24);

/** procedures **/

/* tests if the pixel coordinates have valid values */
static boolean
retina_is_valid_area(gx_device * dev, int x, int y, int width, int height)
{
 return x >= 0 && y >= 0 && x + width <= rdev->width && y + height <= rdev->height;
}

/* Amiga library bases */
struct RetinaBase *RetinaBase = NULL;
struct GfxBase *GfxBase = NULL;


void
gs_retina_setrgb(gx_device *dev,byte r,byte g,byte b)
{
 rdev->palette[rdev->ncols][0]=r; 
 rdev->palette[rdev->ncols][1]=g;
 rdev->palette[rdev->ncols][2]=b;
 rdev->ncols++; 
}

 /*
  * preallocate colors for video-modes with color-lookup tables
  */
int
preallocate_colors(gx_device *dev)
{
 int res=0;

 switch (rdev->screenmode) {
  case MID_DEFAULT_08:
  case MID_DEFAULT_16:
  rdev->palette =(byte (*)[3])calloc(1 << dev->color_info.depth, 3*sizeof(byte));
  if (NULL != rdev->palette) {
   gx_color_index color;
   byte r, g, b;

   /* reset index counter */
   rdev->ncols = 0;

   /* allocate black & white as first 2 colors */
   gs_retina_setrgb(dev,0, 0, 0);
   gs_retina_setrgb(dev,255, 255, 255);

   /* allocate evenly space color cube */
   while (rdev->ncols < RGB_COLS) {
    color = rdev->ncols - 1;
    r = ((color & RED_MASK) >> (GREEN_BITS + BLUE_BITS))
      * ((1 << dev->color_info.depth) - 1) / (RED_COLS - 1);
    g = ((color&GREEN_MASK) >> BLUE_BITS)
      * ((1 << dev->color_info.depth) - 1) / (GREEN_COLS - 1);
    b = (color&BLUE_MASK)
      * ((1 << dev->color_info.depth) - 1) / (RED_COLS - 1);
    /* Retina_SetPalette(rdev->rs,rdev->ncols,r,g,b); */
    gs_retina_setrgb(dev,r,g,b);
   }

   /* load the color map */
   Retina_LoadPalette(rdev->rs, 0, rdev->ncols, (UBYTE *) rdev->palette);
  }
  else {
   eprintf("gs retina: no memory for colormap\n");
   res = -1;
  }
  break;
  default:
 }
 return res;
}

int
open_screen(gx_device *dev)
{
 int res=0;

 if(NULL!=(GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0))) {
  if(NULL!=(RetinaBase=(struct RetinaBase *)OpenLibrary
     ("retina.library",RETINA_LIB_VERSION))) {
   if (NULL !=(rdev->rs = Retina_OpenScreen(
    rdev->width  == 0 ? RSCR_MAXWIDTH  : rdev->width,
    rdev->height == 0 ? RSCR_MAXHEIGHT : rdev->height,
    rdev->screenmode,
    RSFF_AUTOADJUST | RSFF_DONTCLEARONOPEN | RSFF_DONTCLEARONCLOSE,
    NULL))) {
    Retina_ClearScreen(rdev->rs);
    /*
     * real screensize may have changed due to memory lack, so correct the
     * variables
     */
    rdev->width = rdev->rs->_rs_Width;
    rdev->height = rdev->rs->_rs_Height;
   }
   else {
    eprintf("gs retina: can't open retina screen.\n");
    res = -1;
   }
  }
  else {
   res = -1;
   eprintf1("gs retina: can't open version %d of the retina.library.\n",
	    RETINA_LIB_VERSION);
  }
 }
 else {
  res = -1;
  eprintf("gs retina: can't open graphics library.\n");
 }
 return res;
}

void
setup_rdev(gx_device *dev)
{
 rdev->wz_scale_x = (float)rdev->width / PRE_WIDTH;
 rdev->wz_scale_y = (float)rdev->height / PRE_HEIGHT;
 rdev->is_open = TRUE;
}

int
r_open_device(gx_device *dev)
{
 int res=0;
 char buf[3];

 rdev->is_open=FALSE;
 rdev->rs=NULL;
 if(0==(res=open_screen(dev))) {
  preallocate_colors(dev);
  setup_rdev(dev);
 }
 return res;
}

/*
 * closes retina device
 *  - close screen
 *  - close library
 */
int
r_close_device(gx_device * dev)
{
 /*
  * free color palette, if allocated
  */
 switch (rdev->screenmode) {
  case MID_DEFAULT_08:
  case MID_DEFAULT_16:
   if (rdev->palette != NULL) free(rdev->palette);
   break;
  case MID_DEFAULT_24:
   break;
 }

 /*
  * close screen, if opened
  */
 if (rdev->rs != NULL) {
  Retina_CloseScreen(rdev->rs);
  rdev->is_open = FALSE;
 }

 /*
  * close libraries
  */
 if (RetinaBase != NULL) CloseLibrary((struct Library *)RetinaBase);
 if (GfxBase != NULL)    CloseLibrary((struct Library *)GfxBase);

 /*
  * exit without error
  */
 return 0;
}

/*
 * modified sun mapping routines
 */
gx_color_index
r_map_rgb_color_24(gx_device * dev, gx_color_value r, gx_color_value g, gx_color_value b)
{
 return
  (gx_color_value_to_byte(r)<<16)+
  (gx_color_value_to_byte(g)<<8)+
  gx_color_value_to_byte(b);
}

gx_color_index
r_map_rgb_color_8_16(gx_device * dev, gx_color_value r, gx_color_value g, gx_color_value b)
{
 gx_color_index ret = gx_no_color_index;
 byte red_val, green_val, blue_val;
 gx_color_index i;
 static int warn = 1;

 /*
  * Determine the RGB values at display resolution we ideally would want
  * this color to be mapped into.
  */
 red_val  =gx_color_value_to_byte(r);
 green_val=gx_color_value_to_byte(g);
 blue_val =gx_color_value_to_byte(b);

 /*
  * Look for an exact match among the colors already allocated. This
  * includes the pre-allocated default color cube.
  */
 for (i = 0; i < rdev->ncols; i++) {
  if (rdev->palette[i][0] == red_val &&
      rdev->palette[i][1] == green_val &&
      rdev->palette[i][2] == blue_val) {
   ret = i;
  }
 }
 if (ret == gx_no_color_index) {
  if (rdev->ncols == (1 << dev->color_info.depth)) {
   if (warn) {
    eprintf("gs retina: last spare color map entry allocated\n");
    warn = 0;
   }
  }
  else {
   Retina_SetPalette(rdev->rs, i, red_val, green_val, blue_val);
   gs_retina_setrgb(dev,red_val, green_val, blue_val);
   ret = i;
  }				/* else alloc new color */
 }				/* end if no spare colors */
 return ret;
}

/*
 * map retina color index to ghostscript rgb-value
 */
int
r_map_color_rgb_24(gx_device * dev, gx_color_index color, ushort prgb[3])
{
 prgb[0]=gx_color_value_from_byte((color>>16)&0xff);
 prgb[1]=gx_color_value_from_byte((color>> 8)&0xff);
 prgb[2]=gx_color_value_from_byte( color     &0xff);
 return 0;
}

int
r_map_color_rgb_8_16(gx_device * dev, gx_color_index color, ushort prgb[3])
{
 prgb[0]=gx_color_value_from_byte(rdev->palette[color][0]);
 prgb[1]=gx_color_value_from_byte(rdev->palette[color][1]);
 prgb[2]=gx_color_value_from_byte(rdev->palette[color][2]);
 return 0;
}

/*
 * fill rectangle with color
 */
int 
r_fill_rectangle(gx_device * dev, int x, int y, \
  int width, int height, gx_color_index color)
{
 int res = 0;

 if (retina_is_valid_area(dev, x, y, width, height)) {
#ifdef FILLRECTHACK
  switch (rdev->screenmode) {
   case MID_DEFAULT_24:
#endif
    Retina_SetDrMd(rdev->rs, RDM_JAM1);
    Retina_SetAPen(rdev->rs, color);
    Retina_RectFill(rdev->rs, x, y, x + width, y + height);
#ifdef FILLRECTHACK
    break;
    /*
     * There is a bug in the RectFill routine of retina.library version 2.3
     * in the 8 & 16 Bit color-mode so i can't use it.
     *
     * This bug has been fixed in Version 3.8 of retina.library
     */
   case MID_DEFAULT_08:
   case MID_DEFAULT_16:
    {
     int xx;

     Retina_SetDrMd(rdev->rs, RDM_JAM1);
     Retina_SetAPen(rdev->rs, color);
     for (xx = x + width - 1; xx >= x; xx--)
      Retina_Line(rdev->rs, xx, y, xx, y + height - 1);
     break;
    }
  }
#endif
 }
 else res = -1;
 return res;
}

/*
 * draw line
 */
int 
r_draw_line(gx_device * dev, int x0, int y0, int x1, int y1, \
  gx_color_index color)
{
 int h;

 if (!(y1 == y0 && x1 == x0)) {
  Retina_SetDrMd(rdev->rs, RDM_JAM1);
  Retina_SetAPen(rdev->rs, color);
  Retina_Line(rdev->rs, x0, y0, x1, y1);
 }
 return 0;
}

#ifdef COPYMONOHACK
int r_copy_mono
(gx_device *dev,\
 const unsigned char *data, int data_x, int raster, gx_bitmap_id id,\
 int x, int y, int width, int height,\
 gx_color_index color0, gx_color_index color1)
{
 boolean draw = TRUE;
 int res = 0;
 unsigned char *dp;      /* pointer to first byte of each line */
 static gx_bitmap_id old_id = gx_no_bitmap_id;
 unsigned char bit;      /* bitmask */
 long byte;              /* byte number in scanline */
 gx_color_index color;   /* actual color: color_c */
 long ix, iy;            /* coordinates in data space */
 int c;                  /* color index: 0..1 */

 /*
  * this is a hack and has to be replaced in future versions of gdevretina.c
  * 
  * i am trying to replace this by a bitmap-copy but not before the
  * next release of the retina-software
  */
 if (retina_is_valid_area(dev, x, y, width, height)) {
  for (c = 0; c <= 1; c++) {
   color = (c == 0) ? color0 : color1;
   if (color != gx_no_color_index) {
    dp = data;
    for (iy = 0; iy < height; iy++) {
     for (byte = 0, ix = 0; ix < width; byte++) {
      for (bit = 1 << 7; bit && ix < width; ix++, bit >>= 1) {
       if (dp[byte] & bit)
        Retina_SetPixel(rdev->rs, x + ix, y + iy, color);
      }
     }
     dp += raster;
    }
   }
  }
 }
 else res = -1;
 return res;
}
#endif

/*
 * The following function is a replacement for the slow r_copy_mono routine.
 * It still doesn't work because Retina_WriteBitMap doesn't support
 * writing WBMMODE_STD BitMaps to 24Bit Screens.
 * If the bug is fixed in newer versions of the retina.library i will
 * undefine COPYMONOHACK :)
 */
#ifndef COPYMONOHACK
int 
r_fast_copy_mono(gx_device * dev, unsigned char *data, int data_x, \
  int raster, gx_bitmap_id id, int x, int y, \
  int width, int height, gx_color_index color0, \
  gx_color_index color1)
{
 unsigned char pal[2 * 3];
 static struct BitMap mybm;
 int res = 0;

 if (retina_is_valid_area(dev, x, y, width, height)) {
  InitBitMap(&mybm, 1, width, height);
#ifdef DEBUG
  printf("bm.bbr=%d\n", mybm.BytesPerRow);
  printf("bm.row=%d\n", mybm.Rows);
  printf("bm.flg=%d\n", mybm.Flags);
  printf("bm.dpt=%d\n", mybm.Depth);
  printf("x=%d   y=%d   w=%d   h=%d raster=%d\n", x, y, width, height, raster);
#endif
  /*
   * correct the number of bytes per row, because gfx has it's own way to
   * compute the number of bytes from the number of pixels in each line.
   */
  mybm.BytesPerRow = raster;
  mybm.Planes[0] = (PLANEPTR) data;
#ifdef DEBUG
  {
   int i, j;

   for (j = 0; j < height; j++) {
    for (i = 0; i < raster; i++) {
     printf("%02x ", *(mybm.Planes[0] + i + j * raster));
    }
    puts("");
   }
  }
#endif
  pal[0] = ((unsigned char *)&color0)[1];
  pal[1] = ((unsigned char *)&color0)[2];
  pal[2] = ((unsigned char *)&color0)[3];
  pal[3] = ((unsigned char *)&color1)[1];
  pal[4] = ((unsigned char *)&color1)[2];
  pal[5] = ((unsigned char *)&color1)[3];
  Retina_WriteBitMap(rdev->rs, &mybm, pal, x, y, width, WBMMODE_STD);
 }
 else
  res = -1;
 return res;
}
#endif

int
r_copy_color(
 gx_device *dev,\
 const unsigned char *data, int data_x, int raster, gx_bitmap_id id,\
 int x, int y, int width, int height)
{
 int res = 0;

 if (retina_is_valid_area(dev, x, y, width, height)) {
  switch (rdev->screenmode) {
   case MID_DEFAULT_24:
    Retina_WriteRect(data, 0, 0, raster, RECTMODE_RGB, rdev->rs, x, y, width,
      height, NULL);
    break;
   case MID_DEFAULT_08:
    Retina_WriteRect(data, 0, 0, raster, RECTMODE_256, rdev->rs, x, y, width,
      height, NULL);
    break;
  }
 }
 else
  res = -1;
 return res;
}

void 
r_get_initial_matrix(gx_device * dev, gs_matrix * m)
{
 /* scale the user coordinates with SCALE */
 m->xy = m->yx = m->tx = 0;
 m->xx = rdev->x_pixels_per_inch * SCALE * rdev->wz_scale_x /X_DPI;
 m->yy = -rdev->y_pixels_per_inch * SCALE * rdev->wz_scale_y/Y_DPI;
 m->ty = rdev->height;
}
