/*****************************************************************************
*   General routines to	handle the graphics.				     *
*									     *
* Written by:  Gershon Elber			       Ver 0.1, Oct. 1989    *
*									     *
* Support: Interface for the virtual device driver.			     *
*****************************************************************************/

#define SUPPORT_GIF				 /* Dump it out as GIF file. */

#ifdef SUPPORT_GIF
#include "gif_lib.h"
#endif /* SUPPORT_GIF */

#include <stdio.h>
#include "virtrstr.h"
#include "program.h"
#include "igraph.h"

/* #define DEBUG	     Dump virtual screen to Hercules graphic device. */

#ifdef __MSDOS__
#include <io.h>
#include <fcntl.h>
#include <bios.h>
#include <dos.h>
#include <conio.h>
#endif /* __MSDOS__ */

#define EPSON_SIZE_X 1024
#define EPSON_SIZE_Y 640
#define EPSON_SIZE_MAX 1024		    /* The maximum of the two above. */
#define EPSON_SIZE_X2 (EPSON_SIZE_X / 2)
#define EPSON_SIZE_Y2 (EPSON_SIZE_Y / 2)

/* The epson specific are defined here:					     */
#define EPSON_WIDTH		80			/* 80 char per line. */
#define EPSON_PIXEL_2_CHAR	8      /* 8 pixels per char, in REG_DENSITY. */

#define EPSON_ESC		"\x1b"	    /* Actually regular escape char. */
#define EPSON_RESET		"\x1b\x40"     /* Reset the printer - ESC @. */
#define EPSON_VERTICAL_SPACE	"\x1b\x41\x08"   /* 8/72 vert. - ESC A 0x08. */
#define EPSON_REG_DENSITY	"\x1b\x4b"   /* 640 pixels per 7.5" - ESC K. */
#define EPSON_DUAL_DENSITY	"\x1b\x4c"  /* 1280 pixels per 7.5" - ESC L. */

static int GraphicMode = FALSE;

static void PutString(int DirectPrint, char *Str, int Len);
static void PutString2(int DirectPrint, char *Str, int Len);

#ifdef __MSDOS__
#ifdef DEBUG
static void DisplayBufferOnHercules(void);
static void HercGraphics(void);
static void HercPutPoint(int x, int y);
static void HercText(void);
#endif /* DEBUG */
#endif /* __MSDOS__ */

/****************************************************************************
* Routine to reset all the system to starting condition	:		    *
****************************************************************************/
void IGInitGraph(void)
{
    if (!VirtInit(EPSON_SIZE_X, EPSON_SIZE_Y)) {
	fprintf(stderr, "Failed to allocate virtual device (no memory).\n");
	MyExit(1);
    }
    GraphicMode = TRUE;
}

/****************************************************************************
* Routine to close and shutdown	graphic	mode :				    *
****************************************************************************/
void IGCloseGraph(void)
{
    if (GraphicMode) {
	VirtClose();
	GraphicMode = FALSE;
    }
}

/****************************************************************************
* Routine to dump the screen to the desired output device.		    *
****************************************************************************/
void IGDumpScreen(OutputDriverType OutputKind, int DirectPrint,
						BooleanType DoubleDensity)
{
#ifdef SUPPORT_GIF
    static GifColorType GifColorMap[] = { { 0, 0, 0 }, { 255, 255, 255 } };
    GifFileType *GifFile;
#endif /* SUPPORT_GIF */
    int i, j, k, Count;
    unsigned int Shift;
    char LineBuffers[8][EPSON_SIZE_Y / 8 + 1], OutputBuffer[EPSON_SIZE_MAX],
	LinePrefixLen[2];

    switch (OutputKind) {
	case OUTPUT_EPSON:
	    if (DirectPrint != 0) break;

#ifdef __MSDOS__
	    /* Make sure stdout is output for binary output and buffer it. */
	    setmode(1, O_BINARY);
#endif /* __MSDOS__ */
	    break;
#ifdef SUPPORT_GIF
	case OUTPUT_GIF:
	    if ((GifFile = EGifOpenFileHandle(1)) == NULL) {
		fprintf(stderr, "Failed to open gif file.\n");
		MyExit(-1);
	    }
	    break;
#endif /* SUPPORT_GIF */
	case OUTPUT_RAW:
#ifdef __MSDOS__
	    /* Make sure stdout is output for binary output and buffer it. */
	    setmode(1, O_BINARY);
#endif /* __MSDOS__ */
	    break;
	default:
	    fprintf(stderr, "Output kind is not supported, exit.\n");
	    MyExit(-1);
    }

#ifdef __MSDOS__
#ifdef DEBUG
    DisplayBufferOnHercules();
    return;
#endif /* DEBUG */
#endif /* __MSDOS__ */

    switch (OutputKind) {
	case OUTPUT_EPSON:
	    fprintf(stderr, "Dumping line [%d]:     ", EPSON_SIZE_X);

	    /* Reset printer, and make sure no space between adjacent lines: */
	    PutString(DirectPrint, EPSON_RESET, 2);
	    PutString(DirectPrint, EPSON_VERTICAL_SPACE, 3);

	    for (i = 0; i < EPSON_SIZE_X; i += 8) { /* Dump 8 lines at once: */
		fprintf(stderr, "\b\b\b\b%-4d", i);

		for (j = 0; j <8; j++)
		    VirtGetBlock(i + j, 0, i + j, EPSON_SIZE_Y, LineBuffers[j]);

		Shift = 1;
		Count = EPSON_SIZE_Y / 8;
		for (j = 0; j < EPSON_SIZE_Y; j++) {
		    OutputBuffer[j] = 0;
		    for (k = 0; k < 8; k++) {
			OutputBuffer[j] <<= 1;
			if (LineBuffers[k][Count] & Shift) OutputBuffer[j]++;
		    }
		    if ((Shift <<= 1) == 256) {
			Shift = 1;
			Count--;
		    }
		}
		/* Find last position of non empty pixel byte: */
		for (j = EPSON_SIZE_Y - 1; j >= 0; j--)
		    if (OutputBuffer[j] != 0) break;

		if (j != 0 || OutputBuffer[0] != 0) {
		    j++;
		    if (DoubleDensity) {
			PutString(DirectPrint, EPSON_DUAL_DENSITY, 2);
			LinePrefixLen[0] = (j * 2) % 256;
			LinePrefixLen[1] = (j * 2) / 256;
			PutString(DirectPrint, LinePrefixLen, 2);
			PutString2(DirectPrint, OutputBuffer, EPSON_SIZE_Y);
		    }
		    else {
			PutString(DirectPrint, EPSON_REG_DENSITY, 2);
			LinePrefixLen[0] = j % 256;
			LinePrefixLen[1] = j / 256;
			PutString(DirectPrint, LinePrefixLen, 2);
			PutString(DirectPrint, OutputBuffer, EPSON_SIZE_Y);
		    }
		}
		PutString(DirectPrint, "\x0d\x0a", 2);
	    }
	    break;
	case OUTPUT_RAW:
	    fprintf(stderr, "Dumping line [%d]:     ", EPSON_SIZE_X);

	    for (i = 0; i < EPSON_SIZE_X; i++) {
		fprintf(stderr, "\b\b\b\b%-4d", i);
		VirtGetBlock(i, 0, i, EPSON_SIZE_Y, LineBuffers[0]);
		for (j = 0; j < EPSON_SIZE_Y / 8; j++)
		    putchar(LineBuffers[0][j]);
	    }
	    break;
#ifdef SUPPORT_GIF
	case OUTPUT_GIF:
	    fprintf(stderr, "Dumping line [%d]:     ", EPSON_SIZE_Y);

	    if (EGifPutScreenDesc(GifFile, EPSON_SIZE_X, EPSON_SIZE_Y, 1, 0, 1,
						   GifColorMap) == GIF_ERROR ||
		EGifPutImageDesc(GifFile, 0, 0, EPSON_SIZE_X, EPSON_SIZE_Y,
						FALSE, 1, NULL) == GIF_ERROR) {
		fprintf(stderr, "Failed to write to gif file.\n");
		MyExit(-1);
	    }

	    for (i = 0; i < EPSON_SIZE_Y; i++) {
		fprintf(stderr, "\b\b\b\b%-4d", i);
		VirtGetBlock(0, i, EPSON_SIZE_X, i, LineBuffers[0]);
		Shift = 1;
		Count = EPSON_SIZE_X / 8;
		for (j = EPSON_SIZE_X; j >= 0; j--) {
		    OutputBuffer[j] = (LineBuffers[0][Count] & Shift) != 0;
		    if ((Shift <<= 1) == 256) {
			Shift = 1;
			Count--;
		    }
		}
		if (EGifPutLine(GifFile, (GifPixelType *) OutputBuffer,
						 EPSON_SIZE_X) == GIF_ERROR) {
		    fprintf(stderr, "Failed to write to gif file.\n");
		    MyExit(-1);
		}
	    }
	    if (EGifCloseFile(GifFile) == GIF_ERROR) {
		fprintf(stderr, "Failed to close gif file.\n");
		MyExit(-1);
	    }
	    break;
#endif /* SUPPORT_GIF */
    }
}

/******************************************************************************
* Dumps the string of given length, to Prt. No char in Str has special	      *
* meaning, and even zero (NULL) chars are dumped.			      *
* If however DirectPrint is non zero, string is dumped to specifed lpt port.  *
******************************************************************************/
static void PutString(int DirectPrint, char *Str, int Len)
{
    int i;

    if (DirectPrint != 0) {
#ifdef __MSDOS__
	for (i = 0; i < Len; i++) biosprint(0, Str[i], DirectPrint - 1);
#else
	fprintf(stderr, "Output kind is not supported, exit.\n");
	MyExit(-1);
#endif /* __MSDOS__ */
    }
    else
	fwrite(Str, Len, 1, stdout);
}

/******************************************************************************
* Dumps the string of given length, to Prt. No char in Str has special	      *
* meaning, and even zero (NULL) chars are dumped. Every char is dumped twice. *
* If however DirectPrint is non zero, string is dumped to specifed lpt port.  *
******************************************************************************/
static void PutString2(int DirectPrint, char *Str, int Len)
{
    int i;

    if (DirectPrint != 0) {
#ifdef __MSDOS__
	for (i = 0; i < Len; i++) {
	    biosprint(0, Str[i], DirectPrint - 1);
	    biosprint(0, Str[i], DirectPrint - 1);
	}
#else
	fprintf(stderr, "Output kind is not supported, exit.\n");
	MyExit(-1);
#endif /* __MSDOS__ */
    }
    else {
	for (i = 0; i < Len; i++) {
	    putchar(Str[i]);
	    putchar(Str[i]);
	}
    }
}


/****************************************************************************
* Routine to map drawing x coordinate into screen space.		    *
****************************************************************************/
int IGMapX(int x)
{
    return x >> IG_DEFAULT_ZOOM_FACTOR;
}

/****************************************************************************
* Routine to map drawing y coordinate into screen space.		    *
****************************************************************************/
int IGMapY(int y)
{
    return y >> IG_DEFAULT_ZOOM_FACTOR;
}

/****************************************************************************
* Routine to move to a new position, as in Drawing space.		    *
****************************************************************************/
void IGMoveTo(int x, int y)
{
    VirtMoveTo(IGMapX(x), IGMapY(y));
}

/****************************************************************************
* Routine to draw to a new position, as in Drawing space.		    *
****************************************************************************/
void IGLineTo(int x, int y)
{
    VirtLineTo(IGMapX(x), IGMapY(y));
}

/****************************************************************************
* Routine to draw a line between the two given points.			    *
****************************************************************************/
void IGLine(int x1, int y1, int x2, int y2)
{
    VirtMoveTo(IGMapX(x1), IGMapY(y1));
    VirtLineTo(IGMapX(x2), IGMapY(y2));
}

/****************************************************************************
* Routine to draw a new polyline and fill it if Fill.			    *
****************************************************************************/
void IGPoly(int n, int *Points, int Fill)
{
    int i;
    static PrintedWarning = FALSE;

    VirtMoveTo(IGMapX(Points[0]), IGMapY(Points[1]));
    for (i = 1; i < n; i++) {
	VirtLineTo(IGMapX(Points[i * 2]), IGMapY(Points[i * 2 + 1]));
    }
    if (Fill) {
	if (!PrintedWarning) {
	    fprintf(stderr, "Warning: polygon fill not supported - ignored.\n");
	    PrintedWarning = TRUE;
	}
    }
}

/****************************************************************************
* Routine to draw a bar, as in Drawing space.				    *
****************************************************************************/
void IGBar(int x1, int y1, int x2, int y2)
{
    VirtBar(IGMapX(x1), IGMapY(y1), IGMapX(x2), IGMapY(y2));
}

/****************************************************************************
* Routine to draw a circle, as in Drawing space.			    *
****************************************************************************/
void IGCircle(int x, int y, int r)
{
    VirtCirc(IGMapX(x), IGMapY(y), r >> IG_DEFAULT_ZOOM_FACTOR);
}

/****************************************************************************
* Routine to draw an arc, as in Drawing space.				    *
* As the Y axes is inverted the Angles should be inverted as well.	    *
****************************************************************************/
void IGArc(int x, int y, int StAngle, int EndAngle, int r)
{
    VirtArc(IGMapX(x), IGMapY(y), r >> IG_DEFAULT_ZOOM_FACTOR,
							StAngle, EndAngle);
}

/****************************************************************************
* Routine to draw the given text in current cursor position.		    *
****************************************************************************/
void IGText(char *s)
{
    VirtText(s);
}

/****************************************************************************
* Routine to draw the not bar above a negated text.			    *
****************************************************************************/
void IGDrawNotBar(int x1, int y1, int x2, int y2, char *Str)
{
    IGMoveToNoMap(x1, y1);
    IGLineRelToNoMap(x2, y2);
}

/****************************************************************************
* Routine to set attributes - horiz./vert. and centering.		    *
****************************************************************************/
void IGTextFormat(TextOrientationType Orient, int Scale,
		  TextHorizJustifyType XCenter, TextVertJustifyType YCenter)
{
    VirtTextFormat(Orient, Scale, XCenter, YCenter);
}

/****************************************************************************
* Routine to set color to color within range.				    *
****************************************************************************/
void IGSetColor(int Color)
{
    VirtSetColor(Color);
}

/*****************************************************************************
*   Routine to clear all the screen.					     *
*****************************************************************************/
void IGClearAllScreen(void)
{
    VirtClear();
}

/*****************************************************************************
*   Relative line to in screen coordinates.				     *
*****************************************************************************/
void IGLineRelToNoMap(int x, int y)
{
    VirtLineRelTo(x, y);
}

/*****************************************************************************
*   Move to in screen coordinates.					     *
*****************************************************************************/
void IGMoveToNoMap(int x, int y)
{
    VirtMoveTo(x, y);
}

/*****************************************************************************
*   Line in screen coordinates.						     *
*****************************************************************************/
void IGLineNoMap(int x1, int y1, int x2, int y2)
{
    VirtLine(x1, y1, x2, y2);
}

/*****************************************************************************
*   Returns text width in screen coordinates.				     *
*****************************************************************************/
int IGTextWidthNoMap(char *s)
{
    return VirtTextWidth(s);
}

/*****************************************************************************
*   Returns text height in screen coordinates.				     *
*****************************************************************************/
int IGTextHeightNoMap(char *s)
{
    return VirtTextHeight(s);
}

#ifdef __MSDOS__
#ifdef DEBUG

/******************************************************************************
* This routine fetch the vitual frame buffer content and display it on an     *
* hercules device.							      *
******************************************************************************/
static void DisplayBufferOnHercules(void)
{
    int i, j, Shift, MaxX, MaxY;
    char ScanLine[256], *p;

    MaxX = EPSON_SIZE_X;
    if (MaxX > 720 * 2) MaxX = 720 * 2;
    MaxY = EPSON_SIZE_Y;
    if (MaxY > 350 * 2) MaxY = 350 * 2;

    HercGraphics();

    for (i = 0; i < MaxX; i += 2) {
	VirtGetBlock(i, 0, i, MaxY - 1, ScanLine);
	p = ScanLine;
	Shift = 256;
	for (j = 0; j < MaxY; j++) {
	    if (Shift == 1) {
		Shift = 256;
		p++;
	    }
	    if (((*p) & (Shift >>= 1)) != 0)
		HercPutPoint(i >> 1, j >> 1);
	}
    }

    sound(1000);
    delay(100);
    nosound();

    getch();

    HercText();
}

/******************************************************************************
* Go to hercules graphic mode, page 1, and clear the screen.		      *
******************************************************************************/
static void HercGraphics(void)
{
    static int far *HerculesScreen = MK_FP(0xb000, 0x0000);
    static unsigned char GraphModeRegs[] =
	{ 53,45,46, 8,91, 2,87,87, 2, 3, 0, 0, 0, 0, 0, 0 };
    int i;

    outportb(0x3bf, 0x01);
    outportb(0x3b8, 0x00);

    for (i=0; i<12; i++) {
	outportb(0x3b4, i);
	outportb(0x3b5, GraphModeRegs[i]);
    }

    outportb(0x3b8, 0x2a);

    for (i=0; i<16384; i++) HerculesScreen[i] = 0x0000;
}

/******************************************************************************
* Plot one point at x, y on the hercules page 1 screen.			      *
******************************************************************************/
static void HercPutPoint(int x, int y)
{
    static char far *HerculesScreen = MK_FP(0xb000, 0x0000);
    int Bit = x & 0x07, Byte = x >> 3, Page = y & 0x03, PageY = y >> 2;

    HerculesScreen[Page * 0x2000 + PageY * 90 + Byte] |= 128 >> Bit;
}

/******************************************************************************
* Go to hercules text mode.						      *
******************************************************************************/
static void HercText(void)
{
    static int far *HerculesScreen = MK_FP(0xb000, 0x0000);
    static unsigned char TextModeRegs[] =
	{ 97,80,82,15,25, 6,25,25, 2,13,11,12, 0, 0, 0, 0 };
    int i;

    outportb(0x3bf, 0x01);
    outportb(0x3b8, 0x00);

    for (i=0; i<12; i++) {
	outportb(0x3b4, i);
	outportb(0x3b5, TextModeRegs[i]);
    }

    outportb(0x3b8, 0x28);

    for (i=0; i<16384; i++) HerculesScreen[i] = 0x0720;
}

#endif /* DEBUG */
#endif /* __MSDOS__ */
