/* Copyright (C) 1992 Aladdin Enterprises.  All rights reserved.
   Distributed by Free Software Foundation, Inc.

This file is part of Ghostscript.

Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing.  Refer
to the Ghostscript General Public License for full details.

Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License.  A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities.  It should be in a file named COPYING.  Among other
things, the copyright notice and this notice must be preserved on all
copies.  */

/* gdevhf.c */
/* HyperFrame+ devices for Ghostscript. */

#include <pc.h>
#include "dos_.h"
#include "gx.h"
#include "gserrors.h"
#include "gxdevice.h"
#include "gdev98hf.h"

struct gx_device_hf_s {
	gx_device_common;
    byte *fbuf;
};
typedef struct gx_device_hf_s gx_device_hf;

/* ------ The device descriptors ------ */

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 400
#define SCROLL_HEIGHT 16

/* The device descriptors themselves */
/* The device procedures */
private dev_proc_open_device(gdev_pc98hf_open);
private dev_proc_close_device(gdev_pc98hf_close);
private dev_proc_map_rgb_color(gdev_pc98hf_map_rgb_color);
private dev_proc_map_color_rgb(gdev_pc98hf_map_color_rgb);
private dev_proc_fill_rectangle(gdev_pc98hf_fill_rectangle);
private dev_proc_copy_mono(gdev_pc98hf_copy_mono);
private dev_proc_copy_color(gdev_pc98hf_copy_color);
private dev_proc_output_page(gdev_pc98hf_output_page);

private gx_device_procs hf_procs = {
  gdev_pc98hf_open,
  gx_default_get_initial_matrix,
  gx_default_sync_output,
  gdev_pc98hf_output_page,
  gdev_pc98hf_close,
  gdev_pc98hf_map_rgb_color,
  gdev_pc98hf_map_color_rgb,
  gdev_pc98hf_fill_rectangle,
  gx_default_tile_rectangle,
  gdev_pc98hf_copy_mono,
  gdev_pc98hf_copy_color,
  gx_default_draw_line,
  gx_default_get_bits,
  gx_default_get_props,
  gx_default_put_props,
};

#define WIDTH_10TH   83
#define HEIGHT_10TH 117
#define DEFAULT_DPI  48
gx_device_hf gs_pc98hf_device = {
	sizeof(gx_device_hf),
	&hf_procs,
	"pc98hf",
	WIDTH_10TH * DEFAULT_DPI / 10,
    HEIGHT_10TH * DEFAULT_DPI / 10,
	50, 50,
	no_margins,
	dci_color(24, 255, 256),
  0,
};

#define hfdev ((gx_device_hf *)dev)

/* Internal procedures */
private void gdev_pc98hf_graphic_enable(void);
private void gdev_pc98hf_graphic_disable(void);
private void gdev_pc98hf_init_register(void);
private void gdev_pc98hf_hyperframe_screen(void);
private void gdev_pc98hf_impose_screen(void);
private void gdev_pc98hf_copy_fb2hf(byte *,int ,int ,int ,int ,int );
private void gdev_pc98hf_clear_screen(void);

/* These functions are defined in gdevpc98.c */
extern void gdev_pc98_nopause(void);
extern int gdev_pc98_getkeycode(void);
extern void gdev_pc98_flushkey(void);

#ifdef SUPPORT_30BIOS
extern int gdev_pc98_30bios_normal_mode(void);
extern int gdev_pc98_30bios_restore_mode(void);
#endif

/* ------ Color mapping routines ------ */

/* Map an RGB color to a PPM color tuple. */
private gx_color_index
gdev_pc98hf_map_rgb_color(gx_device *dev, gx_color_value r,gx_color_value g, gx_color_value b)
{
	return ((r >> (gx_color_value_bits - 8)) << 16) +
	       ((g >> (gx_color_value_bits - 8)) << 8) +
	       (b >> (gx_color_value_bits - 8));
}

/* Map a PPM color tuple back to an RGB color. */
private int
gdev_pc98hf_map_color_rgb(gx_device *dev, gx_color_index color, gx_color_value prgb[3])
{
	prgb[0] = ((color >> 16) & 0xff) << (gx_color_value_bits - 8);
	prgb[1] = ((color >> 8) & 0xff) << (gx_color_value_bits - 8);
	prgb[2] = (color & 0xff) << (gx_color_value_bits - 8);
	return 0;
}

/* Copy a monochrome bitmap.  The colors are given explicitly. */
/* Color = gx_no_color_index means transparent (no effect on the image). */
private int
gdev_pc98hf_copy_mono(gx_device *dev,
  const byte *base, int sourcex, int raster, gx_bitmap_id id,
  int x, int y, int w, int h, gx_color_index czero, gx_color_index cone)
{	int xi, yi;
	int xend,yend;
	const byte *srow = base + (sourcex >> 3);
	uint imask = 0x80 >> (sourcex & 7);
  int rz = (czero >> 16) & 0xff, gz = (czero >> 8) &0xff, bz = czero & 0xff;
  int ro = (cone >> 16) & 0xff, go = (cone >> 8) &0xff, bo = cone & 0xff;
  int zero_effect = (czero != gx_no_color_index);
  int one_effect = (cone != gx_no_color_index);
  byte *fb = hfdev->fbuf + (dev->width * y + x ) * 3;
	int lsize = dev->width * 3;
  
  xend = x+w;
  yend = y+h;
	for ( yi = y; yi < yend; yi++, fb += lsize ) {
		const byte *sptr = srow;
		uint bits = *sptr;
		register uint mask = imask;
		register byte *fbtmp = fb;
		for (xi = x; xi < xend ; xi++) {
			if ( bits & mask ){
				if (one_effect) {
					*fbtmp++ = ro;
					*fbtmp++ = go;
					*fbtmp++ = bo;
				}
				else fbtmp += 3;
			}else{
				if (zero_effect) {
					*fbtmp++ = rz;
					*fbtmp++ = gz;
					*fbtmp++ = bz;
				}
				else fbtmp += 3;
			}
			if ( !(mask >>= 1) ) mask = 0x80, bits = *++sptr;
		}
		srow += raster;
	}
    if (xend > SCREEN_WIDTH) {
		xend = SCREEN_WIDTH;
    }
    if (yend > SCREEN_HEIGHT) {
		yend = SCREEN_HEIGHT;
	}
	gdev_pc98hf_copy_fb2hf(hfdev->fbuf,x,y,xend-x,yend-y,dev->width);
	return 0;
}

/* Copy a color pixelmap.  This is just like a bitmap, */
/* except that each pixel takes 24 bits instead of 1. */
private int
gdev_pc98hf_copy_color(gx_device *dev,
  const byte *base, int sourcex, int raster, gx_bitmap_id id,
  int x, int y, int w, int h)
{	int xi, yi;
    int xend,yend;
	int skip = raster * 3;
    byte *fb = hfdev->fbuf + (dev->width * y +x ) * 3;
	int lsize = dev->width * 3;
  
	if ( x < 0 || x + w > dev->width ) return -1;	/* skip w */
  base += sourcex * 3;
  
  xend = x + w;
  yend = y + h;
	for ( yi = y; yi < yend; yi++,fb += lsize ){
		memcpy(fb,base,w*3);
		base += skip;
	}
    if (xend > SCREEN_WIDTH) {
		xend = SCREEN_WIDTH;
    }
    if (yend > SCREEN_HEIGHT) {
		yend = SCREEN_HEIGHT;
	}
	gdev_pc98hf_copy_fb2hf(hfdev->fbuf,x,y,xend-x,yend-y,dev->width);
	return 0;
}

/* Fill a rectangle. */
private int
gdev_pc98hf_fill_rectangle(gx_device *dev, int x, int y, int w, int h,
  gx_color_index color)
{
	int xi, yi;
	int xend,yend;
  int r = (color >> 16) & 0xff, g = (color >> 8) &0xff, b = color& 0xff;
  byte *fb = hfdev->fbuf + (dev->width * y + x) * 3;
  int lsize = dev->width * 3;
  
	if ( x < 0 || x + w > dev->width ) return -1;	/* skip w */
  
  xend = w + x;
  yend = h + y;
	for ( yi = y; yi < yend; yi++ ,fb += lsize){
		register byte *fbtmp = fb;
		for ( xi = x; xi < xend; xi++) {
			*fbtmp++ = r;
			*fbtmp++ = g;
			*fbtmp++ = b;
		}
	}
    if (xend > SCREEN_WIDTH) {
		xend = SCREEN_WIDTH;
    }
    if (yend > SCREEN_HEIGHT) {
		yend = SCREEN_HEIGHT;
	}
	gdev_pc98hf_copy_fb2hf(hfdev->fbuf,x,y,xend-x,yend-y,dev->width);
	return 0;
}

private int
gdev_pc98hf_open(gx_device *dev)
{
  if (dev->width > SCREEN_WIDTH) {
    dev->x_pixels_per_inch *= (double)SCREEN_WIDTH/dev->width;
    dev->y_pixels_per_inch = dev->x_pixels_per_inch;
    dev->width = SCREEN_WIDTH;
  }
	
#ifdef SUPPORT_30BIOS
    gdev_pc98_30bios_normal_mode();
#endif
	gdev_pc98hf_graphic_enable();
    gdev_pc98hf_clear_screen();
	gdev_pc98hf_impose_screen();
	hf_impose_on();
	gdev_pc98_nopause();
    hfdev->fbuf = (byte *)gs_malloc(dev->width * dev->height *3,1,
					"gdev_pc98hf_fbuf");
	if (hfdev->fbuf == 0) return -1; /* cannot allocate frame buffer. */
    return 0;
}

private int
gdev_pc98hf_close(gx_device *dev)
{
	gdev_pc98hf_graphic_disable();
#ifdef SUPPORT_30BIOS
    gdev_pc98_30bios_restore_mode();
#endif
    gs_free((char *)hfdev->fbuf,dev->width * dev->height *3,1,
					"gdev_pc98hf_fbuf");
  return 0;
}

private int
gdev_pc98hf_output_page(gx_device *dev,int num_copies,int flush)
{
  int cur_x=0,cur_y=0;
  byte *fb = hfdev->fbuf;
  int imp = 0;
  int zoom = 0;
  
  gdev_pc98hf_hyperframe_screen();
  hf_scroll(0,0);
  gdev_pc98_flushkey();
  
  for ( ; ; ) {
    int c = gdev_pc98_getkeycode();
    if (c == 0x1c || c == 0x00) break;
    switch (c) {
    case 0x3d:
	  if (dev->height > SCREEN_HEIGHT) {
		int n = min(SCROLL_HEIGHT, dev->height-cur_y-SCREEN_HEIGHT);
        for ( ;n>0;n--) {
		  gdev_pc98hf_copy_fb2hf(fb,0,cur_y+SCREEN_HEIGHT,
			dev->width,1,dev->width);
          cur_y++;
		  hf_scroll(cur_x%SCREEN_WIDTH,cur_y%SCREEN_HEIGHT);
        }
	  }
      break;
    case 0x3a:
	  if (dev->height > SCREEN_HEIGHT) {
			int n = min(SCROLL_HEIGHT,cur_y);
		  for ( ;n>0;n--) {
	      	cur_y--;
		  	hf_scroll(cur_x%SCREEN_WIDTH,cur_y%SCREEN_HEIGHT);
		  	gdev_pc98hf_copy_fb2hf(fb,0,cur_y,dev->width,1,dev->width);
		  }
	  }
      break;
    case 0x3f:
      if (imp) { imp = 0; gdev_pc98hf_hyperframe_screen();}
      else { imp = 1; gdev_pc98hf_impose_screen();}
      break;
    case 0x36:
      if (zoom < 3) {
        zoom++;
        hf_zoom(zoom);
      }
      break;
    case 0x37:
      if (zoom > 0) {
        zoom--;
        hf_zoom(zoom);
      }
      break;
    }
  }
  gdev_pc98hf_impose_screen();
  hf_scroll(0,0);
  hf_impose_on();
  hf_zoom(0);
  gdev_pc98_flushkey();
  return 0;
}

/* ------ Internal routines ------ */

private void
gdev_pc98hf_graphic_enable(void)
{
	hf_io_enable();
	hf_enable();
    gdev_pc98hf_init_register();
	hf_fbuf_on();
	hf_io();
	hf_16color_off();
}

private void
gdev_pc98hf_graphic_disable(void)
{
	hf_fbuf_off();
	hf_impose_off();
	hf_disable();
	hf_io_disable();
}

private void
gdev_pc98hf_init_register(void)
{  int i;
	hf_register_on();
    for (i=0;i<0x1b;i++)
		hf_setregister_a(i,0);
    hf_setregister_b(1,0x12);
	hf_setregister_b(2,0);
	hf_setregister_b(3,0);
}

private void
gdev_pc98hf_impose_screen(void)
{
  hf_setregister_b(3,0x30);
  hf_impose_on();
}

private void
gdev_pc98hf_hyperframe_screen(void)
{
  hf_setregister_b(3,0x00);
  hf_impose_off();
}

private void
gdev_pc98hf_clear_screen(void)
{ int y;
  for (y=0;y<SCREEN_HEIGHT;y++) {
    int x;
    for (x=0;x<SCREEN_WIDTH;x++) {
      hf_putpixel(x,y,0,0,0);
    }
  }
}

private void
gdev_pc98hf_copy_fb2hf(byte *fb,int x,int y,int w,int h,int sourcew)
{
  int skip = sourcew * 3;
  int yy;
  int xend,yend;
  xend = x+w;
  yend = y+h;
  
  fb += (y * sourcew + x) * 3;
  for (yy = y; yy < yend ; yy++,fb += skip) {
    int xx;
    int tmpy = yy % SCREEN_HEIGHT;
	byte *tmpfb = fb;
    for (xx = x; xx<xend; xx++) {
		int r = *tmpfb++;
		int g = *tmpfb++;
		int b = *tmpfb++;
		hf_putpixel(xx%SCREEN_WIDTH,tmpy,r,g,b);
 	}
  }
}
