/*
**	Raster.c
**
**	Screen character (raster) buffering routines
**
**	Copyright © 1990-1997 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

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

#define PACK_STATE(Gfx,Attributes,Foreground,Background) (((Gfx)<<12)|((Attributes)<<8)|((Foreground)<<4)|(Background))

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

	/* DeleteRaster():
	 *
	 *	Free the contents of the character raster.
	 */

VOID
DeleteRaster()
{
	FreeVecPooled(Raster);
	Raster = NULL;

	FreeVecPooled(RasterAttr);
	RasterAttr = NULL;

	FreeVecPooled(AttributeRaster);
	AttributeRaster = NULL;
}

	/* CreateRaster():
	 *
	 *	Create the character raster.
	 */

BOOL
CreateRaster()
{
		/* Width of the screen * 2 (in characters),
		 * extra for double width. The window size
		 * may change, the screen size hopefully
		 * doesn't.
		 */

	RasterWidth		= (Window->WScreen->Width / TextFontWidth) * 2;

		/* Height of the character raster. */

	RasterHeight	= Window->WScreen->Height / TextFontHeight;

		/* Allocate the raster. */

	if(Raster = (STRPTR)AllocVecPooled(RasterWidth * RasterHeight,MEMF_ANY | MEMF_CLEAR))
	{
			/* Allocate the raster attributes. */

		if(RasterAttr = (STRPTR)AllocVecPooled(RasterHeight,MEMF_ANY | MEMF_CLEAR))
		{
			if(AttributeRaster = (UWORD *)AllocVecPooled(sizeof(UWORD) * RasterWidth * RasterHeight,MEMF_ANY | MEMF_CLEAR))
				return(TRUE);
		}
	}

	DeleteRaster();

	return(FALSE);
}

	/* RasterEraseScreen(BYTE Mode):
	 *
	 *	Erase parts of the screen.
	 */

VOID
RasterEraseScreen(LONG Mode)
{
	LONG First,Last;

	ObtainSemaphore(&RasterSemaphore);

	switch(Mode)
	{
		case 1:

			First	= 0;
			Last	= CursorY * RasterWidth + CursorX + 1;

			if(CursorY == LastLine)
				SaveRaster(0,CursorY);

			memset(RasterAttr,SCALE_NORMAL,CursorY + 1);

			break;

		case 2:

			First	= 0;
			Last	= RasterHeight * RasterWidth - 1;

			SaveRaster(0,RasterHeight - 1);

			memset(RasterAttr,SCALE_NORMAL,RasterHeight);

			break;

		default:

			First	= CursorY * RasterWidth + CursorX;
			Last	= RasterHeight * RasterWidth - 1;

			if(CursorY == 0)
				SaveRaster(CursorY,RasterHeight - 1);

			memset(&RasterAttr[CursorY],SCALE_NORMAL,RasterHeight - CursorY);

			break;
	}

	RethinkRasterLimit();

	if(Last > First)
	{
		LONG i,Value;

		memset(&Raster[First],' ',Last - First);

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		for(i = First ; i <= Last ; i++)
			AttributeRaster[i] = Value;
	}

	ConFontScaleUpdate();

	ReleaseSemaphore(&RasterSemaphore);
}

	/* RasterEraseLine(BYTE Mode):
	 *
	 *	Erase parts of the current cursor line.
	 */

VOID
RasterEraseLine(LONG Mode)
{
	LONG First,Last;

	ObtainSemaphore(&RasterSemaphore);

	/*	SaveRaster(CursorY,CursorY); */

	switch(Mode)
	{
			/* From beginning to current cursor position. */

		case 1:

			First	= CursorY * RasterWidth;
			Last	= First + CursorX + 1;

			break;

			/* Entire line. */

		case 2:

			First	= CursorY * RasterWidth;
			Last	= First + RasterWidth - 1;

			break;

			/* From current cursor position towards end. */

		default:

			First	= CursorY * RasterWidth + CursorX;
			Last	= (CursorY + 1) * RasterWidth - 1;

			break;
	}

	if(Last > First)
	{
		LONG i,Value;

		memset(&Raster[First],' ',Last - First);

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		for(i = First ; i <= Last ; i++)
			AttributeRaster[i] = Value;
	}

	ReleaseSemaphore(&RasterSemaphore);
}

	/* RasterEraseCharacters(LONG Chars):
	 *
	 *	Erase a number of characters in the current cursor
	 *	line.
	 */

VOID
RasterEraseCharacters(LONG Chars)
{
	if(CursorX < RasterWidth - 1)
	{
		LONG First,Diff;
		UBYTE *To,*From;
		LONG Value,i;

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

	/*		SaveRaster(CursorY,CursorY); */

		ObtainSemaphore(&RasterSemaphore);

		First	= CursorY * RasterWidth + CursorX;
		To		= &Raster[First];

		if(CursorX + Chars >= RasterWidth)
		{
			Diff = RasterWidth - 1 - CursorX;

			for(i = 0 ; i < Diff ; i++)
				AttributeRaster[First + i] = Value;

			memset(To,' ',Diff);
		}
		else
		{
			From = &Raster[First + Chars];
			Diff = RasterWidth - (CursorX + 1 + Chars);

			for(i = 0 ; i < Diff ; i++)
			{
				AttributeRaster[First + i] = AttributeRaster[First + Chars + i];
				AttributeRaster[First + Chars + i] = Value;
			}

			for(i = 0 ; i < Diff ; i++)
			{
				*To++ = *From;

				*From++ = ' ';
			}
		}

		ReleaseSemaphore(&RasterSemaphore);
	}
}

	/* RasterClearLine(LONG Lines):
	 *
	 *	Clear and remove a number of lines.
	 */

VOID
RasterClearLine(LONG Lines,LONG Top)
{
	if(Lines)
	{
		LONG RegionBottom;
		LONG Value,i;

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		ObtainSemaphore(&RasterSemaphore);

		if(RegionSet)
			RegionBottom = Bottom;
		else
			RegionBottom = LastLine + 1;

		if(Top + Lines >= RegionBottom + 1)
		{
			SaveRaster(Top,RegionBottom);

			Lines = RegionBottom - Top + 1;

			for(i = 0 ; i < RasterWidth * Lines ; i++)
				AttributeRaster[Top * RasterWidth + i] = Value;

			memset(&Raster[Top * RasterWidth],' ',RasterWidth * Lines);
			memset(&RasterAttr[Top],SCALE_NORMAL,Lines);
		}
		else
		{
			UBYTE *From,*To;
			LONG Max;

			SaveRaster(Top,Top + Lines - 1);

			Max	= (RegionBottom - (Top + Lines)) * RasterWidth;

			From	= &Raster[(Top + Lines) * RasterWidth];
			To		= &Raster[ Top          * RasterWidth];

			for(i = 0 ; i < Max ; i++)
			{
				AttributeRaster[Top * RasterWidth + i] = AttributeRaster[(Top + Lines) * RasterWidth + i];
				AttributeRaster[(Top + Lines) * RasterWidth + i] = Value;
			}

			for(i = 0 ; i < Max ; i++)
			{
				*To++ = *From;

				*From++ = ' ';
			}

			memset(&RasterAttr[RegionBottom - Lines],SCALE_NORMAL,Lines);
		}

		RethinkRasterLimit();

		ConFontScaleUpdate();

		ReleaseSemaphore(&RasterSemaphore);
	}
}

	/* RasterInsertLine(LONG Lines):
	 *
	 *	Insert a number of lines at the current cursor line.
	 */

VOID
RasterInsertLine(LONG Lines,LONG Top)
{
	if(Lines)
	{
		LONG RegionBottom;
		LONG Value,i;

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		if(RegionSet)
			RegionBottom = Bottom;
		else
			RegionBottom = LastLine + 1;

		if(Top + Lines >= RegionBottom + 1)
		{
			SaveRaster(Top,RegionBottom);

			Lines = RegionBottom - Top + 1;

			for(i = 0 ; i < RasterWidth * Lines ; i++)
				AttributeRaster[Top * RasterWidth + i] = Value;

			memset(&Raster[Top * RasterWidth],' ',RasterWidth * Lines);
			memset(&RasterAttr[Top],SCALE_NORMAL,Lines);
		}
		else
		{
			UBYTE *FromPtr,*ToPtr;
			LONG From,To,Max;

			SaveRaster(RegionBottom - Lines,RegionBottom);

			ObtainSemaphore(&RasterSemaphore);

			Max	= (RegionBottom - Lines - Top) * RasterWidth;

			From	= (RegionBottom - Lines) * RasterWidth - 1;
			To		=  RegionBottom          * RasterWidth - 1;

			FromPtr	= &Raster[From];
			ToPtr	= &Raster[To];

			for(i = 0 ; i < Max ; i++)
				AttributeRaster[To - i] = AttributeRaster[From - i];

			for(i = 0 ; i < Lines * RasterWidth ; i++)
				AttributeRaster[Top * RasterWidth + i] = Value;

			for(i = 0 ; i < Max ; i++)
				*ToPtr-- = *FromPtr--;

			memset(&Raster[Top * RasterWidth],' ',Lines * RasterWidth);

			ReleaseSemaphore(&RasterSemaphore);
		}
	}
}

	/* RasterScrollRegion(LONG Direction,LONG RasterTop,LONG RasterBottom,LONG RasterLines):
	 *
	 *	Scroll the contents of the character raster up/down.
	 */

VOID
RasterScrollRegion(LONG Direction,LONG RasterTop,LONG RasterBottom,LONG RasterLines)
{
	LONG Dir = ABS(Direction);

	ObtainSemaphore(&RasterSemaphore);

	SaveRaster(RasterTop,RasterTop + RasterLines - 1);

	if(Dir >= RasterLines)
	{
		LONG Value,i;

			/* All that is needed is to delete the lines. */

		memset(&Raster[RasterTop * RasterWidth],' ',RasterLines * RasterWidth);

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		for(i = 0 ; i < RasterLines * RasterWidth ; i++)
			AttributeRaster[RasterTop * RasterWidth + i] = Value;
	}
	else
	{
		LONG First,Last,Max,Value,i;
		UBYTE *From,*To;

		Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

		Max = (RasterLines - Dir) * RasterWidth;

		if(Direction < 0)
		{
			First	= (RasterTop + RasterLines - Dir) * RasterWidth - 1;
			Last	= (RasterTop + RasterLines	) * RasterWidth - 1;

			From	= &Raster[First];
			To		= &Raster[Last];

			for(i = 0 ; i < Max ; i++)
				AttributeRaster[Last - i] = AttributeRaster[First - i];

			for(i = 0 ; i < RasterWidth * Dir ; i++)
				AttributeRaster[RasterTop * RasterWidth + i] = Value;

			for(i = 0 ; i < Max ; i++)
				*To-- = *From--;

			for(i = RasterBottom ; i >= (RasterTop + Dir) ; i--)
				RasterAttr[i] = RasterAttr[i - Dir];

			memset(&Raster[RasterTop * RasterWidth],' ',RasterWidth * Dir);

			memset(&RasterAttr[RasterTop],SCALE_NORMAL,Dir);
		}
		else
		{
			First	= RasterTop * RasterWidth + RasterWidth * Dir;
			Last	= RasterTop * RasterWidth;

			From	= &Raster[First];
			To		= &Raster[Last];

			for(i = 0 ; i < Max ; i++)
				AttributeRaster[Last + i] = AttributeRaster[First + i];

			for(i = 0 ; i < RasterWidth * Dir ; i++)
				AttributeRaster[(RasterBottom - Dir) * RasterWidth + i] = Value;

			memcpy(To,From,Max);

			memset(&Raster[(RasterBottom - Dir) * RasterWidth],' ',RasterWidth * Dir);

			for(i = RasterTop ; i <= (RasterBottom - Dir) ; i++)
				RasterAttr[i] = RasterAttr[i + Dir];

			memset(&RasterAttr[RasterBottom - Dir],SCALE_NORMAL,Dir);
		}
	}

	RethinkRasterLimit();

	ConFontScaleUpdate();

	ReleaseSemaphore(&RasterSemaphore);
}

	/* RasterShiftChar(LONG Size):
	 *
	 *	Shift the characters following the current cursor
	 *	position Size characters to the right.
	 */

VOID
RasterShiftChar(LONG Size)
{
	UBYTE *From,*To;
	LONG First,Value,Max,i;

	Value = PACK_STATE((CurrentFont == GFX),0,ForegroundPen,0);

	ObtainSemaphore(&RasterSemaphore);

	if(CursorX + Size >= RasterWidth - 1)
	{
		memset(&Raster[CursorY * RasterWidth + CursorX],' ',RasterWidth - 1 - CursorX);

		for(i = 0 ; i < RasterWidth - 1 - CursorX ; i++)
			AttributeRaster[CursorY * RasterWidth + CursorX + i] = Value;
	}
	else
	{
		First	= (CursorY + 1) * RasterWidth - 1;
		To		= &Raster[First];

		From	= &Raster[First - Size];
		Max		= RasterWidth - Size - CursorX;

		for(i = 0 ; i < Max ; i++)
			AttributeRaster[First - i] = AttributeRaster[First - Size - i];

		for(i = 0 ; i < Size ; i++)
			AttributeRaster[CursorY * RasterWidth + CursorX + i] = Value;

		for(i = 0 ; i < Max ; i++)
			*To-- = *From--;

		memset(&Raster[CursorY * RasterWidth + CursorX],' ',Size);
	}

	ReleaseSemaphore(&RasterSemaphore);
}

	/* RasterPutString(STRPTR String,LONG Length):
	 *
	 *	Put a string into the character raster.
	 */

VOID
RasterPutString(STRPTR String,LONG Length)
{
	ObtainSemaphore(&RasterSemaphore);

	if(Length == 1)
	{
		if(CursorX + 1 < RasterWidth)
		{
			Raster[CursorY * RasterWidth + CursorX] = String[0];

			AttributeRaster[CursorY * RasterWidth + CursorX] = PACK_STATE((CurrentFont == GFX),Attributes,ForegroundPen,BackgroundPen);
		}
	}
	else
	{
		if(CursorX + Length >= RasterWidth)
			Length = RasterWidth - 1 - CursorX;

		if(Length > 0)
		{
			LONG i,Value;

			memcpy(&Raster[CursorY * RasterWidth + CursorX],String,Length);

			Value = PACK_STATE((CurrentFont == GFX),Attributes,ForegroundPen,BackgroundPen);

			for(i = 0 ; i < Length ; i++)
				AttributeRaster[CursorY * RasterWidth + CursorX + i] = Value;
		}
	}

	ReleaseSemaphore(&RasterSemaphore);
}

VOID
SaveRasterDummy(LONG UnusedFirst,LONG UnusedLast)
{
	/* Nothing happens here */
}

VOID
SaveRasterReal(LONG First,LONG Last)
{
	STRPTR Line,This;
	LONG Size,i;

	ObtainSemaphore(&RasterSemaphore);

	if(First < 0)
		First = 0;

	if(Last > RasterHeight - 1)
		Last = RasterHeight - 1;

	for(i = First, This = &Raster[(LONG)First * RasterWidth] ; i <= Last ; i++, This += RasterWidth)
	{
		Line = This;
		Size = RasterWidth - 1;

		while(Size > 0 && Line[Size - 1] == ' ')
			Size--;

		AddLine(Line,Size);
	}

	ReleaseSemaphore(&RasterSemaphore);
}

STATIC VOID
PrintStateLine(struct RastPort *RPort,LONG Left,LONG Top,STRPTR Line,LONG Length,UWORD State)
{
	ForegroundPen	= (State >> 4) & 0xF;
	BackgroundPen	= State & 0xF;
	Attributes		= (State >> 8) & 0xF;

	UpdatePens(RPort);

	Move(RPort,Left,Top);

	if(State >> 12)
		GfxText(RPort,Line,Length);
	else
		Text(RPort,Line,Length);
}

STATIC VOID
PrintFullLine(struct RastPort *RPort,LONG Left,LONG Top,STRPTR Line,LONG Length,UWORD *Attrs)
{
	UWORD State;
	LONG First,Last;

	First = 0;
	Last = 1;
	State = Attrs[0];

	while(Last <= Length)
	{
		if(State != Attrs[Last] || Last == Length)
		{
			PrintStateLine(RPort,Left,Top,&Line[First],Last - First,State);

			Left += (Last - First) * TextFontWidth;

			State = Attrs[Last];
			First = Last;
		}

		Last++;
	}
}

VOID
RepairRaster()
{
	if(Window != NULL)
	{
		if((Window->Flags & WFLG_REFRESHBITS) == WFLG_SIMPLE_REFRESH && (Window->WLayer->Flags & LAYERREFRESH))
			RefreshRaster();
	}
}

VOID
FillSaveRaster(struct RastPort *RPort,LONG Width,LONG Height)
{
	UBYTE *Buffer;
	UWORD *Attrs;
	LONG Top,i;

	ObtainSemaphore(&RasterSemaphore);

	Buffer = Raster;
	Attrs = AttributeRaster;

	Top = TextFontBase;

	for(i = 0 ; i < Height / TextFontHeight ; i++)
	{
		PrintFullLine(RPort,0,Top,Buffer,Width / TextFontWidth,Attrs);
		Top += TextFontHeight;

		Buffer += RasterWidth;
		Attrs += RasterWidth;
	}

	ReleaseSemaphore(&RasterSemaphore);
}

VOID
RefreshRaster()
{
	if(Raster && Window && (!XEmulatorBase || Config->TerminalConfig->EmulationMode != EMULATION_EXTERNAL))
	{
		struct Region * PreviousRegion = NULL;
		UBYTE *Buffer;
		UWORD *Attrs;
		LONG Left,Top,i;
		LONG f,b,a,x,y;
		BOOL MustTurnOn;

		BeginRefresh(Window);

			/* Is a clipping region installed? */

		if(ClipRegion != NULL)
		{
				/* Keep the damage list. */

			EndRefresh(Window,FALSE);

				/* Install clipping region. */

			PreviousRegion = InstallClipRegion(Window->WLayer,ClipRegion);

				/* Rebuild clipping area; subsequent rendering operations
				 * will go through the clipping region and the damage
				 * area. All this is explained in the RKM Libs, section 30
				 * under "Installing Regions".
				 */

			BeginRefresh(Window);
		}

		BackupRender();

		DoStatusUpdate();

		f = ForegroundPen;
		b = BackgroundPen;
		a = Attributes;
		x = CursorX;
		y = CursorY;

		MustTurnOn = ClearCursor();

		ObtainSemaphore(&RasterSemaphore);

		Buffer = Raster;
		Attrs = AttributeRaster;

		SetMask(RPort,DepthMask);

		Left = Window->BorderLeft;
		Top = Window->BorderTop + TextFontBase;

		if(Kick30)
		{
			struct Rectangle Area;

			GetRPAttrs(Window->RPort,
				RPTAG_DrawBounds,&Area,
			TAG_DONE);

			if(Area.MinX <= Area.MaxX)
			{
				LONG FirstX,FirstY,Width,Height;

				Area.MinX -= Window->BorderLeft;
				Area.MaxX -= Window->BorderLeft;
				Area.MinY -= Window->BorderTop;
				Area.MaxY -= Window->BorderTop;

				Area.MinX -= Area.MinX % TextFontWidth;
				Area.MaxX = ((Area.MaxX + TextFontWidth - 1) / TextFontWidth) * TextFontWidth;

				Area.MinY -= Area.MinY % TextFontHeight;
				Area.MaxY = ((Area.MaxY + TextFontHeight - 1) / TextFontHeight) * TextFontHeight;

				Width  = (Area.MaxX - Area.MinX + 1 + TextFontWidth - 1)  / TextFontWidth;
				Height = (Area.MaxY - Area.MinY + 1 + TextFontHeight - 1) / TextFontHeight;

				FirstX = Area.MinX / TextFontWidth;
				FirstY = Area.MinY / TextFontHeight;

				if(FirstX + Width > LastColumn + 1)
					Width = LastColumn + 1 - FirstX;

				if(FirstY + Height > LastLine + 1)
					Height = LastLine + 1 - FirstY;

				Left += TextFontWidth  * FirstX;
				Top  += TextFontHeight * FirstY;

				Buffer += FirstY * RasterWidth + FirstX;
				Attrs  += FirstY * RasterWidth + FirstX;

				for(i = 0 ; i < Height ; i++)
				{
					PrintFullLine(RPort,Left,Top,Buffer,Width,Attrs);
					Top += TextFontHeight;

					Buffer += RasterWidth;
					Attrs += RasterWidth;
				}
			}
		}
		else
		{
			for(i = 0 ; i <= LastLine ; i++)
			{
				PrintFullLine(RPort,Left,Top,Buffer,LastColumn+1,Attrs);
				Top += TextFontHeight;

				Buffer += RasterWidth;
				Attrs += RasterWidth;
			}
		}

		ReleaseSemaphore(&RasterSemaphore);

		ForegroundPen	= f;
		BackgroundPen	= b;
		Attributes		= a;

		UpdatePens(RPort);

		BackupRender();

		CursorX = x;
		CursorY = y;

		RethinkCursorPosition(TRUE);

		WindowMarkerRedrawAll();

		if(MustTurnOn)
			DrawCursor();

			/* Rebuild the clipping area if necessary. */

		if(ClipRegion != NULL)
		{
			EndRefresh(Window,FALSE);

			InstallClipRegion(Window->WLayer,PreviousRegion);

			BeginRefresh(Window);
		}

			/* Dump the damage list. */

		EndRefresh(Window,TRUE);

		if(ClipRegion != NULL)
		{
			InstallClipRegion(Window->WLayer,ClipRegion);
		}
	}
}
