/*
 *  This source copes with all amiga-specific stuff as opening the screen, resizing
 *  it using user copper lists, etc.
 *
 *  Copper-based resizing is now implemented, although no aspect ratio is taken care of.
 *
 *  HAM6 is supported now.
 *
 *  Michael Rausch  14-4-94  1:11:59
 *
 *  Some fixes to allow for non-default monitors
 *
 *  Miloslaw Smyk  21-2-96
 */

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/asl.h>

#include <cybergraphics/cgxvideo.h>
#include <pragmas/cgxvideo_pragmas.h>
#include <clib/cgxvideo_protos.h>

#include <libraries/asl.h>
#include <exec/memory.h>
#include <hardware/custom.h>
#include <graphics/copper.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <graphics/videocontrol.h>
#include <graphics/displayinfo.h>
#include <graphics/display.h>

#include <dos/dos.h>
#include <proto/dos.h>
#include <proto/Picasso96.h>
#include <proto/asyncio.h>

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

#include "video.h"
#include "proto.h"
extern int ditherType;
extern int fullFlag;

extern void HAM8_Init(struct RastPort *);	// kinda static
extern void HAM8_Init_lores(struct RastPort *);
extern void HAM6_Init_lores(struct RastPort *);

void (*HAM8_draw)(void *, int, int);


#define custom ((*(volatile struct Custom *)(0xdff000)))


struct IntuitionBase *IntuitionBase;
/* struct GfxBase *GfxBase; */
extern struct ExecBase *SysBase;
static struct Screen *screen;
static ULONG soerror = NULL;
int previous_x, previous_y;

int gfxver;
ULONG *kaiko = NULL;

int lores=TRUE, sdbl=TRUE, ham6=FALSE;

int max_x, max_y;

static struct ColorSpec firstblack[2]={ {0,0,0,0}, {-1, 0,0,0} };	/* Color 0 is 0x000*/


static void Quit(char *why, int failcode)
{
	puts(why);
	exit(failcode);
}

static void output_term(void)
{
	close_timer();

	if(input)
	{
		CloseAsync(input);
		input = NULL;
	}

	if (screen)
	{
		FreeVPortCopLists(&(screen->ViewPort));
		RemakeDisplay();

		if(cyber_window)
			CloseWindow(cyber_window);

		CloseScreen(screen);
	}

	if (IntuitionBase) CloseLibrary((struct Library *) IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *) GfxBase);
}


int get_ham_modeid(void)
{
	struct ScreenModeRequester *scrMdReq;
	int modeid = 0xffffffff;

	if(scrMdReq = AllocAslRequestTags(ASL_ScreenModeRequest,
																		ASLSM_MinWidth, 200,
																		ASLSM_MinHeight, 200,
																		ASLSM_MinDepth, 6,
																		ASLSM_MaxDepth, 8,
																		ASLSM_PropertyFlags, DIPF_IS_HAM,
																		ASLSM_PropertyMask, DIPF_IS_HAM,
																		TAG_DONE))
	{
		if(AslRequestTags(scrMdReq, TAG_DONE))
			modeid = scrMdReq->sm_DisplayID;

		FreeAslRequest(scrMdReq);
	}

	return(modeid);
}


void InitColorDisplay(void)
{
	atexit(output_term);

	if ((GfxBase=(struct GfxBase *) OpenLibrary("graphics.library",37))==NULL)
		Quit("graphics.library is too old, <V39",25);
	if ((IntuitionBase=(struct IntuitionBase *) OpenLibrary("intuition.library",37))==NULL)
		Quit("intuition.library is too old, <V39",25);

	gfxver = GfxBase->LibNode.lib_Version;
	if(gfxver>=40) kaiko = GfxBase->ChunkyToPlanarPtr;

	HAM8_draw = (void (*)(void *, int, int)) NoDitherImage;	// method casting ... argh!
	DoDitherImage = NoDitherImage;

	if((modeid == 0xffffffff) && ((modeid = get_ham_modeid()) == 0xffffffff))
		Quit("aMiPEG: unable to open HAM display...", 25);
}


/*
 *   Resize the display using a copper list. Nifty'n neat amiga feature.
 *
 *   Phew ... takes now 2 hours to fiddle the system-compliant custom modulo stuff.
 */
void ResizeDisplay(int w, int h)
{
	struct UCopList *ucoplist;
	static struct TagItem uCopTags[] = {
		{ VC_NoColorPaletteLoad, TRUE },
		{ VTAG_USERCLIP_SET, NULL },
		{ VTAG_END_CM, NULL }};
	int i,j,k, y, fp_each, locallores;
	struct CopList *dspins;
	struct CopIns *copins;
	short bpl1mod=-1, bpl2mod=-1, last1, last2, this1, this2;
	ULONG id, result ;
	struct DimensionInfo dim_info;
	BOOL quit = FALSE;
	struct IntuiMessage *msg;
	static char win_title[256];
	static VLH_x, VLH_y;
	static char first_time = TRUE;

	if(ditherType == NO_DITHER) return;

	if(ditherType == CYBERGFX_DITHER || ditherType == CYBERGFXGRAY_DITHER || ditherType == GRAY_DITHER || ditherType == CYBERGFXVLAYER_DITHER || ditherType == CYBERGFXVLAYERGRAY_DITHER || ditherType == PIV_DITHER)
	{
		if(ditherType == CYBERGFXVLAYER_DITHER || ditherType == CYBERGFXVLAYERGRAY_DITHER)
		{
			if(VLH && (VLH_x != w || VLH_y != h))
			{
				DetachVLayer(VLH);
				DeleteVLayerHandle(VLH);
				VLH = NULL;
			}

			if(!VLH && (VLH = CreateVLayerHandleTags(cyber_screen,
																			VOA_SrcType, SRCFMT_YCbCr16,
																			VOA_SrcWidth, w,
																			VOA_SrcHeight, h,
																			TAG_DONE)))
			{
				if(AttachVLayerTags(VLH, cyber_window, TAG_DONE))
					Quit("Unable to attach video layer to a window", 25);
			}
			else
				Quit("Unable to create a video layer", 25);

		}
		else if(ditherType == PIV_DITHER)
		{
			result = p96PIP_SetTags(cyber_window, P96PIP_SourceFormat, RGBFB_Y4U2V2,
						P96PIP_SourceWidth, w,
						P96PIP_SourceHeight, h,
						TAG_END);
			if(result = 0)
				Quit("Unable to create a video layer", 25);
		}

		sprintf(win_title, "%s [%dx%d]", FilePart(animname), w, h);
		SetWindowTitles(cyber_window, win_title, (UBYTE *)~0);

		if(!fullFlag && first_time)
		{
			first_time = FALSE;

			if(ditherType != PIV_DITHER && ditherType != CYBERGFXVLAYER_DITHER && ditherType != CYBERGFXVLAYERGRAY_DITHER)
			{
				ModifyIDCMP(cyber_window, IDCMP_NEWSIZE);

				ChangeWindowBox(cyber_window, cyber_window->LeftEdge, cyber_window->TopEdge,
												w + cyber_window->BorderLeft + cyber_window->BorderRight,
												h + cyber_window->BorderTop + cyber_window->BorderBottom);

				while(!quit)
				{
					WaitPort(cyber_window->UserPort);
					while(msg = (struct IntuiMessage *)GetMsg(cyber_window->UserPort))
					{
						if(msg->Class == IDCMP_NEWSIZE)
							quit = TRUE;

						ReplyMsg((struct Message *)msg);
					}
				}

				ModifyIDCMP(cyber_window, CLOSEWINDOW | MOUSEBUTTONS | VANILLAKEY);
			}
			else
			{
				ChangeWindowBox(cyber_window, cyber_window->LeftEdge, cyber_window->TopEdge,
												w + cyber_window->BorderLeft + cyber_window->BorderRight,
												h + cyber_window->BorderTop + cyber_window->BorderBottom);
			}

			original_x = cyber_window->Width;
			original_y = cyber_window->Height;
		}
		else
			if(!first_time)
			{
				original_x = w + cyber_window->BorderLeft + cyber_window->BorderRight,
				original_y = h + cyber_window->BorderTop + cyber_window->BorderBottom;
			}

		return;
	}

	if(previous_x == w && previous_y == h)
		return;

	if(screen)
	{
		FreeVPortCopLists(&(screen->ViewPort));
		RemakeDisplay();

		if(cyber_window)
			CloseWindow(cyber_window);

		CloseScreen(screen);
	}

	previous_x = w;
	previous_y = h;

	id = modeid & MONITOR_ID_MASK;

	if (GetDisplayInfoData(FindDisplayInfo(id), (UBYTE *)&dim_info, sizeof(struct DimensionInfo), DTAG_DIMS, 0))
		max_y=dim_info.StdOScan.MaxY - dim_info.StdOScan.MinY + 1;
	else
		max_y=200;

	if(h>max_y)
		sdbl=FALSE, max_y<<=1;

	if(!(GfxBase->ChipRevBits0 & (GFXF_AA_ALICE|GFXF_AA_LISA)))
		ham6 = TRUE;

	if(ham6)
		lores=TRUE;
	locallores = lores && (w<=160);
	max_x = (locallores ? 320 : 640);

	switch(id)
	{
		case A2024_MONITOR_ID:
			Quit("Get some colors, dude.", 25);

		case DBLPAL_MONITOR_ID: /* ARGH!  Kick their butts for this one! */
			if(sdbl)
				id = locallores ? DBLPALLORESHAMFF_KEY : DBLPALHIRESHAMFF_KEY;
			else
				id = locallores ? DBLPALLORESHAM_KEY : DBLPALHIRESHAM_KEY;
			break;

		case DBLNTSC_MONITOR_ID:
			if(sdbl)
				id = locallores ? DBLNTSCLORESHAMFF_KEY : DBLNTSCHIRESHAMFF_KEY;
			else
				id = locallores ? DBLNTSCLORESHAM_KEY : DBLNTSCHIRESHAM_KEY;
			break;

		case EURO72_MONITOR_ID:
			if(sdbl)
				id = locallores ? EURO72LORESHAMDBL_KEY : EURO72PRODUCTHAMDBL_KEY;
			else
				id = locallores ? EURO72LORESHAM_KEY : EURO72PRODUCTHAM_KEY;
			break;

		case VGA_MONITOR_ID:
			if(sdbl)
				id = locallores ? VGALORESHAMDBL_KEY : VGAPRODUCTHAMDBL_KEY;
			else
				id = locallores ? VGALORESHAM_KEY : VGAPRODUCTHAM_KEY;
			break;

		case PAL_MONITOR_ID:
		case NTSC_MONITOR_ID:
/*
			if(gfxver >= 40 && sdbl)
				id = locallores ? id | LORESHAMSDBL_KEY: id | HIRESHAMSDBL_KEY;
			else
*/
				sdbl=FALSE, id = locallores ? id | HAM_KEY : id | HIRESHAM_KEY;
			break;

		default:
			printf("ModeID 0x%x either doesn't support HAM or is unknown to aMiPEG.", id);
			Quit("", 10);
	}

	if(!(screen=OpenScreenTags(NULL,
		SA_DisplayID,	id,
		SA_Depth,	ham6?6:8,
		SA_Width,	max_x,
		SA_Colors,	firstblack,
		SA_Type,	CUSTOMSCREEN|SCREENQUIET,
		SA_Quiet, 	TRUE,
		SA_Interleaved,	!ham6, //TRUE,
		SA_Overscan,	OSCAN_STANDARD,
		SA_MinimizeISG,	TRUE,
		SA_ErrorCode,	&soerror,
		TAG_END))) Quit("Couldn't open screen.",25);

	if((screen->RastPort.BitMap->Depth == 6) && !ham6)
		Quit("Incorrect depth. Please try to specify \"-dither ham6\" explicitely.",25);

				if(cyber_window = OpenWindowTags(NULL,
									WA_CustomScreen, screen,
									WA_Flags, WFLG_ACTIVATE | WFLG_BORDERLESS | WFLG_BACKDROP | WFLG_RMBTRAP,
                	WA_IDCMP, VANILLAKEY,
      	          TAG_DONE))
      	          ;

	if(lores) {
		if(ham6)
		{	// the new ham6 routines do not handle interleaved bitmaps anymore
			HAM6_Init_lores(&(screen->RastPort));
			HAM8_draw = HAM6_draw_lores;
			DoDitherImage = ColorDitherImage_12bit;
		} else {
			HAM8_Init_lores(&(screen->RastPort));
			HAM8_draw = HAM8_draw_lores;
			DoDitherImage = ColorDitherImage_lores;	// lacks kaiko support, actually
		}
		max_x >>=1;

	} else {
		HAM8_Init(&(screen->RastPort));
		HAM8_draw = HAM8_draw_hires;
		DoDitherImage = ColorDitherImage;	// kaiko on one day
		max_x >>=2;
	}

	if(noDisplayFlag)
		HAM8_draw = (void (*)(void *, int, int)) NoDitherImage;	// method casting ... argh!


	/* the memory is freed upon exit in output_term via FreeVPortCopLists */
	if(!(ucoplist = AllocMem(sizeof(struct UCopList), MEMF_PUBLIC|MEMF_CLEAR)))
		Quit("No memory for copper list.",25);

	/* fiddle out some hardware values from this screen's init copper list */
	dspins=screen->ViewPort.DspIns;
	copins=dspins->CopIns;
	for(i=dspins->Count-1; i>=0; i--, copins++)
	{
		j = copins->DESTDATA;

		switch(copins->DESTADDR & 0xfff)	// argh! kick 2.1 messes this up!
		{		
			case (int)&((*(struct Custom *)(0)).bpl1mod):
				last1=bpl1mod = j;
				break;
			case (int)&((*(struct Custom *)(0)).bpl2mod):
				last2=bpl2mod = j;
				break;
		}
	}

	if(bpl1mod==-1 || bpl2mod==-1)
	{
		printf("ooops\n");
		return;	/* hmmm? */
	}

	if((bpl1mod == bpl2mod) && sdbl) sdbl=FALSE;

	y = screen->Height;
	(void) CINIT(ucoplist, y*3);		/* ... instructions per line */


	if(sdbl)
	{
		/*
		 *  We abuse some of AGA's features here; double-scanning is implemented
		 *  by applying bpl1mod to ALL planes on one line, bpl2mod on the next.
		 *  Obviously, double-scanning enables some kind of internal chipmem cache.
		 *  At least, it's faster. And (because of?) less copper instructions.
		 */

		fp_each = ((y*2)<<8) / (short)h;
		for(k=fp_each, j=0; j<y; j++)				/* for each line in the display */
		{

			if((k-=0x100)>=0x100)				/* we still gotta double the line */
				this1=bpl1mod;
			else						/* finally, we are ready; next one */
				this1=bpl2mod, k=(k&0xff)+fp_each;

			if((k-=0x100)>=0x100)				/* we still gotta double the line */
				this2=bpl1mod;
			else						/* finally, we are ready; next one */
				this2=bpl2mod, k=(k&0xff)+fp_each;

			if(last1!=this1 || last2!=this2)
				CWAIT(ucoplist, j, 0);

			if(last1!=this1)
			{
				CMOVE(ucoplist, custom.bpl1mod, this1);
				last1=this1;
			}

			if(last2!=this2)
			{
				CMOVE(ucoplist, custom.bpl2mod, this2);
				last2=this2;
			}
		}
	} else {
		/*
		 *  No scan-doubling possible; most probably because of a pal/ntsc screen 
		 *  and no V40 graphics library available.
		 */

		bpl1mod -= screen->RastPort.BitMap->BytesPerRow;

		fp_each = (y<<8) / (short)h;
		for(k=fp_each, j=0; j<y; j++)				/* for each line in the display */
		{

			if((k-=0x100)>=0x100)				/* we still gotta double the line */
				this1=bpl1mod;
			else						/* finally, we are ready; next one */
				this1=bpl2mod, k=(k&0xff)+fp_each;

			if(last1!=this1)
			{
				CWAIT(ucoplist, j, 0);
				CMOVE(ucoplist, custom.bpl1mod, this1);
				CMOVE(ucoplist, custom.bpl2mod, this1);
				last1=this1;
			}
		}
	}

	/*
	 *  Pretty nifty, isn't it? Finally, even a little bit copper-list-optimizing is build in!
	 */

	CEND(ucoplist);

	/* Set the freshly created user copper list */
	//Forbid();
	screen->ViewPort.UCopIns = ucoplist;
	//Permit();

	/*  Enable user copper list clipping for this ViewPort.  */
	(void) VideoControl( screen->ViewPort.ColorMap, uCopTags );

	RethinkDisplay();
}
