/*
	name:	amiga.c

	Amiga display routines
	----------------------

	Currently supported displaymodes:

	0 - No display (default)
	1 - HAM6, lowres, non-interlaced
	2 - Grayscale (16 colors), hires, interlaced

*/


#include  <stdio.h>
#include  <dos.h>
#include  <intuition/intuition.h>
#include  <exec/exec.h>
#include  <proto/intuition.h>
#include  <proto/graphics.h>
#include  <proto/exec.h>

#include  "defs.h"
#include  "extern.h"


/* Prototypes for some subroutines */

void OpenAmigaScreen(struct NewScreen *ScreenPars,struct NewWindow *WindowPars, UWORD Palette[], ULONG NumCols);
void CloseAmigaScreen(void);
UBYTE ConvertColorToHam6(unsigned char RGBColor[], int x, int y);
UBYTE ConvertColorToGray(unsigned char RGBColor[], int x, int y);
UBYTE MG_Dither8(UBYTE Value, int x, int y);
void MG_Dither24(UBYTE RGBColor12[],unsigned char RGBColor24[], int x, int y);
int BreakHandler(void);


/* Some declarations */

#define SCREEN_TITLE_HEIGHT 11

#define DISPLAY_NONE 0			/* Different display modes */
#define DISPLAY_HAM6 1
#define DISPLAY_GRAY 2

#define HAM_MODE_PAL 0x00
#define HAM_MODE_RED 0x20
#define HAM_MODE_GRN 0x30
#define HAM_MODE_BLU 0x10


/* Some global variables */

	int	CurrentDisplayType;
	UBYTE	Last12BitColor[3] = {0,0,0};

	struct	Screen *RL_Screen = NULL;
	struct	Window *RL_Window = NULL;
	struct	RastPort *RL_RastPort = NULL;
	struct	ViewPort *RL_ViewPort = NULL;
	struct	IntuitionBase *IntBase = NULL;
	struct	GfxBase *GfxBase = NULL;


	UWORD	Ham6Palette[16] = {
			0x000, 0xAAA, 0x555, 0xFFF,
			0x808, 0xF4A, 0xE00, 0x620,
			0xF60, 0xFA8, 0xFE0, 0x080,
			0x0D0, 0x0CC, 0x06F, 0x00A
		};

	UWORD	GrayPalette[16] = {
			0x000, 0x111, 0x222, 0x333,
			0x444, 0x555, 0x666, 0x777,
			0x888, 0x999, 0xAAA, 0xBBB,
			0xCCC, 0xDDD, 0xEEE, 0xFFF
		};


	UWORD	DitherMask[16] = {
			0x8000, 0x8080, 0x8410, 0x8888,
			0x9224, 0xA492, 0xA94A, 0xAAAA,
			0xD555, 0xD6B5, 0xDB6D, 0xEDDB,
			0xF777, 0xFBEF, 0xFF7F, 0xFFFF
		};

	UBYTE	DitherYTable[16] = {
			0x08, 0x0B, 0x0E, 0x0F, 0x10, 0x0F, 0x0E, 0x0B,
			0x08, 0x05, 0x02, 0x01, 0x00, 0x01, 0x02, 0x05
		};

	struct TextAttr TopazAttr =
		{(STRPTR)"topaz.font",TOPAZ_EIGHTY,0,0};


/******************************************************************
 *
 *  OpenDisplay()
 *
 ******************************************************************/

long OpenDisplay(long DisplayType)
{
	long	Ret_OK;

	struct NewScreen ScreenPars =
	{
		0, 0,		/* LeftEdge, TopEdge        */
		NULL, NULL,	/* Set screen size later    */
		NULL,		/* Set depth later          */
		2, 7,		/* DetailPen, BlockPen      */
		NULL,		/* Set display-mode later   */
		CUSTOMSCREEN,	/* SCREENQUIET ?            */
		NULL,		/* Set font later           */
		"RayLab 1.0 display",	/* Title            */
		NULL,		/* No special gadgets       */
		NULL		/* Nu custom bitmap         */
	};

	struct NewWindow WindowPars =
	{
		0, SCREEN_TITLE_HEIGHT, /* LeftEdge, TopEdge*/
		NULL, NULL,	/* Set screen size later    */
		3, 7,		/* DetailPen, BlockPen      */
		MENUPICK,	/* IDCMP                    */
		SMART_REFRESH | BORDERLESS | NOCAREREFRESH | ACTIVATE,
		NULL,		/* No special gadgets       */
		NULL,		/* No checkmark             */
		NULL,		/* No window title          */
		NULL,		/* Set screen later         */
		NULL,		/* No bitmap                */
		NULL, NULL,	/* Set min size later       */
		NULL, NULL,	/* Set max size later       */
		CUSTOMSCREEN
	};


	RL_Screen = NULL; RL_Window = NULL;

	(void) onbreak((int (*)(void))BreakHandler);	/* Install break-handler */

	IntBase = (struct IntuitionBase *)
		OpenLibrary("intuition.library",33);

	if(IntBase==NULL) {
		fprintf(stderr,"Error: Could not open intuition.library; no display possible\n\n");
		IntBase = NULL;
	}
	else {
		GfxBase = (struct GfxBase *)
			OpenLibrary("graphics.library",33);
		if(GfxBase==NULL) {
			fprintf(stderr,"Error: Could not open graphics.library; no display possible\n\n");
			CloseLibrary((struct Library *)IntBase);
			IntBase = NULL; GfxBase = NULL;
		}
	}

	if( (IntBase!=NULL) && (GfxBase!=NULL) ) {
	    if( (DisplayType>DISPLAY_NONE) && (DisplayType<=DISPLAY_GRAY) ) {
		CurrentDisplayType = DisplayType;

		ScreenPars.Width = (SHORT)PicWidth;
		ScreenPars.Height = (SHORT)(PicHeight+SCREEN_TITLE_HEIGHT);
		ScreenPars.Font = &TopazAttr;

		WindowPars.Width = (SHORT)PicWidth;
		WindowPars.Height = (SHORT)PicHeight;
		WindowPars.MinWidth = WindowPars.MaxWidth = (SHORT)PicWidth;
		WindowPars.MinHeight = WindowPars.MaxHeight = (SHORT)PicHeight;

		switch(CurrentDisplayType) {
		    case DISPLAY_HAM6:
			ScreenPars.ViewModes = HAM;
			ScreenPars.Depth = 6;
			OpenAmigaScreen(&ScreenPars,&WindowPars,Ham6Palette,16L);
			break;

		    case DISPLAY_GRAY:
			ScreenPars.ViewModes = HIRES | LACE;
			ScreenPars.Depth = 4;
			OpenAmigaScreen(&ScreenPars,&WindowPars,GrayPalette,16L);
			break;

		    case DISPLAY_NONE:
			break;
		}
	    }
	    else {
		if(DisplayType!=DISPLAY_NONE)
		    fprintf(stderr,"Warning: Unsupported display type (%d), no display opened\n\n",DisplayType);
		CurrentDisplayType = DISPLAY_NONE;
	    }
	}
	else {
		CurrentDisplayType = DISPLAY_NONE;
	}

	Ret_OK = 0L;
	if(CurrentDisplayType!=DISPLAY_NONE) {
		Ret_OK = 1L;
	}

	return(Ret_OK);
}


/******************************************************************
 *
 *  CloseDisplay()
 *
 ******************************************************************/

void CloseDisplay(void)
{
	ULONG	done, signalmask, signals, class, code, menunum, itemnum;
	struct	IntuiMessage *message;

	if((CurrentDisplayType!=0)&&(picturerendering!=0)) {
	    CloseAmigaScreen();
	}
	else if((CurrentDisplayType!=0)&&(picturerendering==0)) {
	    struct IntuiText ItemsText[] =
	    {
		{2,1,JAM1,2,1,NULL," Close",    NULL}
	    };

	    struct MenuItem Items[] =
	    {
		{
		NULL,		/* No more items            */
		0, 1, 110, 10,	/* Left, top, width, height */
		ITEMTEXT | COMMSEQ | ITEMENABLED | HIGHBOX,
		0,		/* MutualExclude            */
		NULL,		/* Set item text later      */
		NULL,
		'C',		/* Key equivalent           */
		NULL,		/* Sub item                 */
		MENUNULL	/* NextSelect               */
		}
	    };

	    struct Menu MenuPars =
	    {
		NULL,		/* No more menus            */
		0, 0, 120, 40,	/* Left, top, width, height */
		MENUENABLED,	/* Flags                    */
		"Action",	/* Menu name                */
		NULL		/* Set first item later     */
	    };

	    ItemsText[0].ITextFont = &TopazAttr;
	    Items[0].ItemFill = &ItemsText[0];
	    MenuPars.FirstItem = &Items[0];

	    SetMenuStrip(RL_Window, &MenuPars);	/* Add menu */


	    signalmask = 1L << RL_Window->UserPort->mp_SigBit;
	    done = 0L;

	    while( done == 0L ) {
		signals = Wait(signalmask);
		if((signals&signalmask)!=0L) {
		    while( message = (struct IntuiMessage *)GetMsg(RL_Window->UserPort) ) {
			class = message->Class;
			code = message->Code;
			ReplyMsg((struct Message *)message);
			if(class==MENUPICK) {
			    menunum = MENUNUM(code);
			    itemnum = ITEMNUM(code);
			    if(menunum==0L) {
				if(itemnum==0L) {
				    CloseAmigaScreen();
				    done = 1L;
				}
			    }
			}
		    }
		}
	    }
	}
}


/******************************************************************
 *
 *  DisplayPlot()
 *
 ******************************************************************/

void DisplayPlot(int x, int y, unsigned char RGBColor[])
{
	switch(CurrentDisplayType) {
	    case DISPLAY_NONE:
		break;

	    case DISPLAY_HAM6:
		SetAPen(RL_RastPort, ConvertColorToHam6(RGBColor,x,y));
		(void) WritePixel(RL_RastPort,x,y);
		break;

	    case DISPLAY_GRAY:
		SetAPen(RL_RastPort, ConvertColorToGray(RGBColor,x,y));
		(void) WritePixel(RL_RastPort,x,y);
		break;
	}
}


/******************************************************************

    This routine is called if CTRL-C is issued

 ******************************************************************/

int BreakHandler(void)
{
	fprintf(stderr,"\n*** The execution of RayLab was terminated by the user\n");
	cleanup();
	return(1);	/* Exit RayLab after this */
}


/******************************************************************

    Some routines for converting 24-bit rgb colors to apropriate
    display-modes

 ******************************************************************/


UBYTE ConvertColorToHam6(unsigned char RGBColor[], int x, int y) {
	UBYTE	New12BitColor[3];
	UBYTE	HamMode, HamCol;
	long	Diff12BitColor[3];
	register long RGBDiff, t1, t2, t3, t4, i, BestColor;

	if(x==0) {
		t1 = Ham6Palette[0];
		Last12BitColor[0] = (t1&0xf00)>>8;
		Last12BitColor[1] = (t1&0x0f0)>>4;
		Last12BitColor[2] = t1&0x00f;
	}

/* When dithering, it seems best to use the 24-bit color for comparsion: */

	Diff12BitColor[0] = (long)RGBColor[0]-(long)(Last12BitColor[0]<<4);
	Diff12BitColor[1] = (long)RGBColor[1]-(long)(Last12BitColor[1]<<4);
	Diff12BitColor[2] = (long)RGBColor[2]-(long)(Last12BitColor[2]<<4);
	Diff12BitColor[0] *= Diff12BitColor[0];
	Diff12BitColor[1] *= Diff12BitColor[1];
	Diff12BitColor[2] *= Diff12BitColor[2];
	HamMode = HAM_MODE_RED;
	if(Diff12BitColor[1]>Diff12BitColor[0]) {
		HamMode = HAM_MODE_GRN;
		if(Diff12BitColor[2]>Diff12BitColor[1]) {
			HamMode = HAM_MODE_BLU;
		}
	}
	else {
		if(Diff12BitColor[2]>Diff12BitColor[0]) {
			HamMode = HAM_MODE_BLU;
		}
	}
	Diff12BitColor[0] = Diff12BitColor[0]>>8;
	Diff12BitColor[1] = Diff12BitColor[1]>>8;
	Diff12BitColor[2] = Diff12BitColor[2]>>8;

	MG_Dither24(New12BitColor,RGBColor,x,y);

	switch(HamMode) {
	    case HAM_MODE_RED:
		HamCol = Last12BitColor[0] = New12BitColor[0];
		RGBDiff = Diff12BitColor[1] + Diff12BitColor[2];
		break;
	    case HAM_MODE_GRN:
		HamCol = Last12BitColor[1] = New12BitColor[1];
		RGBDiff = Diff12BitColor[0] + Diff12BitColor[2];
		break;
	    case HAM_MODE_BLU:
		HamCol = Last12BitColor[2] = New12BitColor[2];
		RGBDiff = Diff12BitColor[0] + Diff12BitColor[1];
		break;
	}

	BestColor = 100;
	for(i=0;i<16;i++) {
		t1 = (long)Ham6Palette[i];
		t2 = ((t1&0xf00)>>8) - (long)New12BitColor[0]; t2 *= t2;
		t3 = ((t1&0x0f0)>>4) - (long)New12BitColor[1]; t3 *= t3;
		t4 = (t1&0x00f)      - (long)New12BitColor[2]; t4 *= t4;
		t1 = t2+t3+t4;
		if(RGBDiff>t1) {
			RGBDiff = t1;
			BestColor = i;
		}
	}
	if(BestColor<16) {
		HamMode = HAM_MODE_PAL;
		HamCol  = (UBYTE)BestColor;
		t1 = Ham6Palette[BestColor];
		Last12BitColor[0] = (t1&0xf00)>>8;
		Last12BitColor[1] = (t1&0x0f0)>>4;
		Last12BitColor[2] = t1&0x00f;
	}

	return((UBYTE)(HamMode | (HamCol&0x0f)));
}



UBYTE ConvertColorToGray(unsigned char RGBColor[], int x, int y)
{
	UBYTE	Gray;

	Gray = (UBYTE)(((UWORD)RGBColor[0]+(UWORD)RGBColor[1]+(UWORD)RGBColor[2])/(UWORD)3);
	return(MG_Dither8(Gray,x,y));
}



/* This is some strange dithering method that */
/* I managed to produce late one night        */

UBYTE MG_Dither8(UBYTE Value, int x, int y)
{
	UBYTE	Dithered;

	Dithered = Value>>4;
	if(Dithered<15) {
	    if(((0x01<<(((y<<2)+x+DitherYTable[y&0x000f])&0x000f))&DitherMask[Value&0x0f])!=0) Dithered++;
	}
	return(Dithered);
}


void MG_Dither24(UBYTE RGBColor12[], unsigned char RGBColor24[], int x, int y)
{
	register int	Dither;

	RGBColor12[0] = (RGBColor24[0]>>4)&0x0f;
	RGBColor12[1] = (RGBColor24[1]>>4)&0x0f;
	RGBColor12[2] = (RGBColor24[2]>>4)&0x0f;
	Dither = 0x01<<(((y<<2)+x+DitherYTable[y&0x000f])&0x000f);
	if((RGBColor12[0]<15)&(Dither&DitherMask[RGBColor24[0]&0x0f])!=0) RGBColor12[0]++;
	if((RGBColor12[1]<15)&(Dither&DitherMask[RGBColor24[1]&0x0f])!=0) RGBColor12[1]++;
	if((RGBColor12[2]<15)&(Dither&DitherMask[RGBColor24[2]&0x0f])!=0) RGBColor12[2]++;
}


/*
   A routine for simplifying the opening of Amiga screens and windows
*/

void OpenAmigaScreen(struct NewScreen *ScreenPars,struct NewWindow *WindowPars, UWORD Palette[], ULONG NumCols)
{
	RL_Screen = (struct Screen *)OpenScreen(ScreenPars);
	if(RL_Screen!=NULL) {
	    WindowPars->Screen = RL_Screen;
	    RL_Window = (struct Window *)OpenWindow(WindowPars);
	    if(RL_Window!=NULL) {
		RL_RastPort = RL_Window->RPort;
		RL_ViewPort = &(RL_Screen->ViewPort);
		LoadRGB4(RL_ViewPort,Palette,NumCols);
		SetDrMd(RL_RastPort, JAM1);
	    }
	    else {
		RL_RastPort = NULL;
		fprintf(stderr,"Error: Could not open display window\n\n");
		CurrentDisplayType = DISPLAY_NONE;
		CloseScreen(RL_Screen);
		RL_Screen = NULL; RL_Window = NULL;
	    }
	}
	else {
	    fprintf(stderr,"Error: Could not open display screen\n\n");
	    CurrentDisplayType = DISPLAY_NONE;
	    RL_Screen = NULL; RL_Window = NULL;
	}
}


void CloseAmigaScreen(void)
{
	CurrentDisplayType = DISPLAY_NONE;
	if(RL_Window!=NULL) {
		if(RL_Window->MenuStrip)
			ClearMenuStrip(RL_Window);	/* Remove menu  */
		CloseWindow(RL_Window);		/* Close window */
		RL_Window = NULL;
	}
	if(RL_Screen!=NULL) {
		CloseScreen(RL_Screen);		/* Close screen */
		RL_Screen = NULL;
	}
	if(GfxBase!=NULL) {
		CloseLibrary((struct Library *)GfxBase);  /* Close graphics.library */
		GfxBase = NULL;
	}
	if(IntBase!=NULL) {
		CloseLibrary((struct Library *)IntBase);  /* Close intuition.library */
		IntBase = NULL;
	}
}
