/*
**	MandelSquare - AmigaDOS 2.0/3.0 Mandelbrot set explorer
**
**	Double.c, Display double-buffering routines
**
**	Copyright © 1991-1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

	/* Double buffering info. */

struct DBI
{
	struct Screen		*Screen,		/* Current display. */
				*NewScreen;		/* Local display. */
	struct Window		*NewWindow;		/* Local window. */

	struct ScreenBuffer	*ScreenBuffer[2];	/* v39 double buffering info. */
	struct MsgPort		*ScreenPort;		/* v39 double buffering sync port. */

	struct BitMap		*OriginalBitMap;	/* Old display bitmap. */

	struct Palette		*OriginalPalette;	/* Original display palette. */
	struct Palette		*BlackPalette;		/* Paint it black. */

	struct BitMap		*BitMap[2];		/* v37 double buffering bitmaps. */

	struct timerequest	*TimeRequest;		/* Timer io for inter-frame delays. */
	struct MsgPort		*TimePort;
};

	/* Are we running under control of Kickstart 3.0 or higher? */

extern BYTE		Is39;

	/* Safe shared window closing routine. */

extern VOID		CloseWindowSafely(struct Window *Window);

	/* Bitmap allocation routines. */

extern VOID		FreeCustomBitMap(struct BitMap *BitMap);
extern struct BitMap *	AllocCustomBitMap(UWORD Depth,UWORD Width,UWORD Height);

	/* Palette management. */

extern VOID		FreePalette(struct Palette *Palette);
extern struct Palette *	AllocPalette(LONG NumColours,BYTE TrueColour);
extern struct Palette *	GetPalette(struct Screen *Screen,struct Palette *Palette);
extern VOID		LoadPalette(struct Screen *Screen,struct Palette *Palette,LONG NumColours);
extern LONG		GetPaletteSize(struct Palette *Palette);
extern BYTE		GetPaletteTriplet(struct Palette *Palette,UBYTE *Triplet,LONG Index);
extern BYTE		SetPaletteTriplet(struct Palette *Palette,UBYTE R,UBYTE G,UBYTE B,LONG Index);
extern ULONG		GetPaletteEntry(struct Palette *Palette,LONG Index);
extern BYTE		SetPaletteEntry(struct Palette *Palette,ULONG Entry,LONG Index);

	/* Local routines. */

VOID			FreeDBI(struct DBI *Info);
struct DBI *		AllocDBI(struct Screen *Screen);
VOID			SwapDBI(struct DBI *Info,BYTE Copy);
struct BitMap *		GetDBI(struct DBI *Info);
VOID			PaletteDBI(struct DBI *Info,struct Palette *Palette);
VOID			StartDBI(struct DBI *Info,ULONG Jiffies);
VOID			WaitDBI(struct DBI *Info);

	/* FreeDBI(struct DBI *Info):
	 *
	 *	Free double-buffering information.
	 */

VOID
FreeDBI(struct DBI *Info)
{
	WORD i;

		/* Release timer io request. */

	if(Info -> TimeRequest)
	{
		if(Info -> TimeRequest -> tr_node . io_Device)
			CloseDevice(Info -> TimeRequest);

		DeleteIORequest(Info -> TimeRequest);
	}

		/* Releae timer io reply port. */

	if(Info -> TimePort)
		DeleteMsgPort(Info -> TimePort);

		/* Turn the screen black before we change
		 * the bitmap.
		 */

	if(Info -> BlackPalette && !Info -> NewScreen)
	{
		LoadPalette(Info -> Screen,Info -> BlackPalette,0);

		FreePalette(Info -> BlackPalette);
	}

		/* Reinstall the original viewport bitmap. */

	if(Info -> OriginalBitMap)
	{
		Info -> Screen -> ViewPort . RasInfo -> BitMap = Info -> OriginalBitMap;

		MakeScreen(Info -> Screen);

		RethinkDisplay();
	}

		/* Release the display bitmaps. */

	for(i = 0 ; i < 2 ; i++)
	{
		if(Info -> BitMap[i])
			FreeCustomBitMap(Info -> BitMap[i]);
	}

		/* Free the double-buffering information. */

	for(i = 0 ; i < 2 ; i++)
	{
		if(Info -> ScreenBuffer[i])
			FreeScreenBuffer(Info -> NewScreen,Info -> ScreenBuffer[i]);
	}

		/* Reset the screen palette. */

	if(Info -> OriginalPalette && !Info -> NewScreen)
	{
		LoadPalette(Info -> Screen,Info -> OriginalPalette,0);

		FreePalette(Info -> OriginalPalette);
	}

		/* Release the double buffering sync port. */

	if(Info -> ScreenPort)
		DeleteMsgPort(Info -> ScreenPort);

		/* Close the window if any. */

	if(Info -> NewWindow)
		CloseWindowSafely(Info -> NewWindow);

		/* Bring the original screen to the front. */

	if(Info -> NewScreen)
	{
		ScreenToFront(Info -> Screen);

		CloseScreen(Info -> NewScreen);
	}

		/* Free the info buffer. */

	FreeVec(Info);
}

	/* AllocDBI(struct Screen *Screen):
	 *
	 *	Allocate double-buffering information handle.
	 */

struct DBI *
AllocDBI(struct Screen *Screen)
{
	struct DBI *Info;

		/* Allocate the buffer. */

	if(Info = (struct DBI *)AllocVec(sizeof(struct DBI),MEMF_ANY | MEMF_CLEAR))
	{
		Info -> Screen = Screen;

			/* Get the screen palette. */

		if(Info -> OriginalPalette = GetPalette(Screen,NULL))
		{
				/* Allocate blank palette. */

			if(Info -> BlackPalette = AllocPalette(GetPaletteSize(Info -> OriginalPalette),FALSE))
			{
				BYTE Success;
				WORD i;

					/* Are we running under control of
					 * Kickstart 3.0 or higher?
					 */

				if(Is39)
				{
					struct Rectangle	DisplayClip;
					ULONG			Mode;

						/* Just in case... */

					Success = FALSE;

						/* Obtain the main screen mode ID. */

					Mode = GetVPModeID(&Screen -> ViewPort);

						/* Query the overscan dimensions,
						 * we wish to center the screen
						 * on the display if possible.
						 */

					if(QueryOverscan(Mode,&DisplayClip,OSCAN_VIDEO))
					{
						LONG Differ;

							/* Determine intervening space. */

						Differ = ((DisplayClip . MaxX - DisplayClip . MinX + 1) - Screen -> Width) / 2;

							/* Adjust position. */

						DisplayClip . MinX += Differ;
						DisplayClip . MaxX -= Differ;

							/* Open local screen,
							 * don't mess with the
							 * original screen.
							 */

						if(Info -> NewScreen = OpenScreenTags(NULL,
							SA_Width,	Screen -> Width,
							SA_Height,	Screen -> Height,
							SA_DClip,	&DisplayClip,
							SA_Left,	DisplayClip . MinX,
							SA_Depth,	Screen -> RastPort . BitMap -> Depth,
							SA_DisplayID,	Mode,
							SA_Font,	Screen -> Font,
							SA_ShowTitle,	FALSE,
							SA_Quiet,	TRUE,
							SA_AutoScroll,	TRUE,
							SA_Behind,	TRUE,
						TAG_END))
						{
							struct Window *Window = Screen -> FirstWindow;

								/* The simple way to make sure that
								 * the screen remains black.
								 */

							LoadPalette(Info -> NewScreen,Info -> BlackPalette,0);

								/* Open a window our the local
								 * screen, just in case the user
								 * clicks the left mouse button.
								 */

							if(Info -> NewWindow = OpenWindowTags(NULL,
								WA_Width,	Screen -> Width,
								WA_Height,	Screen -> Height,
								WA_Borderless,	TRUE,
								WA_Backdrop,	TRUE,
								WA_RMBTrap,	TRUE,
								WA_CustomScreen,Info -> NewScreen,
							TAG_DONE))
							{
									/* Copy the input port. */

								Info -> NewWindow -> UserPort = Window -> UserPort;

									/* Install the standard main window IDCMP flags. */

								ModifyIDCMP(Info -> NewWindow,IDCMP_MOUSEBUTTONS | IDCMP_MOUSEMOVE | IDCMP_MENUPICK | IDCMP_RAWKEY | IDCMP_VANILLAKEY | IDCMP_MENUVERIFY);

									/* Clear the mouse pointer. */

								SetPointer(Info -> NewWindow,Window -> Pointer,1,16,0,0);

									/* Create the sync port. */

								if(Info -> ScreenPort = CreateMsgPort())
								{
									Success = TRUE;

										/* Allocate the double-buffering
										 * information.
										 */

									for(i = 0 ; Success && i < 2 ; i++)
									{
										if(Info -> ScreenBuffer[i] = AllocScreenBuffer(Info -> NewScreen,NULL,(i ? NULL : SB_SCREEN_BITMAP)))
										{
											Info -> ScreenBuffer[i] -> sb_DBufInfo -> dbi_DispMessage . mn_ReplyPort = Info -> ScreenPort;

											BltBitMap(Screen -> RastPort . BitMap,0,0,Info -> ScreenBuffer[i] -> sb_BitMap,0,0,Info -> Screen -> Width,Info -> Screen -> Height,0xC0,0xFF,NULL);
										}
										else
											Success = FALSE;
									}

										/* Bring the new screen to the front. */

									if(Success)
										ScreenToFront(Info -> NewScreen);
								}
							}
						}
					}
				}
				else
				{
					Success = TRUE;

						/* Allocate the double-buffering bitmaps. */

					for(i = 0 ; Success && i < 2 ; i++)
					{
						if(!(Info -> BitMap[i] = AllocCustomBitMap(Screen -> RastPort . BitMap -> Depth,Screen -> Width,Screen -> Height)))
							Success = FALSE;
					}
				}

					/* Successful so far? */

				if(Success)
				{
						/* Create the timer io reply port. */

					if(Info -> TimePort = CreateMsgPort())
					{
							/* Create the timer io request. */

						if(Info -> TimeRequest = (struct timerequest *)CreateIORequest(Info -> TimePort,sizeof(struct timerequest)))
						{
								/* Open timer.device. */

							if(!OpenDevice(TIMERNAME,UNIT_VBLANK,Info -> TimeRequest,0))
							{
									/* Remember the original viewport bitmap. */

								if(!Is39)
									Info -> OriginalBitMap = Screen -> ViewPort . RasInfo -> BitMap;

									/* Swap the on-screen bitmaps. */

								SwapDBI(Info,FALSE);

								return(Info);
							}
						}
					}
				}
			}
		}

		FreeDBI(Info);
	}

	return(NULL);
}

	/* SwapDBI(struct DBI *Info,BYTE Copy):
	 *
	 *	Swap the on-screen bitmaps.
	 */

VOID
SwapDBI(struct DBI *Info,BYTE Copy)
{
		/* Pre-v39 double-buffering? */

	if(Info -> OriginalBitMap)
	{
		struct BitMap *Swap;

			/* Install the `hidden' bitmap. */

		Info -> Screen -> ViewPort . RasInfo -> BitMap = Swap = Info -> BitMap[0];

			/* Rebuild screen copper list. */

		MakeScreen(Info -> Screen);

		RethinkDisplay();

			/* Swap the bitmaps. */

		Info -> BitMap[0] = Info -> BitMap[1];
		Info -> BitMap[1] = Swap;

			/* Copy the bitmap contents if necessary. */

		if(Copy)
			BltBitMap(Info -> BitMap[1],0,0,Info -> BitMap[0],0,0,Info -> Screen -> Width,Info -> Screen -> Height,0xC0,0xFF,NULL);
	}
	else
	{
		struct ScreenBuffer *Swap;

			/* Install the hidden `bitmap'. */

		ChangeScreenBuffer(Info -> NewScreen,Swap = Info -> ScreenBuffer[0]);

			/* Swap the bitmaps. */

		Info -> ScreenBuffer[0] = Info -> ScreenBuffer[1];
		Info -> ScreenBuffer[1] = Swap;

			/* Wait for bitmaps to get displayed. */

		WaitPort(Info -> ScreenPort);

		GetMsg(Info -> ScreenPort);

			/* Copy the bitmap contents if necessary. */

		if(Copy)
			BltBitMap(Info -> ScreenBuffer[1] -> sb_BitMap,0,0,Info -> ScreenBuffer[0] -> sb_BitMap,0,0,Info -> NewScreen -> Width,Info -> NewScreen -> Height,0xC0,0xFF,NULL);
	}
}

	/* GetDBI(struct DBI *Info):
	 *
	 *	Obtain the currently `hidden' bitmap.
	 */

struct BitMap *
GetDBI(struct DBI *Info)
{
	if(Info -> OriginalBitMap)
		return(Info -> BitMap[0]);
	else
		return(Info -> ScreenBuffer[0] -> sb_BitMap);
}

	/* PaletteDBI(struct DBI *Info,struct Palette *Palette):
	 *
	 *	Set the new colour palette.
	 */

VOID
PaletteDBI(struct DBI *Info,struct Palette *Palette)
{
	if(Info -> NewScreen)
		LoadPalette(Info -> NewScreen,Palette,0);
	else
		LoadPalette(Info -> Screen,Palette,0);
}

	/* StartDBI(struct DBI *Info,ULONG Jiffies):
	 *
	 *	Start frame delay counter (1 jiffy = 1/60 s).
	 */

VOID
StartDBI(struct DBI *Info,ULONG Jiffies)
{
	Info -> TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
	Info -> TimeRequest -> tr_time . tv_secs	= Jiffies / 60;
	Info -> TimeRequest -> tr_time . tv_micro	= ((Jiffies % 60) * 1000000) / 60;

	SendIO(Info -> TimeRequest);
}

	/* WaitDBI(struct DBI *Info):
	 *
	 *	Wait for timer to elapse.
	 */

VOID
WaitDBI(struct DBI *Info)
{
	WaitIO(Info -> TimeRequest);
}
