/* 
 * UAE - The Un*x Amiga Emulator
 *
 * Amiga (native) specific support code; compiles with SAS/C 6.56,
 * to get CyberGraphX support, add "DEFINE=_CYBERGFX" to the compiler
 * flags in the smakefile. When running, hold down both Amiga keys
 * to quit the emulation. The emulation also responds to ^C, which
 * will abort it and to ^D which will reset it. Take care, the code
 * appears to be very brittle and collapses easily if you switch
 * screens. With CyberGraphX support enabled, the screen setup tries
 * to be smart and falls back to standard screen update techniques
 * if the screen to be opened does not belong to CyberGraphX. The
 * 15/16/24 bit colour modes require that a graphics card like the
 * CyberVision64 is installed that's not subject to byte ordering
 * problems.
 * 
 * (c) 1996 Olaf `Olsen' Barthel
 */

#include <intuition/intuitionbase.h>
#include <intuition/pointerclass.h>

#include <graphics/gfxbase.h>
#include <graphics/displayinfo.h>

#include <libraries/asl.h>

#include <exec/memory.h>

#include <dos/dos.h>
#include <dos/dosasl.h>

#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>

#include <pragmas/intuition_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/asl_pragmas.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#ifdef _CYBERGFX
#include <cybergraphics/cybergraphics.h>
#include <clib/cybergraphics_protos.h>
#include <pragmas/cybergraphics_pragmas.h>
#endif

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

#include "custom.h"
#include "options.h"
#include "keybuf.h"
#include "autoconf.h"
#include "xwin.h"

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

extern struct ExecBase		*SysBase;
extern struct DosLibrary	*DOSBase;

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct Library		*AslBase;
struct Library		*CyberGfxBase;

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

STATIC struct Screen	*Screen;
STATIC struct Window	*Window;
STATIC Object		*Pointer;
STATIC ULONG		 WindowMask;

STATIC struct RastPort	*TempRPort,
			*RenderPort;
STATIC struct BitMap	*BitMap;
STATIC UBYTE		*Line;

STATIC LONG		 ScreenHeight,
			 ScreenWidth;

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

int buttonstate[3];
int lastmx, lastmy;
int newmousecounters;
long int xcolors[4096];
struct vidbuf_description gfxvidinfo;

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

void m68k_reset(void);

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

STATIC VOID
AmigaCleanup(VOID)
{
	if(BitMap)
	{
		WaitBlit();
		FreeBitMap(BitMap);
		BitMap = NULL;
	}

	if(TempRPort)
	{
		FreeVec(TempRPort);
		TempRPort = NULL;
	}

	if(Line)
	{
		FreeVec(Line);
		Line = NULL;
	}

	if(Window)
	{
		CloseWindow(Window);
		Window = NULL;
	}

	if(Pointer)
	{
		DisposeObject(Pointer);
		Pointer = NULL;
	}

	if(Screen)
	{
		CloseScreen(Screen);
		Screen = NULL;
	}

	if(AslBase)
	{
		CloseLibrary(AslBase);
		AslBase = NULL;
	}

	if(GfxBase)
	{
		CloseLibrary(GfxBase);
		GfxBase = NULL;
	}

	if(IntuitionBase)
	{
		CloseLibrary(IntuitionBase);
		IntuitionBase = NULL;
	}

	if(CyberGfxBase)
	{
		CloseLibrary(CyberGfxBase);
		CyberGfxBase = NULL;
	}
}

void
flush_line(int y)
{
	if(!CyberGfxBase)
	{
		if(y >= 0 && y < ScreenHeight)
		{
			char *linebuf = y * gfxvidinfo.rowbytes + gfxvidinfo.bufmem;

			CopyMem(linebuf,Line,ScreenWidth);
			WritePixelLine8(RenderPort,0,y,ScreenWidth,Line,TempRPort);
		}
	}
}

void
flush_block(int ystart,int ystop)
{
	if(!CyberGfxBase)
	{
		if(ystart < 0)
			ystart = 0;
		else
		{
			if(ystart >= ScreenHeight)
				ystart = ScreenHeight - 1;
		}

		if(ystop < 0)
			ystop = 0;
		else
		{
			if(ystop >= ScreenHeight)
				ystop = ScreenHeight - 1;
		}

		if(ystart <= ystop)
		{
			char *mem;
			int i;

			mem = ystart * gfxvidinfo.rowbytes + gfxvidinfo.bufmem;

			for(i = ystart ; i <= ystop ; i++, mem += gfxvidinfo.rowbytes)
			{
				CopyMem(mem,Line,ScreenWidth);
				WritePixelLine8(RenderPort,0,i,ScreenWidth,Line,TempRPort);
			}
		}
	}
}

void
flush_screen(int ystart,int ystop)
{
}

static int
get_color(int r,int g,int b,long *cnp)
{
	ULONG Red,Green,Blue;
	LONG Pen;

	Red	= 0x11111111 * r;
	Green	= 0x11111111 * g;
	Blue	= 0x11111111 * b;

	Pen = ObtainBestPen(Screen->ViewPort.ColorMap,Red,Green,Blue,
		OBP_Precision,PRECISION_IMAGE,
	TAG_DONE);

	if(Pen == -1)
		return(0);
	else
	{
		*cnp = Pen;

		return(1);
	}
}

int
graphics_init(void)
{
	struct ScreenModeRequester *ScreenRequest;
	ULONG DisplayID;
	struct BitMap PointerBitMap;
	UWORD PointerLine;
	struct DimensionInfo Dims;
	BOOL GotOne;
	LONG Depth;
	int vsize;

	atexit(AmigaCleanup);

	vsize = correct_aspect ? 2*numscrlines : numscrlines;

	if(!(gfxvidinfo.bufmem = malloc(800 * vsize)))
	{
		fprintf(stderr,"Sorry guv'nor, not enough memory for the line buffer.\n");
		return(0);
	}

	memset(gfxvidinfo.bufmem,0,800 * vsize);

	gfxvidinfo.rowbytes		= 800;
	gfxvidinfo.pixbytes		= 1;
	gfxvidinfo.maxblocklines	= 100;
        gfxvidinfo.maxline = 100000; /* ??? */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",39)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to open intuition.library v39.\n");
		return(0);
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",39)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to open graphics.library v39.\n");
		return(0);
	}

	DisplayID = INVALID_ID;
	GotOne = FALSE;

	while((DisplayID = NextDisplayInfo(DisplayID)) != INVALID_ID)
	{
		if(GetDisplayInfoData(NULL,(APTR)&Dims,sizeof(Dims),DTAG_DIMS,DisplayID))
		{
			if(Dims.MaxDepth >= 8)
			{
				GotOne = TRUE;
				break;
			}
		}
	}

	if(!GotOne)
	{
		fprintf(stderr,"Sorry guv'nor, your Amiga needs an AA chip set or a graphics card.\n");
		return(0);
	}

	if(!(AslBase = OpenLibrary("asl.library",38)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to open asl.library v38.\n");
		return(0);
	}

	if(!(ScreenRequest = AllocAslRequest(ASL_ScreenModeRequest,NULL)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to allocate screen mode requester.\n");
		return(0);
	}

	if(AslRequestTags(ScreenRequest,
		ASLSM_TitleText,	"Select screen display mode",
		ASLSM_MinDepth,		8,
	TAG_DONE))
	{
		DisplayID = ScreenRequest->sm_DisplayID;

		GetDisplayInfoData(NULL,(APTR)&Dims,sizeof(Dims),DTAG_DIMS,DisplayID);

		Depth = Dims.MaxDepth;

		if(Depth > 8)
			color_mode = 1;
	}
	else
		DisplayID = INVALID_ID;

	FreeAslRequest(ScreenRequest);

	CloseLibrary(AslBase);
	AslBase = NULL;

	if(DisplayID == INVALID_ID)
		return(0);

	if(!(Screen = OpenScreenTags(NULL,
		SA_DisplayID,	DisplayID,
		SA_Depth,	Depth,
		SA_Interleaved,	TRUE,
		SA_ShowTitle,	FALSE,
		SA_BackFill,	LAYERS_NOBACKFILL,
		SA_Quiet,	TRUE,
		SA_Behind,	TRUE,
		SA_SharePens,	TRUE,
		SA_Exclusive,	TRUE,
		SA_Draggable,	FALSE,
	TAG_DONE)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to open the screen.\n");
		return(0);
	}

	ScreenWidth	= Screen->Width;
	ScreenHeight	= Screen->Height;

	if(ScreenWidth > 800)
		ScreenWidth = 800;

	if(ScreenHeight > 600)
		ScreenHeight = 600;

#ifdef _CYBERGFX
	if(CyberGfxBase = OpenLibrary("cybergraphics.library",40))
	{
		if(!IsCyberModeID(DisplayID) || ScreenHeight < vsize)
		{
			CloseLibrary(CyberGfxBase);
			CyberGfxBase = NULL;
		}
	}
#endif

	if(!(BitMap = AllocBitMap(Screen->Width,1,8,BMF_CLEAR|BMF_MINPLANES,Screen->RastPort.BitMap)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to allocate BitMap.\n");
		return(0);
	}

	if(!(TempRPort = AllocVec(sizeof(struct RastPort),MEMF_ANY|MEMF_PUBLIC)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to allocate RastPort.\n");
		return(0);
	}

	if(!(Line = AllocVec((Screen->Width + 15) & ~15,MEMF_ANY|MEMF_PUBLIC)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to allocate raster buffer.\n");
		return(0);
	}

	CopyMem(&Screen->RastPort,TempRPort,sizeof(struct RastPort));
	TempRPort->Layer = NULL;
	TempRPort->BitMap = BitMap;

	PointerLine = 0x0000;

	InitBitMap(&PointerBitMap,2,16,1);
	PointerBitMap.Planes[0] = (PLANEPTR)&PointerLine;
	PointerBitMap.Planes[1] = (PLANEPTR)&PointerLine;

	if(!(Pointer = NewObject(NULL,POINTERCLASS,
		POINTERA_BitMap,	&PointerBitMap,
		POINTERA_WordWidth,	1,
	TAG_DONE)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to allocate blank mouse pointer.\n");
		return(0);
	}

	if(!(Window = OpenWindowTags(NULL,
		WA_Width,		Screen->Width,
		WA_Height,		Screen->Height,
		WA_CustomScreen,	Screen,
		WA_Backdrop,		TRUE,
		WA_Borderless,		TRUE,
		WA_BackFill,		LAYERS_NOBACKFILL,
		WA_RMBTrap,		TRUE,
		WA_IDCMP,		IDCMP_RAWKEY|IDCMP_MOUSEBUTTONS|IDCMP_MOUSEMOVE,
		WA_Pointer,		Pointer,
		WA_ReportMouse,		TRUE,
	TAG_DONE)))
	{
		fprintf(stderr,"Sorry guv'nor, unable to open the window.\n");
		return(0);
	}

	RenderPort = &Screen->RastPort;

	WindowMask = 1UL << Window->UserPort->mp_SigBit;

	SetRast(RenderPort,0);

	switch(Depth)
	{
		case 8:

			alloc_colors256(get_color);
			break;

		case 15:

			alloc_colors64k(5,5,5,10,5,0);
			break;

		case 16:

			alloc_colors64k(5,6,5,11,5,0);
			break;

		case 24:
		case 32:

			alloc_colors64k(8,8,8,16,8,0);
			break;
	}

	ScreenToFront(Screen);
	ActivateWindow(Window);

	buttonstate[0] = buttonstate[1] = buttonstate[2] = 0;

	lastmx = lastmy = 0;
	newmousecounters = 0;

#ifdef _CYBERGFX
	if(CyberGfxBase)
	{
		free(gfxvidinfo.bufmem);

		gfxvidinfo.bufmem	= (char *)GetCyberMapAttr(Screen->RastPort.BitMap,CYBRMATTR_DISPADR);
		gfxvidinfo.rowbytes	= GetCyberMapAttr(Screen->RastPort.BitMap,CYBRMATTR_XMOD);
		gfxvidinfo.pixbytes	= GetCyberMapAttr(Screen->RastPort.BitMap,CYBRMATTR_BPPIX);
	}
#endif
	return(1);
}

void
graphics_leave(void)
{
	AmigaCleanup();
}

void
handle_events(void)
{
	ULONG Mask;

	Mask = SetSignal(0,SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D|WindowMask);

	if(Mask & SIGBREAKF_CTRL_C)
	{
		PrintFault(ERROR_BREAK,"uae");
		exit(RETURN_WARN);
	}

	if(Mask & SIGBREAKF_CTRL_D)
		m68k_reset();

	if(Mask & WindowMask)
	{
		struct IntuiMessage *IMsg;
		ULONG MsgClass;
		UWORD MsgCode;
		WORD MsgX,MsgY;
		WORD Button;
		BOOL Quit;

		newmousecounters = 0;
		Quit = FALSE;

		while(IMsg = (struct IntuiMessage *)GetMsg(Window->UserPort))
		{
			MsgClass	= IMsg->Class;
			MsgCode		= IMsg->Code;
			MsgX		= IMsg->MouseX;
			MsgY		= IMsg->MouseY;

			if((IMsg->Qualifier & (IEQUALIFIER_LCOMMAND|IEQUALIFIER_RCOMMAND)) == (IEQUALIFIER_LCOMMAND|IEQUALIFIER_RCOMMAND))
				Quit = TRUE;

			ReplyMsg(IMsg);

			switch(MsgClass)
			{
				case IDCMP_RAWKEY:

					if(MsgCode & IECODE_UP_PREFIX)
						record_key(((MsgCode & ~IECODE_UP_PREFIX) << 1) | 1);
					else
						record_key((MsgCode) << 1);

					break;

				case IDCMP_MOUSEBUTTONS:

					switch(MsgCode & ~IECODE_UP_PREFIX)
					{
						case IECODE_LBUTTON:

							Button = 0;
							break;

						case IECODE_MBUTTON:

							Button = 1;
							break;

						case IECODE_RBUTTON:

							Button = 2;
							break;
					}

					if(MsgCode & IECODE_UP_PREFIX)
						buttonstate[Button] = 0;
					else
						buttonstate[Button] = 1;

					break;

				case IDCMP_MOUSEMOVE:

					lastmx = MsgX;
					lastmy = MsgY;

					newmousecounters = 1;

					break;
			}
		}

		if(Quit)
			exit(RETURN_OK);
	}
}

int
debuggable(void)
{
	return 1;
}

int
needmousehack(void)
{
	return 1;
}

void
LED(int on)
{
}

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

int quit_program;

int
gui_init(void)
{
	quit_program = 0;
}

void
gui_exit(void)
{
}

void
gui_led(int led, int on)
{
}

void
gui_filename(int num, char *name)
{
}

void
gui_handle_events(void)
{
}

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

int	opterr = 1;			/* error => print message */
int	optind = 1;			/* next argv[] index */
char	*optarg = NULL;			/* option parameter if any */

static int
Err( name, mess, c )			/* returns '?' */
	char	*name;			/* program name argv[0] */
	char	*mess;			/* specific message */
	int	c;			/* defective option letter */
	{
	if ( opterr )
		(void) fprintf( stderr,
				"%s: %s -- %c\n",
				name, mess, c
			      );

	return '?';			/* erroneous-option marker */
	}

int
getopt( argc, argv, optstring )		/* returns letter, '?', EOF */
	int		argc;		/* argument count from main */
	char		*argv[];	/* argument vector from main */
	char		*optstring;	/* allowed args, e.g. "ab:c" */
	{
	static int	sp = 1;		/* position within argument */
	register int	osp;		/* saved `sp' for param test */
	register int	c;		/* option letter */
	register char	*cp;		/* -> option in `optstring' */

	optarg = NULL;

	if ( sp == 1 )			/* fresh argument */
		if ( optind >= argc		/* no more arguments */
		  || argv[optind][0] != '-'	/* no more options */
		  || argv[optind][1] == '\0'	/* not option; stdin */
		   )
			return EOF;
		else if ( strcmp( argv[optind], "--" ) == 0 )
			{
			++optind;	/* skip over "--" */
			return EOF;	/* "--" marks end of options */
			}

	c = argv[optind][sp];		/* option letter */
	osp = sp++;			/* get ready for next letter */

	if ( argv[optind][sp] == '\0' )	/* end of argument */
		{
		++optind;		/* get ready for next try */
		sp = 1;			/* beginning of next argument */
		}

	if ( c == ':'			/* optstring syntax conflict */
	  || (cp = strchr( optstring, c )) == NULL	/* not found */
	   )
		return Err( argv[0], "illegal option", c );

	if ( cp[1] == ':' )		/* option takes parameter */
		{
		if ( osp != 1 )
			return Err( argv[0],
				    "option must not be clustered",
				    c
				  );

		if ( sp != 1 )		/* reset by end of argument */
			return Err( argv[0],
			       "option must be followed by white space",
				    c
				  );

		if ( optind >= argc )
			return Err( argv[0],
				    "option requires an argument",
				    c
				  );

		optarg = argv[optind];	/* make parameter available */
		++optind;		/* skip over parameter */
		}

	return c;
	}

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

static void usage(void)
{
    printf("UAE - The Un*x Amiga emulator\n");
    printf("Summary of command-line options:\n");
    printf("  -h                       : Print help\n");
    printf("  -m VOLNAME:mount_point   : mount file system at <mount point> as AmigaDOS\n"
	   "                             volume VOLNAME:\n");
    printf("  -M VOLNAME:mount_point   : like -m, but mount read-only\n");
    printf("  -a                       : Don't mount the harddisk file automatically.\n"
	   "                             Useful only for testing.\n");
    printf("  -S                       : Turn off sound support (if it is configured)\n");
    printf("  -s                       : Emulate 1 MB slow memory at 0xC00000\n");
    printf("  -F n                     : Emulate n MB fast memory at 0x200000\n");
    printf("  -f n                     : Set the frame rate to 1/n\n");
    printf("  -x                       : Use visible cross-hair cursor (X Windows version)\n");
    printf("  -D                       : Start up the built-in debugger\n");
    printf("  -[0123] file             : Use file instead of df[0123].adf as disk image file\n");
    printf("  -r file                  : Use file as ROM image instead of kick.rom\n");
    printf("  -J                       : Fake joystick emulation with the numeric pad.\n");
    printf("  -g                       : Turn on gfx-lib replacement.\n");
    printf("  -d mode                  : Select resolution with the mode parameter.\n");
    printf("  -H mode                  : Set the number of colors with the mode parameter.\n");
    printf("\n");
    printf("Valid resolutions: 0 (320x200); 1 (320x240); 2 (320x400); 3 (800x600);\n"
	   "                   4 (800x600, correct aspect)\n"
	   "Valid color modes: 0 (256 colors); 1 (32768 colors); 2 (65536 colors)\n"
	   "UAE may choose to ignore the color mode/resolution setting.\n");
}

void parse_cmdline(int argc, char **argv)
{
extern ULONG fastmem_size;

    int c;

    while(((c = getopt(argc, argv, "l:Df:gd:hxF:asSJm:M:0:1:2:3:r:H:")) != EOF)) switch(c) {
     case 'h': usage();	exit(0);

     case '0': strncpy(df0, optarg, 255); df0[255] = 0;	break;
     case '1': strncpy(df1, optarg, 255); df1[255] = 0; break;
     case '2': strncpy(df2, optarg, 255); df2[255] = 0; break;
     case '3': strncpy(df3, optarg, 255); df3[255] = 0; break;
     case 'r': strncpy(romfile, optarg, 255); romfile[255] = 0; break;

     case 'm':
     case 'M':
	{
	    /* mount file system (repeatable)
	     * syntax: [-m | -M] VOLNAME:/mount_point
	     * example: -M CDROM:/cdrom -m UNIXFS:./disk
	     */
	    static int mount_seen = 0;
	    char buf[256];
	    char *s2;
	    int readonly = (c == 'M');

	    if (mount_seen)
		fprintf(stderr, "warning: multiple mounts confuse Kickstart 1.3\n");
	    mount_seen = 1;
	    strncpy(buf, optarg, 255); buf[255] = 0;
	    s2 = strchr(buf, ':');
	    if(s2) {
		*s2++ = '\0';
		add_filesys_unit(buf, s2, readonly);
	    } else {
		fprintf(stderr, "Usage: [-m | -M] VOLNAME:/mount_point\n");
	    }
	}
	break;

     case 'S': produce_sound = 0; break;
     case 'f': framerate = atoi(optarg); break;
     case 'x': use_xhair = 1; break;
     case 'D': use_debugger = 1; break;
     case 'J': fake_joystick = 1; break;
     case 'a': automount_uaedev = 0; break;
     case 's': use_slow_mem = 1; break;
     case 'g': use_gfxlib = 1; break;

     case 'F':
	fastmem_size = atoi(optarg) * 0x100000;
	if (fastmem_size != 0x100000 && fastmem_size != 0x200000
	    && fastmem_size != 0x400000 && fastmem_size != 0x800000)
	{
	    fastmem_size = 0;
	    fprintf(stderr, "Unsupported fastmem size!\n");
	}

	break;

     case 'd':
	screen_res = atoi(optarg);
	if (screen_res >= 0 && screen_res <= 4) {
	} else {
	    fprintf(stderr, "Bad video mode selected. Using default.\n");
	    screen_res = 3;
	}
	/*dont_want_aspect = screen_res != 4;*/
	use_lores = screen_res < 3;
	break;

     case 'H':
	color_mode = atoi(optarg);
	if (color_mode < 0 || color_mode > 2) {
	    fprintf(stderr, "Bad color mode selected. Using default.\n");
	    color_mode = 0;
	}
 	break;
    }
}
