// Copyright 1999
// College of Computer Science
// Northeastern University Boston MA 02115

// This software may be used for educational purposes as long as this copyright
// notice is retained at the top of all files

// Should this software be modified, the words "Modified from Original" must be
// included as a comment below this notice

// All publication rights are retained.  This software or its documentation may
// not be published in any media either in whole or in part.

///////////////////////////////////////////////////////////////////////////////

// Graphics.cpp

///////////////////////////////////////////////////////////////////////////////

#include "CoreTools.h"				// needed for core tools compile in Win32

#include "Graphics.h"


// Marks

void Mark(short mark, short size) {

	GraphicsState GS = GetGraphicsState();

	// extract the current position

	short x, y;
	GS.Location.Get(x, y);

	// set the pen size to 1, 1

	PenSizeWidget PS(1, 1);
	PS.Draw();

	// temporarily set the fill color to the pen color
	// to maintain the convention that marks always use the pen color

	FillColorWidget FC(GS.PenColor);
	FC.Draw();

	// do sanity check for the size

	if (size <= 0)
		size = 1;
	
	// draw mark
	switch (mark) {
		case DotMark:			// simple dot
			LineTo(x,y);
			break;
			
		case HBarMark:			// horizontal bar: can be used for vertical axis tick
			DrawLine(x - size, y, x + size, y);
			break;
			
		case VBarMark:			// vertical bar: can be used for horizontal axis tick
			DrawLine(x, y - size, x, y + size);
			break;
			
		case PlusMark:			// plus:  +
			DrawLine(x - size, y, x + size, y);
			DrawLine(x, y - size, x, y + size);
			break;
			
		case CrossMark:			// cross: x
			DrawLine(x - size, y - size, x + size, y + size);
			DrawLine(x - size, y + size, x + size, y - size);
			break;
			
		case StarMark:			// star:  *
			DrawLine(x - size, y, x + size, y);
			DrawLine(x, y - size, x, y + size);
			DrawLine(x - size, y - size, x + size, y + size);
			DrawLine(x - size, y + size, x + size, y - size);
			break;
			
		case SquareMark:		// open square
			// add 1 so right and bottom boundaries are correct
			FrameRect(x - size, y - size, x + size + 1, y + size + 1);
			break;
			
		case CircleMark:		// open circle
			// add 1 so right and bottom boundaries are correct
			FrameOval(x - size, y - size, x + size + 1, y + size + 1);
			break;
			
		case WedgeUMark:		// triangle wedge up
			MoveTo(x, y - size);
			LineTo(x - size, y + size);
			LineTo(x + size, y + size);
			LineTo(x, y - size);
			break;
			
		case WedgeDMark:		// triangle wedge down
			MoveTo(x, y + size);
			LineTo(x + size, y - size);
			LineTo(x - size, y - size);
			LineTo(x, y + size);
			break;
			
		case WedgeLMark:		// triangle wedge left
			MoveTo(x - size, y);
			LineTo(x + size, y + size);
			LineTo(x + size, y - size);
			LineTo(x - size, y);
			break;
			
		case WedgeRMark:		// triangle wedge right
			MoveTo(x + size, y);
			LineTo(x - size, y - size);
			LineTo(x - size, y + size);
			LineTo(x + size, y);
			break;
			
		case FillSquareMark:	// filled square
			// add 1 so right and bottom boundaries are correct
			FillRect(x - size, y - size, x + size + 1, y + size + 1);
			break;
			
		case FillCircleMark:	// filled circle
			// add 1 so right and bottom boundaries are correct
			FillOval(x - size, y - size, x + size + 1, y + size + 1);
			break;
		
		case FillWedgeUMark:	// filled triangle wedge up
			{
				int i = 0;
				int k = 0;
				int z = y - size;
				int limit = 2 * size;
				
				for (; i <= limit; i++, k = (i + 1)/2, z++)					
					DrawLine(x - k, z, x + k, z);
			}
			break;
		
		case FillWedgeDMark:	// filled triangle wedge down
			{
				int i = 0;
				int k = 0;
				int z = y + size;
				int limit = 2 * size;
				
				for (; i <= limit; i++, k = (i + 1)/2, z--)					
					DrawLine(x - k, z, x + k, z);
			}
			break;
		
		case FillWedgeLMark:	// filled triangle wedge left
			{
				int i = 0;
				int k = 0;
				int z = x - size;
				int limit = 2 * size;
				
				for (; i <= limit; i++, k = (i + 1)/2, z++)					
					DrawLine(z, y - k, z, y + k);
			}
			break;
		
		case FillWedgeRMark:	// filled triangle wedge right
			{
				int i = 0;
				int k = 0;
				int z = x + size;
				int limit = 2 * size;
				
				for (; i <= limit; i++, k = (i + 1)/2, z--)					
					DrawLine(z, y - k, z, y + k);
			}
			break;
		
		case DiamondMark:
			DrawLine(x, y - size, x + size, y);
			DrawLine(x + size, y, x, y + size);
			DrawLine(x, y + size, x - size, y);
			DrawLine(x - size, y, x, y - size);

			break;

		case FillDiamondMark:
			{
				int i;

				for (i = 0; i <= size; i++) {
					DrawLine(x - size + i, y - i, x + size - i, y - i);
					DrawLine(x - size + i, y + i, x + size - i, y + i);
				}
			}
			break;

		default:
			break;
	} // end switch to draw mark
	
	// restore the current pen spot
	MoveToWidget MT(x, y);
	MT.Draw();

	// restore the current pen size
	PS.Set(GS.PenSize).Draw();

	// reset the fill color
	FC.Set(GS.FillColor).Draw();
}


// Axes routines

void PlotAxes2D(
	const Rect2D& Bounds			// data bounds
) {
	// bounds coordinates
	double x1;
	double y1;
	double x2;
	double y2;
	
	Bounds.Get(x1, y1, x2, y2);
	
	// origin coordinates
	double x;
	double y;
	
	if (x1 <= 0 && 0 <= x2)
		x = 0;						// if in bounds put axis at x = 0
	else
		x = x1;						// otherwise at x = x1
	
	if (y1 <= 0 && 0 <= y2)
		y = 0;						// if in bounds put axis at y = 0
	else
		y = y1;						// otherwise at y = y1
	
	// axis endpoints
	Point2D P;
	Point2D Q;
	
	// x axis
	P.Set(x1, y);
	Q.Set(x2, y);
	
	MoveTo2D(P);
	LineTo2D(Q);
	
	// y axis
	P.Set(x, y1);
	Q.Set(x, y2);
	
	MoveTo2D(P);
	LineTo2D(Q);
}


void MarkAxes2D(
	const Rect2D& Bounds,			// data bounds
	double xdelta,					// spacing on x axis
	double ydelta,					// spacing on y axis
	int size						// hash mark size
) {
	// bounds coordinates
	double x1;
	double y1;
	double x2;
	double y2;
	
	Bounds.Get(x1, y1, x2, y2);
	
	// origin coordinates
	double x;
	double y;
	
	if (x1 <= 0 && 0 <= x2)
		x = 0;						// if in bounds put axis at x = 0
	else
		x = x1;						// otherwise at x = x1
	
	if (y1 <= 0 && 0 <= y2)
		y = 0;						// if in bounds put axis at y = 0
	else
		y = y1;						// otherwise at y = y1
	
	// mark position and coordinates
	Point2D P;
	double xp;
	double yp;
	
	// loop variables
	short min;
	short max;
	short index;
	
	// along x axis
	if (xdelta > 0) {
		min = SafeRoundToShort(x1 / xdelta);
		
		if (min * xdelta < x1)
			min++;
		
		max = SafeRoundToShort(x2 / xdelta);
		
		if (max * xdelta > x2)
			max--;
		
		for (index = min; index <= max; index++) {
			xp = index * xdelta;

			P.Set(xp, y);
			
			MoveTo2D(P);
			Mark(VBarMark, size);
		}
	}
	
	// along y axis
	if (ydelta > 0) {
		min = SafeRoundToShort(y1 / ydelta);
		
		if (min * ydelta < y1)
			min++;
		
		max = SafeRoundToShort(y2 / ydelta);
		
		if (max * ydelta > y2)
			max--;
		
		for (index = min; index <= max; index++) {
			yp = index * ydelta;

			P.Set(x, yp);
			
			MoveTo2D(P);
			Mark(HBarMark, size);
		}
	}
}


void PlotGrids2D(
	const Rect2D& Bounds,			// data bounds
	double xdelta,					// spacing on x axis
	double ydelta					// spacing on y axis
) {
	// bounds coordinates
	double x1;
	double y1;
	double x2;
	double y2;
	
	Bounds.Get(x1, y1, x2, y2);
	
	// grid positions and coordinates
	Point2D P;
	Point2D Q;
	double xp;
	double yp;
	
	// loop variables
	short min;
	short max;
	short index;
	
	// vertical grid
	if (xdelta > 0) {
		min = SafeRoundToShort(x1 / xdelta);
		
		if (min * xdelta < x1)
			min++;
		
		max = SafeRoundToShort(x2 / xdelta);
		
		if (max * xdelta > x2)
			max--;
		
		for (index = min; index <= max; index++) {
			xp = index * xdelta;

			P.Set(xp, y1);
			Q.Set(xp, y2);
			
			MoveTo2D(P);
			LineTo2D(Q);
		}
	}
	
	// horizontal grid
	if (ydelta > 0) {
		min = SafeRoundToShort(y1 / ydelta);
		
		if (min * ydelta < y1)
			min++;
		
		max = SafeRoundToShort(y2 / ydelta);
		
		if (max * ydelta > y2)
			max--;
		
		for (index = min; index <= max; index++) {
			yp = index * ydelta;

			P.Set(x1, yp);
			Q.Set(x2, yp);
			
			MoveTo2D(P);
			LineTo2D(Q);
		}
	}
}


void GuessOne(double a1, double a2, double& delta) {
	if (a2 <= a1) {
		delta = 0;
		return;
	}
	
	// helper variables
	
	double difference = a2 - a1;
	double ratio;

	// find largest power of 10 < difference
	
	delta = 1;
	
	while (delta <  difference)		// go up
		delta *= 10;
	
	while (delta >= difference)		// go down
		delta /= 10;
	
	// now delta has the form 10^k and
	// delta < difference <= 10 * delta
	
	// get ratio of difference to delta
	// will have: 1 < ratio <= 10
	
	ratio = difference / delta;
	
	// reduce delta if ratio is less than 6
	
	if (ratio < 6) {
		if (ratio >= 3)
			delta /= 2;		// delta becomes 5 * 10^(k-1)
		else
			delta /= 5;		// delta becomes 2 * 10^(k-1)
	}
}


void GuessDelta2D(
	const Rect2D& Bounds,			// data bounds
	double& xdelta,					// spacing on x axis
	double& ydelta,					// spacing on y axis
	bool TrueShape					// if true then force xdelta == ydelta
) {
	// bounds coordinates
	double x1;
	double y1;
	double x2;
	double y2;
	
	Bounds.Get(x1, y1, x2, y2);
	
	GuessOne(x1, x2, xdelta);
	GuessOne(y1, y2, ydelta);

	if (TrueShape)
		EqualizeSize(xdelta, ydelta);
}


void SetupPlot2D(
	const Rect2D& Bounds,
	const RectData& Limits,
	bool TrueShape
) {
	// set the current transform
	SetTransform2D(Bounds, Limits, TrueShape);

	// obtain grid spacing
	double xdelta;
	double ydelta;

	GuessDelta2D(Bounds, xdelta, ydelta, TrueShape);

	// save graphics state
	GraphicsState GS = GetGraphicsState();

	// set the pen size to 1, 1

	PenSizeWidget PS(1, 1);
	PS.Draw();

	// set the pen color to green for the grids

	PenColorWidget PC;
	PC.Set(RGBgreen).Draw();

	// plot grids

	PlotGrids2D(Bounds, xdelta, ydelta);

	// set the pen color to black for the axes

	PC.Set(RGBblack).Draw();

	// plot axes

	PlotAxes2D(Bounds);

	// restore graphics settings

	PS.Set(GS.PenSize).Draw();
	PC.Set(GS.PenColor).Draw();
}
