//
// SPINPLT - spinning rainbow SPX plotting functions
//
// Version 1.0 10/04/91 Copyright (C) 1991 Lantern Corp.
// Author: Edward Hutchins
// Revisions:
//

#include "spinner.h"

//
// defines
//

#define p1  Pt[0]
#define x1  (Pt[0].x)
#define y1  (Pt[0].y)
#define p2  Pt[1]
#define x2  (Pt[1].x)
#define y2  (Pt[1].y)
#define p2o Pt[2]
#define x2o (Pt[2].x)
#define y2o (Pt[2].y)
#define p1o Pt[3]
#define x1o (Pt[3].x)
#define y1o (Pt[3].y)
#define nR  rgbC[0]
#define nG  rgbC[1]
#define nB  rgbC[2]
#define nRd rgbCd[0]
#define nGd rgbCd[1]
#define nBd rgbCd[2]

#define newd() ((arand( 10 )) + 3)
#define negd(d) ((d)=(((d)<0) ? newd():-newd()))
#define randsgn() (arand( 2 ) ? 1 : -1)
#define randpos(d) (arand( (d) - 40 ) + 20)
#define randcolor() arand(256)

//
// imports
//

IMPORT HANDLE       hLibInst FROM( spinner.c );
IMPORT INT          nColorCnt FROM( spinner.c );
IMPORT BOOL         bEnabled FROM( spinner.c );
IMPORT TRI          triBlank FROM( spinner.c );
IMPORT TRI          triSpin FROM( spinner.c );
IMPORT TRI          triSolid FROM( spinner.c );

//
// locals
//

LOCAL DWORD         dwSeed;

//
// arand - pseudorandom number from 0 to x-1
//

INT NEAR PASCAL arand( INT x )
{
	dwSeed = dwSeed * 0x343fd + 0x269ec3;
	return( (INT)(((dwSeed >> 16) & 0x7fff) * x >> 15) );
}

//
// Tri - convert a tri-state into a boolean (unset == random)
//

BOOL NEAR PASCAL Tri( TRI tri )
{
	switch (tri)
	{
	case TRI_FALSE:
		return( FALSE );
	case TRI_TRUE:
		return( TRUE );
	default:
		return( (BOOL)arand( 2 ) );
	}
}

//
// AllocPalette - allocate the logical palette
//

HPALETTE NEAR PASCAL AllocPalette( INT nSize )
{
	NPLOGPALETTE    npLogPalette;
	HPALETTE        hPalette;
	INT             t;

	npLogPalette = (NPLOGPALETTE)LocalAlloc( LPTR, sizeof(LOGPALETTE) +
					sizeof(PALETTEENTRY) * nSize );
	if (!npLogPalette) return( FALSE );
	npLogPalette->palVersion = 0x0300;
	npLogPalette->palNumEntries = nSize;

	for (t = 0; t < nSize; ++t)
	{
		npLogPalette->palPalEntry[t].peRed =
		npLogPalette->palPalEntry[t].peGreen =
		npLogPalette->palPalEntry[t].peBlue = (BYTE)((t * 256) / nSize);
		npLogPalette->palPalEntry[t].peFlags = PC_RESERVED;
	}

	hPalette = CreatePalette( npLogPalette );
	LocalFree( (HANDLE)npLogPalette );
	return( hPalette );
}

//
// SaverDraw - the main drawing routine
//

VOID FAR PASCAL EXPORT SaverDraw( HWND hwnd, HDC hdc, HANDLE hinst,
								  BOOL (FAR PASCAL *yieldproc)( VOID ) )
{
	INT             xClient, yClient;
	INT             x1d, y1d, x2d, y2d;
	POINT           Pt[4];
	INT             nCurPalEntry;
	INT             rgbC[3], rgbCd[3];
	HPALETTE        hAppPalette, hOldPalette;
	BOOL            bSpin;
	BOOL            bSolid;
	RECT            rect;

	dwSeed += GetTickCount();

	GetWindowRect( hwnd, &rect );
	xClient = rect.right - rect.left;
	yClient = rect.bottom - rect.top;

	if (Tri( triBlank )) FillRect( hdc, &rect, GetStockObject( BLACK_BRUSH ) );
	bSpin = Tri( triSpin );
	bSolid = Tri( triSolid );

	hAppPalette = AllocPalette( nColorCnt );
	hOldPalette = SelectPalette( hdc, hAppPalette, FALSE );
	nCurPalEntry = 0;

	x1 = randpos( xClient ); x1d = newd() * randsgn();
	x2 = randpos( xClient ); x2d = newd() * randsgn();
	y1 = randpos( yClient ); y1d = newd() * randsgn();
	y2 = randpos( yClient ); y2d = newd() * randsgn();

	nR = randcolor(); nRd = newd() * randsgn();
	nG = randcolor(); nGd = newd() * randsgn();
	nB = randcolor(); nBd = newd() * randsgn();

	while ((*yieldproc)())
	{
		INT             t;
		PALETTEENTRY    PalEntry;

		// save the old coordinates
		p1o = p1; p2o = p2;

		// advance the points
		x1 += x1d; y1 += y1d;
		x2 += x2d; y2 += y2d;

		// check the bounds
		if (x1 < 0 || x1 > xClient) { x1 -= x1d; negd( x1d ); x1 += x1d; }
		if (x2 < 0 || x2 > xClient) { x2 -= x2d; negd( x2d ); x2 += x2d; }
		if (y1 < 0 || y1 > yClient) { y1 -= y1d; negd( y1d ); y1 += y1d; }
		if (y2 < 0 || y2 > yClient) { y2 -= y2d; negd( y2d ); y2 += y2d; }

		// spin as needed
		if (bSpin)
		{
			INT nDx = x2 - x1, nDy = y2 - y1;
			INT dx = ((nDx < 0) ? -nDx : nDx);
			INT dy = ((nDy < 0) ? -nDy : nDy);
			INT nDist = dx + dy - (((dx > dy) ? dy : dx) >> 1);
			if (nDist > 1)
			{
				INT ddx = nDx * 2 / nDist;
				INT ddy = nDy * 2 / nDist;
				x1d += ddx, x2d -= ddx;
				y1d += ddy, y2d -= ddy;
			}
		}

		// change the colors one component at a time
		t = arand( 3 );
		rgbC[t] += rgbCd[t];
		if (rgbC[t] < 0 || rgbC[t] > 255)
		{
			rgbC[t] -= rgbCd[t];
			negd( rgbCd[t] );
			rgbC[t] += rgbCd[t];
		}

		// put the color into the current palette entry
		PalEntry.peRed = (BYTE)nR;
		PalEntry.peGreen = (BYTE)nG;
		PalEntry.peBlue = (BYTE)nB;
		PalEntry.peFlags = PC_RESERVED;
		RealizePalette( hdc );
		AnimatePalette( hAppPalette, nCurPalEntry, 1, &PalEntry );

		if (bSolid)
		{
			HBRUSH      hBrush;

			hBrush = CreateSolidBrush( PALETTEINDEX(nCurPalEntry) );
			hBrush = SelectObject( hdc, hBrush );
			SelectObject( hdc, GetStockObject( NULL_PEN ) );
			SetPolyFillMode( hdc, ALTERNATE );
			Polygon( hdc, Pt, 4 );
			hBrush = SelectObject( hdc, hBrush );
			DeleteObject( hBrush );
		}
		else // line segments
		{
			HPEN        hPen;

			hPen = CreatePen( PS_SOLID, 0, PALETTEINDEX(nCurPalEntry) );
			hPen = SelectObject( hdc, hPen );
			MoveTo( hdc, x1, y1 );
			LineTo( hdc, x2, y2 );
			hPen = SelectObject( hdc, hPen );
			DeleteObject( hPen );
		}

		// use the next palette entry next time
		if (++nCurPalEntry >= nColorCnt) nCurPalEntry = 0;
	}

	DeleteObject( SelectPalette( hdc, hOldPalette, FALSE ) );
}
