/*
		File: DirectBits.c

	Copyright 1990, 1991, 1992 by Thomas Knoll and Adobe Systems Inc..
	
	Written by	Marc Kaufman
				Kaufman Research
				17 Mountain Meadow
				Woodside, CA  94062
				(415) 851-5777
*/

#include "DirectBits.h"

#include <GestaltEqu.h>
#include <Quickdraw.h>
#include <QDOffscreen.h>
#include "ScreenSaverGestalt.h"

void AddDirectBits (GDHandle	 gdh,
					PixMap		*map,
					const Rect	*sRect,
					const Rect	*dRect,
					RgnHandle	 dRgn);

pascal void DirectToggleCopyBits (PixMap *map,
								  const Rect *srcRect,
								  const Rect *dstRect,
								  RgnHandle maskRgn);

pascal void DoToggleCopyBits (PixMap *map,
							   const Rect *srcRect,
							   const Rect *dstRect,
							   RgnHandle maskRgn);


// A black-50% gray color table useful in building 1-bit deep PixMaps used
// in addOver mode for eraseable screen feedback on direct devices.

CTabHandle gAddOverTable = NULL;

extern GlobalData theGlobalData;

static GlobalData *GetGlobalData(void)
{
	return &theGlobalData;
}




/*
NeedToSwapMMU(GPtr myData)
	{
#ifdef __powerc
	return FALSE;
#else
	return myData->need32check && !GetMMUMode();		// Now == 24 bit
#endif

	}
*/
//#endif

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

void AddDirectBits (GDHandle	 gdh,
					PixMap		*map,
					const Rect	*sRect,
					const Rect	*dRect,
					RgnHandle	 dRgn)
{
	Point	offset;
	Rect	sub;
	PixMap	*dMap;
	Ptr		sBase, dBase, gBuffer;
	int16	sRowBytes, dRowBytes;
	int16	pixelSize, rLongs;
	int16	sLeft, dLeft;
	int16	rows, *r;
	GPtr	myData;
	RgnPtr	rgn;
	Boolean	need32Swap;


	myData = GetGlobalData();
	HLock (myData->gRegionBits);
	gBuffer = StripAddress(*myData->gRegionBits);
		
	need32Swap = FALSE;	//	NeedToSwapMMU(myData);

	// Apply region to source coordinates, rather than dst coordinates
	
	offset.h = sRect->left - dRect->left;
	offset.v = sRect->top  - dRect->top;
	
	OffsetRgn (dRgn, offset.h, offset.v);		// Source region mask

	LocalToGlobal ((Point *) &dRect->top);
	LocalToGlobal ((Point *) &dRect->bottom);
	
	offset.v = offset.h = 0;
	ShieldCursor (dRect, offset);
	

	// Compute Source addresses (for topLeft)
	//	This is a 1-bit deep bitmap (8 bits per byte)
	
	sBase = map->baseAddr;
	
	if (map->pmVersion == 0)
		sBase = StripAddress (sBase);
	else if (map->pmVersion != baseAddr32) {
		PixMap **h;
		h = (PixMap **) RecoverHandle((Ptr) map);
		if (h)
			sBase = GetPixBaseAddr(h);
	}
	
	sRowBytes = map->rowBytes & 0x7fff;
	
	sLeft = sRect->left - map->bounds.left;		// sRect inset from left edge
	
	sBase += (sRect->top - map->bounds.top) * (long) sRowBytes
			 + ((sLeft & ~31) >> 3);			// long aligned left boundary

	OffsetRgn (dRgn, -sRect->left + (sLeft & 31), -sRect->top);	// Relative to sBase
	
	// Set up a one line bit string corresponding to the region box
	
	rLongs = ((*dRgn)->rgnBBox.right + 31) >> 5;
	
	DoClearLongs ((unsigned long *)gBuffer, rLongs);
	
	// Compute Destination (screen) addresses (for topLeft)
	
	dMap = *(*gdh)->gdPMap;
	pixelSize = dMap->pixelSize;
	dRowBytes = dMap->rowBytes & 0x7fff;
	dBase = 	GetPixBaseAddr ((*gdh)->gdPMap);		// 32-bit clean address
	
	dLeft = dRect->left - (sLeft & 31) - (*gdh)->gdRect.left;
	
	dBase += (dRect->top - (*gdh)->gdRect.top) * (long) dRowBytes
			 + (dLeft * (pixelSize >> 3));

	// Now expand the region and draw each masked rectangle
	
	HLock ((Handle) dRgn);
	rgn = (RgnPtr)StripAddress(*dRgn);
	
	while (!QDDone (nil)); // Wait for QD to finish drawing, just in case...
	
	if (need32Swap)
		Set32BitMode();
	
	if (rgn->rgnSize == sizeof(Region)) {		// rectangular region
	
		DoInvertBits ((unsigned long *)gBuffer,
					  rgn->rgnBBox.right - rgn->rgnBBox.left,
					  rgn->rgnBBox.left );
		
		rows = rgn->rgnBBox.bottom - rgn->rgnBBox.top;
			
		switch (pixelSize) {
			case 8:
					AddDirect8 ((unsigned long *)sBase, (unsigned char *)dBase,
								(unsigned long *)gBuffer, rows,
								rLongs, sRowBytes, dRowBytes);
					break;
			
			case 16:
					AddDirect16 ((unsigned long *)sBase, (unsigned short *)dBase,
								 (unsigned long *)gBuffer, rows,
								 rLongs, sRowBytes, dRowBytes);
					break;
			
			case 32:
					AddDirect32 ((unsigned long *)sBase, (unsigned long *)dBase,
								 (unsigned long *)gBuffer, rows,
								 rLongs, sRowBytes, dRowBytes);
					break;
		}
	}
	else {			// Non-rectangular region

		r = (int16 *)rgn + sizeof(Region)/sizeof(int16);
		
		while (*r < rgn->rgnBBox.bottom) {
			sub.top = *r++;
			
			while (*r != 32767) {
				sub.left = *r++;
				if (*r != 32767) {		// This should never be 32767, but...
					sub.right = *r++;
				}
				else
					sub.right = rgn->rgnBBox.right;
				
				DoInvertBits ((unsigned long *)gBuffer,
							  sub.right - sub.left,
							  sub.left );
			}
			++r;
			
			if (*r != 32767) {			// This should never be 32767, but...
				sub.bottom = *r;
			}
			else
				sub.bottom = rgn->rgnBBox.bottom;
						 
			rows = sub.bottom - sub.top;
				
			switch (pixelSize) {
				case 8:
						AddDirect8 ((unsigned long *)sBase, (unsigned char *)dBase,
									(unsigned long *)gBuffer, rows,
									rLongs, sRowBytes, dRowBytes);
						break;
				
				case 16:
						AddDirect16 ((unsigned long *)sBase, (unsigned short *)dBase,
									 (unsigned long *)gBuffer, rows,
									 rLongs, sRowBytes, dRowBytes);
						break;
				
				case 32:
						AddDirect32 ((unsigned long *)sBase, (unsigned long *)dBase,
									 (unsigned long *)gBuffer, rows,
									 rLongs, sRowBytes, dRowBytes);
						break;
			}
			sBase += rows * sRowBytes;
			dBase += rows * dRowBytes;
		}
	}
	
	if (need32Swap)
		Clear32BitMode();
		
	ShowCursor();
	HUnlock (myData->gRegionBits);
	HUnlock ((Handle) dRgn);
}

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

// Toggles the pixels on the screen that are set to 1 in the source pixmap,
// which is always 1 bit deep.	Also, the srcRect and dstRect are always the
// same size.  srcXor mode is used to toggle the pixels on non-direct devices,
// while addOver mode is used on direct devices.

pascal void DirectToggleCopyBits (PixMap *map,
								  const Rect *srcRect,
								  const Rect *dstRect,
								  RgnHandle maskRgn)
{
	GPtr		myData;
	Rect		bounds, srcArea, dstArea;
	GDHandle	gdh, currentDevice;
	RgnHandle	dRgn = NewRgn();
	GrafPtr		thePort;
	long		response;

	FixPC();						// Make sure PC is n-bit clean
	
	myData = GetGlobalData();

	if (myData->checkScreen)
		if (Gestalt (gestaltScreenSaverAttr, &response) == noErr)
			if (response & (1 << gestaltSaverAsleep) &&
			    response & (1 << gestaltAppDrawingDisabled))
				return;

	bounds = *dstRect;
	LocalToGlobal ((Point *) &bounds.top);
	LocalToGlobal ((Point *) &bounds.bottom);
	GetPort	(&thePort);

	currentDevice = GetGDevice();
	
	for (gdh = GetDeviceList (); gdh; gdh = GetNextDevice (gdh)) {

		dstArea = (*gdh)->gdRect;

		if (SectRect (&bounds, &dstArea, &dstArea)) {	// On the device

			GlobalToLocal ((Point *) &dstArea.top);
			GlobalToLocal ((Point *) &dstArea.bottom);

			if (dRgn) {
				RectRgn (dRgn, &dstArea);
				SectRgn	(dRgn, thePort->visRgn, dRgn);		// Clip to vis
				SectRgn	(dRgn, thePort->clipRgn, dRgn);		// Clip to clip
				if (maskRgn)
					SectRgn	(dRgn, maskRgn, dRgn);			// Clip to mask
	
				if (!EmptyRgn (dRgn)) {
	
					dstArea = (*dRgn)->rgnBBox;
					srcArea = dstArea;
					OffsetRect (&srcArea,
								srcRect->left - dstRect->left,
								srcRect->top  - dstRect->top );
	
					if ((*(*gdh)->gdPMap)->pixelSize >= 8) {
					
						SetGDevice (gdh);
						
						AddDirectBits (gdh,
									   map,
									   &srcArea,
									   &dstArea,
									   dRgn);
					
						SetGDevice (currentDevice);
					}
					else
	
						DoToggleCopyBits (map,
										  &srcArea,
										  &dstArea,
										  dRgn);
				}
			}
			else {		// Backup for the case where NewRgn fails
				srcArea = dstArea;
				OffsetRect (&srcArea,
							srcRect->left - dstRect->left,
							srcRect->top  - dstRect->top );
							
				DoToggleCopyBits (map,
								  &srcArea,
								  &dstArea,
								  maskRgn);
			}
		}
	}
	if (dRgn)
		DisposeRgn (dRgn);
}


#if 1
static void CursorSafeCopyBits (BitMap *srcMap,
								const Rect *srcArea,
								const Rect *dstArea,
								int16 mode,
								RgnHandle maskRegion)
{
	CopyBits(srcMap, &qd.thePort->portBits, srcArea, dstArea, mode, maskRegion);
}

#else

static void CursorSafeCopyBits (BitMap *srcMap,
								const Rect *srcArea,
								const Rect *dstArea,
								int16 mode,
								RgnHandle maskRegion)
{

	Point mousePt;
	Point srcOffset;
	Rect mouseRect;

	GetMouse(&mousePt);

	#if Macintosh

	SetRect(&mouseRect, -16, -16, 64, 16); 		// 64 because of bug in ROM

	#else

	SetRect(&mouseRect, -16, -16, 16, 16);

	#endif

	OffsetRect(&mouseRect, mousePt.h, mousePt.v);

	srcOffset.h = srcArea->left - dstArea->left;
	srcOffset.v = srcArea->top - dstArea->top;

	if (SectRect (dstArea, &mouseRect, &mouseRect))
		{

		Rect r = *dstArea;

		if (mouseRect.top > dstArea->top)
			{

			r.bottom = mouseRect.top;

			CopyBits (srcMap,
						&qd.thePort->portBits,
						r + srcOffset,
						r,
						mode,
						maskRegion);

			r.bottom = dstArea->bottom;

			}

		if (mouseRect.bottom < dstArea.bottom)
			{

			r.top = mouseRect.bottom;

			CopyBits (srcMap,
						&qd.thePort->portBits,
						r + srcOffset,
						r,
						mode,
						maskRegion);

			}

		r.top	 = mouseRect.top;
		r.bottom = mouseRect.bottom;

		if (mouseRect.left > dstArea.left)
			{

			r.right = mouseRect.left;

			CopyBits (srcMap,
						&qd.thePort->portBits,
						r + srcOffset,
						r,
						mode,
						maskRegion);

			r.right = dstArea.right;

			}

		if (mouseRect.right < dstArea.right)
			{

			r.left = mouseRect.right;

			CopyBits (srcMap,
						&qd.thePort->portBits,
						r + srcOffset,
						r,
						mode,
						maskRegion);

			}

		r.left	= mouseRect.left;
		r.right = mouseRect.right;
		
		int16 srcLeft = srcMap->bounds.left;

		int16 offset = (r.left + srcOffset.h - srcLeft) >> 3;

		int16 rowBytes = srcMap->rowBytes & 0x7FFF;
		
		DoSetMMUMode (kWant32BitAddressing);
		
		Boolean empty =
			DoTestRect (CalcPtr (srcMap->baseAddr,
								 r.top + srcOffset.v - srcMap.bounds.top,
								 rowBytes,
								 offset),
					    r.bottom - r.top,
						((r.right + srcOffset.h - srcLeft + 7) >> 3) - offset,
						rowBytes,
						0);
						
		DoSetMMUMode (!kWant32BitAddressing);

		if (!empty)
			{

			DoCopyBits (srcMap,
						qd.thePort->portBits,
						r + srcOffset,
						r,
						mode,
						maskRegion);

			}

		}

	else
		DoCopyBits (srcMap,
					qd.thePort->portBits,
					srcArea,
					dstArea,
					mode,
					maskRegion);

	}
#endif

static pascal void VisibleRgn(RgnHandle rgn)
{
	SectRgn(rgn, qd.thePort->visRgn, rgn);
}

static pascal void DrawableRgn(RgnHandle rgn)
{
	SectRgn(rgn, qd.thePort->clipRgn, rgn);
}

static pascal void VisibleRect(Rect *r)
{
	RgnHandle rRgn;

	rRgn = NewRgn();
	if (rRgn)
	{
		RectRgn(rRgn, r);
		VisibleRgn(rRgn);
		*r = (*(RgnHandle)rRgn)->rgnBBox;
		DisposeRgn(rRgn);
	}
}

static pascal void DrawableRect(Rect *r)
{
	RgnHandle rRgn;

	VisibleRect(r);

	rRgn = NewRgn();
	if (rRgn)
	{
		RectRgn(rRgn, r);
		DrawableRgn(rRgn);
		*r = (*(RgnHandle)rRgn)->rgnBBox;
		DisposeRgn(rRgn);
	}
}



// Toggles the pixels on the screen that are set to 1 in the source pixmap,
// which is always 1 bit deep.	Also, the srcRect and dstRect are always the
// same size.  srcOr mode is used to toggle the pixels on non-direct devices,
// while addOver mode is used on direct devices.

pascal void DoToggleCopyBits (PixMap *map,
							 const Rect *srcRect,
							 const Rect *dstRect,
							 RgnHandle maskRgn)
{
	
	// If the button is down, we are probably tracking and we would be
	// better off having a flickering cursor than having tearing.  If
	// the button is up, we are probably better off with tearing.
	
	Rect bounds = *dstRect;
	Boolean btnState = Button ();

	LocalToGlobal((Point *)&bounds.top);
	LocalToGlobal((Point *)&bounds.bottom);

	for (GDHandle gdh = GetDeviceList (); gdh; gdh = GetNextDevice (gdh)) {

		Rect dstArea = (*gdh)->gdRect;

		if (SectRect(&bounds, &dstArea, &dstArea)) {
			GlobalToLocal((Point *)&dstArea.top);
			GlobalToLocal((Point *)&dstArea.bottom);

			DrawableRect(&dstArea);

			if (!EmptyRect(&dstArea)) {
				Rect srcArea = dstArea;
				Point offset;
				int16 saveRowBytes = map->rowBytes;
				CTabHandle saveTable = map->pmTable;
				int16 mode = srcXor;

				offset.v = srcRect->top - dstRect->top;
				offset.h = srcRect->left - dstRect->left;
				OffsetRect(&srcArea, offset.h, offset.v);

				if ((*gdh)->gdType == directType) {
					mode = addOver;
					map->rowBytes |= 0x8000;
					map->pmTable = gAddOverTable;
				}
					
				if (btnState)
					CopyBits((BitMap *)map,
							 &qd.thePort->portBits,
							 &srcArea,
							 &dstArea,
							 mode,
							 maskRgn);
								
				else
					CursorSafeCopyBits((BitMap *)map,
									   &srcArea,
									   &dstArea,
									   mode,
									   maskRgn);

				if (mode == addOver) {
					map->rowBytes = saveRowBytes;
					map->pmTable = saveTable;
				}
			}
		}
	}
}

typedef unsigned char LookUpTable [256];

typedef struct
	{
	LookUpTable R;
	LookUpTable G;
	LookUpTable B;
	} RGBLookUpTable;

static CTabHandle BuildColorTable (const RGBLookUpTable &clut,
								   int16 count,
								   long seed)
{
	int index;
	const long kBaseColorTableSize = sizeof (ColorTable) -
									  sizeof (CSpecArray);
	CTabHandle cTable = (CTabHandle)
						NewHandle (kBaseColorTableSize +
										sizeof (ColorSpec) * count);

	(*cTable)->ctSeed  = seed;
	(*cTable)->ctFlags = 0;
	(*cTable)->ctSize  = count - 1;

	for (index = 0; index < count; ++index)
	{
		unsigned short r = clut.R[index];
		unsigned short g = clut.G[index];
		unsigned short b = clut.B[index];

		ColorSpec *spec = &(*cTable)->ctTable [index];

		spec->value = index;

		spec->rgb.red	= (r << 8) + r;
		spec->rgb.green = (g << 8) + g;
		spec->rgb.blue	= (b << 8) + b;
	}

	return cTable;

}

#define NeedAscendingColorTable		0
#define Multiply16(x,y)		((int16)(x)*(long)(int16)(y))


static unsigned char ComputeTableEntry (int16 index,
									int16 levels,
									Boolean forceDescending)
{
	if (!forceDescending && NeedAscendingColorTable)
		return (unsigned char) (Multiply16 (index, 255) / (levels - 1));

	else
		return (unsigned char) (255 - Multiply16 (index, 255) / (levels - 1));
}

static CTabHandle MakeGrayTable (int16 levels,
						  int16 colorize,
						  Boolean forceDescending)
{
	RGBLookUpTable clut;
	long seed;

	for (int index = 0; index < levels; ++index)
	{

		clut.R[index] = ComputeTableEntry(index, levels, forceDescending);

		clut.G[index] = clut.R[index];
		clut.B[index] = clut.R[index];

		switch (colorize)
		{

			case 0:
				clut.G[index] = 0;
				clut.B[index] = 0;
				break;

			case 1:
				clut.R[index] = 0;
				clut.B[index] = 0;
				break;

			case 2:
				clut.R[index] = 0;
				clut.G[index] = 0;

		}

	}

	if (colorize >= 0)
		seed = 900 + colorize;
	else
		seed = 500 + levels;

	return BuildColorTable(clut, levels, seed);
}

int initDirectBits()
{
	theGlobalData.gRegionBits = NewHandle (32768/8);
	if (theGlobalData.gRegionBits == nil)
		return MemError();

	gAddOverTable = MakeGrayTable(2, -1, false);
	if (gAddOverTable == nil)
		return MemError();

	(*gAddOverTable)->ctSeed = GetCTSeed();

	(*gAddOverTable)->ctTable [0].rgb.red = 0;
	(*gAddOverTable)->ctTable [0].rgb.green = 0;
	(*gAddOverTable)->ctTable [0].rgb.blue = 0;
	(*gAddOverTable)->ctTable [1].rgb.red = 0x8000;
	(*gAddOverTable)->ctTable [1].rgb.green = 0x8000;
	(*gAddOverTable)->ctTable [1].rgb.blue = 0x8000;
	return 0;
}

void cleanupDirectBits()
{
	if (gAddOverTable)
		DisposeCTable(gAddOverTable);
	if (theGlobalData.gRegionBits != nil)
		DisposeHandle(theGlobalData.gRegionBits);
}
