/* Copyright (C) 1989, 1990, 1991 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.  */

/* gdevpc98.c */
/* Ghostscript driver for PC-9801 with DJ's GCC */
#include <string.h>
#include <stdlib.h>
#include <dos.h>
#include <pc.h>
#include "ghost.h"
#include "errors.h"
#include "oper.h"
#include "store.h"
#include "dict.h"
#include "dstack.h"
#include "gxdevice.h"
#include "gsprops.h"
#include "djgpp.h"

#define WIDTH_INCHES	8
#define HEIGHT_INCHES	11
#define DEFAULT_DPI	48
#define DEFAULT_DPI_XA 118

#define SCREEN_WIDTH8	(640 / 8)
#define SCREEN_HEIGHT	400

#define SCREEN_WIDTH8_XA	(1120 / 8)
#define SCREEN_HEIGHT_XA	750

#define VIEWPORT_WIDTH8	(528 / 8)
#define VIEWPORT_HEIGHT	386

#define VIEWPORT_WIDTH8_XA	(1000 / 8)
#define VIEWPORT_HEIGHT_XA	726

#define GRAY_TONE	3
#define COLOR_TONE	2

#define NUM_PLANES	4

static int hireso_flag=-1;
static int rgb_separate=0;
static int screen_width8;
static int screen_height;
static int viewport_width8;
static int viewport_height;
static byte *GRAM[NUM_PLANES];

#define dci_16color \
{ 3, NUM_PLANES, GRAY_TONE - 1, COLOR_TONE - 1, GRAY_TONE, COLOR_TONE }
#define dci_16gray { 1, 4, 15, 0, 16, 0 }

/*
 * The frame buffer
 */

struct fbuf_s {
	int width;
	int height;
	int width8;
	int plane_size;
	byte *plane[NUM_PLANES];
};

typedef struct fbuf_s fbuf;
private int fbuf_open(P3(int, int, fbuf *));
private int fbuf_close(P1(fbuf *));
private int fbuf_pixel(P4(fbuf *, int, int, int));
private int fbuf_bar(P6(fbuf *, int, int, int, int, int));
private int fbuf_output(P7(fbuf *, int, int, int, int, int, int));
private void gshift( unsigned int adr_1, unsigned int adr_2, unsigned int ln );
private void select_framebuffer(int );
private void select_impose(int );

/*
 * The device procedures
 */

dev_proc_open_device(pc98_open);
dev_proc_open_device(pc98xa_open);
dev_proc_sync_output(pc98_sync_output);
dev_proc_output_page(pc98_output_page);
dev_proc_close_device(pc98_close);
dev_proc_map_rgb_color(pc98_map_rgb_color);
dev_proc_map_color_rgb(pc98_map_color_rgb);
dev_proc_fill_rectangle(pc98_fill_rectangle);
dev_proc_tile_rectangle(pc98_tile_rectangle);
dev_proc_copy_mono(pc98_copy_mono);
dev_proc_copy_color(pc98_copy_color);
dev_proc_put_props(pc98_put_props);
dev_proc_get_props(pc98_get_props);

static gx_device_procs pc98_procs = {
	pc98_open,
	gx_default_get_initial_matrix,
	pc98_sync_output,
	pc98_output_page,
	pc98_close,
	pc98_map_rgb_color,
	pc98_map_color_rgb,
	pc98_fill_rectangle,
	pc98_tile_rectangle,
	pc98_copy_mono,
	pc98_copy_color,
	gx_default_draw_line,
	gx_default_get_bits,
	pc98_get_props,
	pc98_put_props
};

/*
 * The device descriptor
 */

struct gx_device_pc98_s {
	gx_device_common;
	int init_props_f;
	fbuf portrait;
	fbuf landscape;
	fbuf *pfb;		/* the current frame buffer */
	int x8, y;
	int vx8, vy, vw8, vh;	/* viewport */
	int modified;		/* the number of modified pixels */
	int limit;		/* the limit of delayed modifications */
};

typedef struct gx_device_pc98_s gx_device_pc98;

gx_device_pc98 gs_pc98_device = {
	sizeof(gx_device_pc98),
	&pc98_procs,
	"pc98",
	WIDTH_INCHES*DEFAULT_DPI,				/* width_10ths */
	HEIGHT_INCHES*DEFAULT_DPI,				/* height_10ths */
	DEFAULT_DPI,								/* x_dpi */
	DEFAULT_DPI,								/* y_dpi */
	no_margins,
	dci_16color,					/* color information */
	0,								/* not open yet */
	0,								/* not initialized yet */
};

gx_device_pc98 gs_pc98mono_device = {
	sizeof(gx_device_pc98),
	&pc98_procs,
	"pc98mono",
	WIDTH_INCHES*DEFAULT_DPI,				/* width_10ths */
	HEIGHT_INCHES*DEFAULT_DPI,				/* height_10ths */
	DEFAULT_DPI,								/* x_dpi */
	DEFAULT_DPI,								/* y_dpi */
	no_margins,
	dci_16gray,					/* color information */
	0,								/* not open yet */
	0,								/* not initialized yet */
};

private void init_props(P1(gx_device_pc98 *));

/*
 * Graphics interface
 */

private void pixel(P4(gx_device_pc98 *, int, int, int));
private void bar(P6(gx_device_pc98 *, int, int, int, int, int));
private void gflush(P1(gx_device_pc98 *));
private void gcls(P0());
private void small_screen(P1(gx_device_pc98 *));
private void full_screen(P1(gx_device_pc98 *));
private void portrait(P1(gx_device_pc98 *));
private void landscape(P1(gx_device_pc98 *));
private void go_left(P1(gx_device_pc98 *));
private void go_right(P1(gx_device_pc98 *));
private void go_up(P1(gx_device_pc98 *));
private void go_down(P1(gx_device_pc98 *));
private void go_center(P1(gx_device_pc98 *));

/*
 * Forward declarations
 */

void gdev_pc98_flushkey(P0());
int gdev_pc98_getkeycode(P0());
void gdev_pc98_nopause(P0());
private void enable_text(P0());
private void disable_text(P0());
private void start_graphics(P1(int));
private void stop_graphics(P0());

#ifdef SUPPORT_30BIOS
int gdev_pc98_30bios_normal_mode(P0());
int gdev_pc98_30bios_restore_mode(P0());
#endif

/* -------- The device procedures -------- */

int
pc98_put_props(gx_device *dev,gs_prop_item *plist,int count)
{
  init_props((gx_device_pc98 *)dev);
  return gx_default_put_props(dev,plist,count);
}

int
pc98_get_props(gx_device *dev, gs_prop_item *plist)
{
  init_props((gx_device_pc98 *)dev);
  return gx_default_get_props(dev,plist);
}

/*
 * Open the PC-9801 driver for graphics mode
 */

int
pc98_open(gx_device *dev)
{	
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;

	init_props(pdev);
	if (rgb_separate) {
		gshift( 0, 0, 750 );
	}else{
		/* change to 400 lines mode */
		union REGS inregs;
		inregs.h.ah = 0x42;
		inregs.h.ch = 0xc0;
		int86(0x18, &inregs, &inregs);
	}

	if(fbuf_open(pdev->width, pdev->height, &pdev->portrait) < 0 ||
	   fbuf_open(pdev->height, pdev->width, &pdev->landscape) < 0)
		return -1;

	small_screen(pdev);

	pdev->modified = 0;
	pdev->limit = pdev->width * pdev->height * 1 / 100;	/* 1 % */

	gdev_pc98_nopause();
#ifdef SUPPORT_30BIOS
    if (!hireso_flag) gdev_pc98_30bios_normal_mode();
#endif
	start_graphics(gx_device_has_color(pdev));
    select_impose(gx_device_has_color(pdev));
    
	return 0;
}

/*
 * Close the PC-9801 driver
 */

int
pc98_close(gx_device *dev)
{
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;

	fbuf_close(&pdev->portrait);
	fbuf_close(&pdev->landscape);

	gcls();
	stop_graphics();
#ifdef SUPPORT_30BIOS
    if (!hireso_flag) gdev_pc98_30bios_restore_mode();
#endif

	return 0;
}

/*
 * Map a r-g-b color to the 16 colors available with PC-9801 GRAM.
 */

#define BLACK	0
#define WHITE	15

gx_color_index
pc98_map_rgb_color(gx_device *dev,
		   gx_color_value r, gx_color_value g, gx_color_value b)
{
	return gx_device_has_color(dev) ?
	  (gx_color_index)
		((r > gx_max_color_value / 4 ? 4 : 0) +
		 (g > gx_max_color_value / 4 ? 2 : 0) +
		 (b > gx_max_color_value / 4 ? 1 : 0) +
		 (r > gx_max_color_value / 4 * 3 ||
		  g > gx_max_color_value / 4 * 3 ||
		  b > gx_max_color_value / 4 * 3 ? 8 : 0)) :
    (gx_color_index) max(max(r,g),max(g,b));
}

/*
 * Map a color code to r-g-b.  Surprisingly enough, this is algorithmic.
 */

int
pc98_map_color_rgb(gx_device *dev, gx_color_index color,
		  gx_color_value prgb[3])
{
#define icolor (int)color
if (gx_device_has_color(dev)) {
	gx_color_value one =
		(icolor & 8 ? gx_max_color_value : gx_max_color_value / 3);
	prgb[0] = (icolor & 4 ? one : 0);
	prgb[1] = (icolor & 2 ? one : 0);
	prgb[2] = (icolor & 1 ? one : 0);
}
else {
	prgb[0] = prgb[1] = prgb[2] = color;
}
	return 0;
#undef icolor
}

/*
 * Copy a monochrome bitmap.  The colors are given explicitly.
 * Color = gx_no_color_index means transparent (no effect on the image).
 */

int
pc98_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 zero, gx_color_index one)
{
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;
	const byte *ptr_line = base + (sourcex >> 3);
	int left_bit = 0x80 >> (sourcex & 7);
	int dest_y = y, end_x = x + w;
	int invert = 0;
	int color;

	if( zero == gx_no_color_index ) {
		if( one == gx_no_color_index )
			return 0;
		color = (int)one;
	}
	else {
		if( one == gx_no_color_index ) {
			color = (int)zero;
			invert = -1;
		}
		else {
			/* Pre-clear the rectangle to zero */
			bar(pdev, x, y, w, h, (int)zero);
			color = (int)one;
		}
	}

	while( h-- ) {		/* for each line */
		const byte *ptr_source = ptr_line;
		register int dest_x = x;
		register int bit = left_bit;
		while( dest_x < end_x ) {	/* for each bit in the line */
			if((*ptr_source ^ invert) & bit)
				pixel(pdev, dest_x, dest_y, color);
			dest_x++;
			if((bit >>= 1) == 0)
				bit = 0x80, ptr_source++;
		}
		dest_y++;
		ptr_line += raster;
	}

	return 0;
}

/*
 * Copy a color pixel map.  This is just like a bitmap, except that
 * each pixel takes 4 bits instead of 1 when device driver has color.
 */

int
pc98_copy_color(gx_device *dev,
		const byte *base, int sourcex, int raster, gx_bitmap_id id,
		int x, int y, int w, int h)
{
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;

	if (gx_device_has_color(dev)) {
		/* color device, four bits per pixel */
		const byte *line = base + (sourcex >> 1);
		int dest_y = y, end_x = x + w;

		if( w <= 0 )
			return 0;
		while( h-- ) {
			/* for each line */
			const byte *source = line;
			register int dest_x = x;
			if( sourcex & 1 ) {	/* odd nibble first */ 
				int color =  *source++ & 0xf;
				pixel(pdev, dest_x, dest_y, color);
				dest_x++;
			}
			/* Now do full bytes */
			while( dest_x < end_x ) {
				int color = *source >> 4;
				pixel(pdev, dest_x, dest_y, color);
				dest_x++;
				if( dest_x < end_x ) {
					color =  *source++ & 0xf;
					pixel(pdev, dest_x, dest_y, color);
					dest_x++;
				}
			}
			dest_y++;
			line += raster;
		}
	}
	else {	/* monochrome device: one bit per pixel */
		/* bitmap is the same as bgi_copy_mono: one bit per pixel */
		pc98_copy_mono(dev, base, sourcex, raster, id, x, y, w, h, 
			       (gx_color_index)BLACK, (gx_color_index)WHITE);
	}

	return 0;
}

/*
 * Fill a rectangle.
 */

int
pc98_fill_rectangle(gx_device *dev,
		    int x, int y, int w, int h, gx_color_index color)
{
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;

	bar(pdev, x, y, w, h, (int)color);
	return 0;
}

/*
 * Tile a rectangle.  If neither color is transparent,
 * pre-clear the rectangle to color0 and just tile with color1.
 * This is faster because of how pc98_copy_mono is implemented.
 * Note that this also does the right thing for colored tiles.
 */

int
pc98_tile_rectangle(gx_device *dev, const gx_bitmap *tile,
		    int x, int y, int w, int h, 
		    gx_color_index czero, gx_color_index cone,
		    int px, int py)
{
	if(czero != gx_no_color_index && cone != gx_no_color_index) {
		pc98_fill_rectangle(dev, x, y, w, h, czero);
		czero = gx_no_color_index;
	}
	return gx_default_tile_rectangle(dev, tile, x, y, w, h,
					 czero, cone, px, py);
}

/*
 * Flush output
 */

int
pc98_sync_output(gx_device *dev)
{
	gx_device_pc98 *pdev = (gx_device_pc98 *)dev;

	gflush(pdev);
	return 0;
}

/*
 * Output page
 */

int
pc98_output_page(gx_device *dev, int num_copies, int flush)
{
  gx_device_pc98 *pdev = (gx_device_pc98 *)dev;
  int ls_f = 0;
  int imp = 0;
  int col_f = gx_device_has_color(pdev);

	full_screen(pdev);
	portrait(pdev);
	gflush(pdev);

	rgb_separate = 0;
    gdev_pc98_flushkey();
    
	for ( ; ; ) {
    int c = gdev_pc98_getkeycode();
    if (c == 0x1c || c == 0x00) break;
		switch(c) {
    case 0x34:
      if (ls_f) goto pr;
      else goto ls;
      break;
		case 0x25: /* Landscape */
ls:   landscape(pdev);
      ls_f = 1;
			break;
		case 0x19: /* Portrait */
pr:   portrait(pdev);
      ls_f = 0;
			break;
		case 0x01: /* Left and down */
		case 0x4a:
			go_left(pdev);
			go_down(pdev);
			break;
		case 0x02: /* Down */
		case 0x4b:
		case 0x2a:
    case 0x3d:
			go_down(pdev);
			break;
		case 0x03: /* Right and Down */
    case 0x4c:
			go_right(pdev);
			go_down(pdev);
			break;
		case 0x04: /* Left */
		case 0x46:
		case 0x1e:
    case 0x3b:
			go_left(pdev);
			break;
    case 0x05: /* Center */
		case 0x47:
    case 0x3e:
    case 0x5e:
			go_center(pdev);
			break;
		case 0x06: /* Right */
		case 0x48:
		case 0x1f:
    case 0x3c:
			go_right(pdev);
			break;
		case 0x07: /* Left and Up */
    case 0x42:
			go_left(pdev);
			go_up(pdev);
			break;
		case 0x08: /* Up */
		case 0x43:
		case 0x12:
    case 0x3a:
			go_up(pdev);
			break;
		case 0x09: /* Right and Up */
    case 0x44:
			go_right(pdev);
			go_up(pdev);
			break;
    case 0x3f:
		if (imp) { imp = 0; select_framebuffer(col_f); }
		else { imp = 1; select_impose(col_f); }
		break;
		default:
			continue;
		}
		gflush(pdev);
	}

	small_screen(pdev);
	gflush(pdev);
    select_impose(col_f);

	rgb_separate = (hireso_flag)?1:0;

	return 0;
}

/* -------- Initialize propaties. -------- */
private void
init_props(gx_device_pc98 *dev)
{
  if (dev->init_props_f) return;
  if (hireso_flag < 0) {
    if (*(byte *)(PC_CONV_BASE+0x501) & 8){
	/* システム共通域 0h:459h の ビット 3 でインターレースモードの調査 */
		hireso_flag=( (*(char *)(PC_CONV_BASE+0x459)) & 8)? 2:1;
		rgb_separate=1;
		screen_width8=SCREEN_WIDTH8_XA;
		screen_height=SCREEN_HEIGHT_XA;
		viewport_width8=VIEWPORT_WIDTH8_XA;
		viewport_height=VIEWPORT_HEIGHT_XA;
		GRAM[0] = (byte *)PC_VRAM_BASE;	/* Blue */
		GRAM[1] = (byte *)(PC_VRAM_BASE+0x40000);	/* Green */
		GRAM[2] = (byte *)(PC_VRAM_BASE+0x20000);	/* Red */
		GRAM[3] = (byte *)(PC_VRAM_BASE+0x60000);	/* Intensity */
	}
	else{
		hireso_flag=0;
		rgb_separate=0;
		screen_width8=SCREEN_WIDTH8;
		screen_height=SCREEN_HEIGHT;
		viewport_width8=VIEWPORT_WIDTH8;
		viewport_height=VIEWPORT_HEIGHT;
		GRAM[0] = (byte *)(PC_CONV_BASE+0xA8000);	/* Blue */
		GRAM[1] = (byte *)(PC_CONV_BASE+0xB8000);	/* Green */
		GRAM[2] = (byte *)(PC_CONV_BASE+0xB0000);	/* Red */
		GRAM[3] = (byte *)(PC_CONV_BASE+0xE0000);	/* Intensity */
	}
  }
  if (rgb_separate) {
    dev->x_pixels_per_inch=DEFAULT_DPI_XA;
    dev->width = WIDTH_INCHES*DEFAULT_DPI_XA;
    dev->y_pixels_per_inch=DEFAULT_DPI_XA;
    dev->height = HEIGHT_INCHES*DEFAULT_DPI_XA;
  }
  else {
    dev->x_pixels_per_inch=DEFAULT_DPI;
    dev->width = WIDTH_INCHES*DEFAULT_DPI;
    dev->y_pixels_per_inch=DEFAULT_DPI;
    dev->height = HEIGHT_INCHES*DEFAULT_DPI;
  }
  dev->init_props_f = 1;
}

/* -------- Graphics interface for the device -------- */

private void
pixel(gx_device_pc98 *pdev, int x, int y, int color)
{
	fbuf_pixel(&pdev->portrait, x, y, color);
	fbuf_pixel(&pdev->landscape, pdev->height - 1 - y, x, color);
	if(++ pdev->modified > pdev->limit)
		gflush(pdev);
}

private void
bar(gx_device_pc98 *pdev, int x, int y, int w, int h, int color)
{
	fbuf_bar(&pdev->portrait, x, y, w, h, color);
	fbuf_bar(&pdev->landscape, pdev->height - y - h, x, h, w, color);
	if((pdev->modified += w*h) > pdev->limit)
		gflush(pdev);
}

private void
gflush(gx_device_pc98 *pdev)
{
	fbuf_output(pdev->pfb, pdev->x8, pdev->y,
		    pdev->vx8, pdev->vy, pdev->vw8, pdev->vh);
	pdev->modified = 0;
}

private void
gcls(void)
{
	int i, lnum;
	byte *ptr;

	if (rgb_separate==0){
		for(lnum=0; lnum<screen_height; lnum++) {
			for(i=0; i<NUM_PLANES; i++) {
				ptr = GRAM[i] + screen_width8 * lnum;
				memset(ptr, 0, screen_width8);
			}
		}
	}else{
		for(i=0; i<NUM_PLANES; i++) {
			memset(GRAM[i], 0, screen_width8 * screen_height);
		}
	}
}

private void
small_screen(gx_device_pc98 *pdev)
{
	pdev->pfb = &pdev->landscape;
	pdev->x8  = max(0, (pdev->landscape.width8 - viewport_width8) / 2);
	pdev->y   = max(0, (pdev->landscape.height - viewport_height) / 2);

	pdev->vx8 = screen_width8 - viewport_width8;
	pdev->vy  = 0;
	pdev->vw8 = viewport_width8;
	pdev->vh  = viewport_height;

	enable_text();
}

private void
full_screen(gx_device_pc98 *pdev)
{
	pdev->vx8 = 0;
	pdev->vy  = 0;
	pdev->vw8 = screen_width8;
	pdev->vh  = screen_height;

	if (hireso_flag) gshift( 0, 0, 750 );

	disable_text();
}

private void
adjust_xy(gx_device_pc98 *pdev)
{
	if(pdev->x8 < 0)
		pdev->x8 = 0;
	else if(pdev->pfb->width8 <= pdev->vw8)
		pdev->x8 = 0;
	else if(pdev->x8 > pdev->pfb->width8 - pdev->vw8)
		pdev->x8 = pdev->pfb->width8 - pdev->vw8;
	
	if(pdev->y < 0)
		pdev->y = 0;
	else if(pdev->pfb->height <= pdev->vh)
		pdev->y = 0;
	else if(pdev->y > pdev->pfb->height - pdev->vh)
		pdev->y = pdev->pfb->height - pdev->vh;
}

private void
portrait(gx_device_pc98 *pdev)
{
	int cx, cy, ccx, ccy;
	int vw = pdev->vw8 * 8, vh = pdev->vh;
	fbuf *pfb = pdev->pfb;

	if(pfb == &pdev->portrait)
		return;

	cx = pdev->x8 * 8 + min(pfb->width,  vw) / 2;
	cy = pdev->y      + min(pfb->height, vh) / 2;

	ccx = cy;
	ccy = pdev->height - 1 - cx;

	pdev->x8 = (ccx - vw / 2) / 8;
	pdev->y  = (ccy - vh / 2);
	pdev->pfb = &pdev->portrait;

	adjust_xy(pdev);
}

private void
landscape(gx_device_pc98 *pdev)
{
	int cx, cy, ccx, ccy;
	int vw = pdev->vw8 * 8, vh = pdev->vh;
	fbuf *pfb = pdev->pfb;

	if(pfb == &pdev->landscape)
		return;

	cx = pdev->x8 * 8 + min(pfb->width,  vw) / 2;
	cy = pdev->y      + min(pfb->height, vh) / 2;

	ccx = pdev->height - 1 - cy;
	ccy = cx;

	pdev->x8 = (ccx - vw / 2) / 8;
	pdev->y  = (ccy - vh / 2);
	pdev->pfb = &pdev->landscape;

	adjust_xy(pdev);
}

private void
go_left(gx_device_pc98 *pdev)
{
	int step = pdev->vw8 / 2;

	pdev->x8 -= step;

	adjust_xy(pdev);
}

private void
go_right(gx_device_pc98 *pdev)
{
	int step = pdev->vw8 / 2;

	pdev->x8 += step;

	adjust_xy(pdev);
}

private void
go_up(gx_device_pc98 *pdev)
{
	int step = pdev->vh / 2;

	pdev->y -= step;

	adjust_xy(pdev);
}

private void
go_down(gx_device_pc98 *pdev)
{
	int step = pdev->vh / 2;

	pdev->y += step;

	adjust_xy(pdev);
}

private void
go_center(gx_device_pc98 *pdev)
{
	int vw = pdev->vw8 * 8, vh = pdev->vh;
	fbuf *pfb = pdev->pfb;

	pdev->x8 = (pfb->width / 2 - vw / 2) / 8;
	pdev->y  = (pfb->height / 2 - vh / 2);

	adjust_xy(pdev);
}

/* -------- Frame buffer utilities -------- */

#define fbuf_clip_x(pfb, x) \
	((x) < 0 ? 0 : ((x) >= (pfb)->width ? (pfb)->width - 1 : (x)))
#define fbuf_clip_y(pfb, y) \
	((y) < 0 ? 0 : ((y) >= (pfb)->height ? (pfb)->height - 1 : (y)))

private int
fbuf_open(int width, int height, fbuf *pfb)
{
	int i;

	pfb->width = width;
	pfb->height = height;
	pfb->width8 = (pfb->width - 1) / 8 + 1;
	pfb->plane_size = pfb->width8 * pfb->height;

	for(i=0; i<NUM_PLANES; i++) {
		if((pfb->plane[i] = 
		    (byte *)gs_malloc(pfb->plane_size, 1,
				      "fbuf_open(plane)")) == NULL)
			return -1;
		memset(pfb->plane[i], 0, pfb->plane_size);
	}

	return 0;
}

private int
fbuf_close(fbuf *pfb)
{
	int i;

	for(i=0; i<NUM_PLANES; i++)
		gs_free((char *)pfb->plane[i],
			pfb->plane_size, 1, "fbuf_close(plane)");

	return 0;
}

private int
fbuf_pixel(fbuf *pfb, int x, int y, int color)
{
	int i, offset;
	byte mask;

	if(x != fbuf_clip_x(pfb, x) || y != fbuf_clip_y(pfb, y))
		return 0;

	offset = x / 8 + y * pfb->width8;
	mask = 0x80 >> (x % 8);

	for(i=0; i<NUM_PLANES; i++) {
		if(color & (1 << i))
			pfb->plane[i][offset] |= mask;
		else
			pfb->plane[i][offset] &= ~mask;
	}

	return 0;
}

private int
fbuf_bar(fbuf *pfb, int x, int y, int w, int h, int color)
{
	int x_beg, x_end;
	int x1, y1, x2, y2;

	if(w <= 0 || h <= 0)
		return 0;

	x1 = fbuf_clip_x(pfb, x);
	y1 = fbuf_clip_y(pfb, y);
	x2 = fbuf_clip_x(pfb, x + w - 1);
	y2 = fbuf_clip_y(pfb, y + h - 1);

	x_beg = x1 / 8;
	x_end = x2 / 8;

	if(x_beg == x_end) {
		int i;
		byte mask;
		
		mask = ((0xff >> (x1 % 8)) & (0xff << (7 - (x2 % 8))));

		for(i=0; i<NUM_PLANES; i++) {
			int yy;
			byte *ptr;

			ptr = pfb->plane[i] + x_beg + y1 * pfb->width8;

			if(color & (1 << i)) {
				for(yy = y1; yy <= y2; yy ++) {
					*ptr |= mask;
					ptr += pfb->width8;
				}
			}
			else {
				for(yy = y1; yy <= y2; yy ++) {
					*ptr &= ~mask;
					ptr += pfb->width8;
				}
			}
		}
	}
	else {
		int i;
		byte head_mask, tail_mask;
		
		head_mask = 0xff >> (x1 % 8);
		tail_mask = 0xff << (7 - (x2 % 8));

		for(i=0; i<NUM_PLANES; i++) {
			int xx, yy;
			byte *base;

			base = pfb->plane[i] + y1 * pfb->width8;

			if(color & (1 << i)) {
				for(yy = y1; yy <= y2; yy ++) {
					base[x_beg] |= head_mask;
					for(xx = x_beg + 1; xx < x_end; xx ++)
						base[xx] = 0xff;
					base[x_end] |= tail_mask;
					base += pfb->width8;
				}
			}
			else {
				for(yy = y1; yy <= y2; yy ++) {
					base[x_beg] &= ~head_mask;
					for(xx = x_beg + 1; xx < x_end; xx ++)
						base[xx] = 0;
					base[x_end] &= ~tail_mask;
					base += pfb->width8;
				}
			}
		}
	}

	return 0;
}

private int
fbuf_output(fbuf *pfb, int x8, int y, int vx8, int vy, int vw8, int vh)
{
	int i, lnum, width8, height;
	int left, top, right, bottom;
	int src_offset, dst_offset;
	byte *src, *dst;

	vw8 = min(screen_width8, vw8);
	vh  = min(screen_height, vh );

	width8 = min(vw8, pfb->width8 - x8);
	height = min(vh , pfb->height - y );

	left = vx8 + (vw8 - width8) / 2;
	top  = vy + (vh - height) / 2;
	right = left + width8 - 1;
	bottom = top + height - 1;

	src_offset = x8 + y * pfb->width8;
	dst_offset = 0;
	if (rgb_separate==0){
		for(lnum=0; lnum < top; lnum ++) {
			for(i=0; i<NUM_PLANES; i++)
				memset(GRAM[i] + dst_offset, 0, screen_width8);
			dst_offset += screen_width8;
		}
		for(lnum=top; lnum<=bottom; lnum++) {
			for(i=0; i<NUM_PLANES; i++) {
				src = pfb->plane[i] + src_offset;
				dst = GRAM[i] + dst_offset;
				memset(dst, 0, left);
				dst += left;
				memcpy(dst, src, width8);
				dst += width8;
				memset(dst, 0, screen_width8 - right - 1);
			}
			src_offset += pfb->width8;
			dst_offset += screen_width8;
		}
		for(lnum=bottom + 1; lnum<screen_height; lnum++) {
			for(i=0; i<NUM_PLANES; i++)
				memset(GRAM[i] + dst_offset, 0, screen_width8);
			dst_offset += screen_width8;
		}
	}else{
		for(i=0; i<NUM_PLANES; i++){
			memset(GRAM[i], 0, screen_width8*top);
		}
		dst_offset = screen_width8 * top;
		for(i=0; i<NUM_PLANES; i++) {
			int work;
			src = pfb->plane[i] + src_offset;
			dst = GRAM[i] + dst_offset;
			work = screen_width8 - right - 1;
			for(lnum=top; lnum<=bottom; lnum++) {
				memset(dst, 0, left);
				dst += left;
				memcpy(dst, src, width8 );
				dst += width8;
				memset(dst, 0, work );
				dst += work;
				src += pfb->width8;
			}
		}
		dst_offset = screen_width8 * bottom;
		for(i=0; i<NUM_PLANES; i++){
			memset(GRAM[i] + dst_offset, 0, 
			screen_width8 * (screen_height - bottom - 1) );
		}
	}
	return 0;
}

/* -------- Miscellaneous Routines -------- */

void gdev_pc98_flushkey(void)
{
  union REGS in,out;
  
  do { /* flush keyboard buffer. */
    in.h.ah = 0x01;
    int86(0x18,&in,&in);
    if (in.h.bh) {
      out.h.ah = 0;
      int86(0x18,&out,&out);
    }
  } while(in.h.bh);
}

int
gdev_pc98_getkeycode(void)
{
  union REGS in,out;
  
  in.h.ah = 0x01;
  do {
    int86(0x18,&in,&out);
  } while (out.h.bh == 0);
  in.h.ah = 0;
  int86(0x18,&in,&out);
  return out.h.ah;
}

private void
enable_text(void)
{
#if 0
	outportb(0x62, 0x0D);	/* GDC start */
#else
	union REGS inregs;
	inregs.h.ah = 0x0c;
	int86(0x18, &inregs, &inregs);
#endif
}

private void
disable_text(void)
{
#if 0
	outportb(0x62, 0x0C);	/* GDC stop */
#else
	union REGS inregs;
	inregs.h.ah = 0x0d;
	int86(0x18, &inregs, &inregs);
#endif
}

private void
set_pallet(int c,int color_f)
{
	int color, one, red, green, blue;
    
	for(color=0; color < 16; color ++) {
		if (color_f) {
			one   = ((color & 0x08) ? c : c>>1);
			green = ((color & 0x04) ? one : 0);
			red   = ((color & 0x02) ? one : 0);
			blue  = ((color & 0x01) ? one : 0);
		}
		else {
		  green = red = blue = (((color * c) << 4) / 15) >> 4;
		}
		outportb(0xA8, color);
		if (hireso_flag){
			outportb(0xA8, color);
			outportb(0x5f, 0x0);
			outportb(0x5f, 0x0);
			outportb(0xAA, green);
			outportb(0x5f, 0x0);
			outportb(0x5f, 0x0);
			outportb(0xAC, red);
			outportb(0x5f, 0x0);
			outportb(0x5f, 0x0);
			outportb(0xAE, blue);
		}else{
			outportb(0xA8, color);
			outportb(0xAA, green);
			outportb(0xAC, red);
			outportb(0xAE, blue);
		}
	}

}

private void
select_impose(int c)
{
  set_pallet(12,c);
  enable_text();
}

private void
select_framebuffer(int c)
{
  set_pallet(15,c);
  disable_text();
}

private void
start_graphics(int color_f)
{

	/* 16 color mode */
	if (hireso_flag==0){
		outportb(0x6A, 0x01);
	}
	
    set_pallet(0,color_f);
    
	/* GDC start */
	if (hireso_flag){
		int i;
		do{
			outportb(0x5f,0);
			i= inportb(0xa0);
			outportb(0x5f,0);
		}while ( (i & 0x22) != 0x20 );
		do{
			outportb(0x5f,0);
			i=inportb(0xa0);
			outportb(0x5f,0);
		}while ( i & 2 );
		outportb(0x5f,0);
		outportb(0xa2, 0x0d);
		outportb(0x5f,0);
	}else{
		while ( inportb(0xa0) & 2 );
		while ( inportb(0xa0) & 0x20 == 0 );
		outportb(0xA2, 0x0D);
	}
}

private void
stop_graphics(void)
{
	int i;
	if(hireso_flag){
		do{
			outportb(0x5f,0);
			i=inportb(0xa0);
			outportb(0x5f,0);
		}while ( i & 2 );
		outportb(0x5f, 0x0);
		outportb(0xa2, 0x05);
		outportb(0x5f,0);
	}else{
		/* GDC stop */
		while( inportb(0xa0) & 2 );
			outportb(0xA2, 0x0C);
		/* 8 color mode */
		outportb(0x6A, 0x00);
	}
}

void
gdev_pc98_nopause(void)
{
	ref key, value;
	char *str = "NOPAUSE";

	make_tasv(&key, t_string, a_read+a_execute, 
		  strlen(str), bytes, (byte *)str);
	make_true(&value);

	if(dict_put(systemdict, &key, &value) < 0)
		fprintf(stderr, "Error dict_put\n");
	return;
}
private void
gshift( unsigned int adr_1, unsigned int adr_2, unsigned int ln )
{
	unsigned int ln2, GDC5M, INCADR;
	ln2 = 750-ln;

	GDC5M = (hireso_flag==0 && (inportb( 0x31 ) & 0x80) == 0 )? 0x40:0;
	INCADR = (hireso_flag==2)? 0x80:0;
	if (hireso_flag==1){		/* インターレースモードの配慮 */
				ln=ln>>1;
				ln2=ln2>>1;
	}
    outportb( 0xa2, 0x70 );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, adr_1 & 0xff );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, (adr_1 >> 8) & 0xff );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, (ln & 0xf) << 4 );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, (ln>>4)|GDC5M|INCADR);
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, adr_2 & 0xff );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, adr_2 >> 8 );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, (ln2 & 0xf) << 4 );
    outportb( 0x5f, 0x0 );
    outportb( 0x5f, 0x0 );
    outportb( 0xa0, (ln2 >>4)|GDC5M|INCADR);
}

/* 30 bios support routines. */

#ifdef SUPPORT_30BIOS
#define INT_CRT 0x18

/*
 * return
 * -1 : error
 *  0 : 30 bios does not resident.
 *  1 : CRT mode has changed
 */
private int
  gdev_pc98_30bios_check(void)
{
  union REGS ireg,oreg;
  ireg.h.ah = 0x0b;
  ireg.x.bx = 0xc0a3;
  int86(INT_CRT,&ireg,&oreg);
  if (oreg.h.al & 0x40) {  /* 30 bios residents */
    return oreg.h.al;
  }
  ireg.h.ah = 0x18;
  ireg.h.al = 0;
  ireg.x.bx = 0x5454;
  int86(INT_CRT,&ireg,&oreg);
  return (oreg.x.ax == 0x5454) ? -1 : 0;
}

int gdev_pc98_30bios_normal_mode(void)
{
  union REGS ireg,oreg;
  int omode = gdev_pc98_30bios_check();

  if (omode == 0) return 0;
  else if (omode > 0) { /* 30 bios */
    ireg.x.ax = 0xff01;
    ireg.x.bx = 0xc0a3;
    int86(INT_CRT,&ireg,&oreg);
    if (oreg.x.ax != 0xffff) return -1;
    ireg.h.ah = 0x0a;
    ireg.h.al = omode & 0xef;
    int86(INT_CRT,&ireg,&oreg);
    return 1;
  }
  else { /* TT */
    ireg.h.ah = 0x18;
    ireg.h.al = 0x0a;
    ireg.x.bx = 0x5454;
    int86(INT_CRT,&ireg,&oreg);
    if (oreg.h.al == 16) return -1;
    ireg.h.al = 0x04;
    int86(INT_CRT,&ireg,&oreg);
    return 1;
  }
  return 0;
}

int gdev_pc98_30bios_restore_mode(void)
{
  union REGS reg;
  int check = gdev_pc98_30bios_check();

  if (check == 0) return 0;
  else if (check > 0) {
    reg.x.ax = 0xff02;
    int86(INT_CRT,&reg,&reg);
    return reg.x.ax == 0xffff ? 0 : -1;
  }
  else {
    reg.h.ah = 0x18;
    reg.h.al = 0x0b;
    reg.x.bx = 0x5454;
    int86(INT_CRT,&reg,&reg);
    return reg.h.al == 0xff ? -1 : 0;
  }
}
#endif /* SUPPORT_30BIOS */
