#include <math.h>
#include <stdio.h>

#include "chart.h"
#include "gdfontl.h"

/* Color definitions. These can be changed here. */

#define BACKGROUND	192, 192, 192
#define BORDER		0,   0,   0
#define TEXT		0,   0,   0
#define BAR		255, 64,  64
#define LINE		64,  64,  255
#define CROSS		64,  160, 64
#define TICK		64,  64,  64 

/* A module to draw a chart on the specified gd image. The
	chart will occupy the entire image. */

void chartDraw(gdImagePtr im, chartType t, 
	char **labels, char *units, float *data, int points)
{
	/* Basic colors */
	int backgroundColor;
	int barColor;
	int lineColor;
	int crossColor;
	int borderColor;
	int textColor;
	int tickColor;

	/* Range of data */
	float dataMin, dataMax, dataRange;

	/* Spacing */
	int spaceX, spaceY, baseX, baseY;

	int i;

	/* Previous pixel, to draw continuous lines */
	int lastX, lastY;

	/* Points for polygons */
	gdPoint p[4];		

	float lastLabel;
	
	int first;

	/* Divisor to use to make the display of units
		on the Y axis manageable. */
	float divisor;

	/* The size of a Y-axis tick, and an accumulator
		to progress through ticks. */	
	float tick, tickAccum;

	/* First, allocate colors. */
	backgroundColor = gdImageColorAllocate(im, BACKGROUND);

	/* Set this as the transparent color for the GIF. */
	gdImageColorTransparent(im, backgroundColor);

	borderColor = gdImageColorAllocate(im, BORDER);
	
	textColor = gdImageColorAllocate(im, TEXT);

	tickColor = gdImageColorAllocate(im, TICK);

	if (!points) {
		/* Nothing to draw! */
		return;
	}
	
	if (t & chartBar) {
		barColor = gdImageColorAllocate(im, BAR);
	}	
	if (t & chartLine) {
		lineColor = gdImageColorAllocate(im, LINE);
	}	
	if (t & chartCross) {
		crossColor = gdImageColorAllocate(im, CROSS);
	}	

	/* Second, find the range of the data. */	
	for (i=0; (i < points); i++) {
		if ((!i) || (data[i] < dataMin)) {
			dataMin = data[i];
		}	
		if ((!i) || (data[i] > dataMax)) {
			dataMax = data[i];
		}	
	}
	dataRange = dataMax - dataMin;

	/* Decide how much space is available. Start with all of it... */
	
	spaceX = gdImageSX(im);

	/* The left edge is occupied by the Y axis labels and numbers,
		plus a bit of space for aesthetic purposes... */
	baseX = 0;
	if (units) {
		baseX += gdFontLarge->h;
	}

	baseX += gdFontLarge->h + 2;

	/* Subtract that space from the space available for the graph... */
	spaceX -= baseX;

	/* Now compute the space available on the Y axis. */
	spaceY = gdImageSY(im);
	
	spaceY -= (gdFontLarge->h + 2);

	baseY = 0;

	/* Make sure we don't divide by zero. */
	if (!dataRange) {
		dataRange = 1.0;
	}

	/* Discover how many Y-axis "ticks" are appropriate.
		There should be at least two and no more than twenty, and
		the step between ticks should be a power of ten. */
	tick = pow(10.0, floor(log10(dataRange / 2.0))); 
	tickAccum = tick * ceil(dataMin / tick);	

	/* Decide the divisor for labels. 
		Powers of 1000 are familiar
		to the user. */
	divisor = 1.0;
	if (abs(dataMax) > abs(dataMin)) {
		while (abs(dataMax) / divisor >= 1000.0) {
			divisor *= 1000.0;
		}
	} else {
		while (abs(dataMin) / divisor >= 1000.0) {
			divisor *= 1000.0;
		}
	}
	/* Draw the tick lines now, so they will appear
		"behind" the graph. Also label the ticks
		as often as is possible without crowding. */

	first = 1;
	while (tickAccum <= dataMax) {
		int y = spaceY - (spaceY * (tickAccum - dataMin) / dataRange)
			 + baseY; 
		int textY;
		char s[20];
		int w;
		/* Format the tick label, and see if there is enough
			space after the previous one to make room for
			a text label. */
		sprintf(s, "%3.2f", tickAccum / divisor);
		w = gdFontLarge->w * strlen(s);
		textY = y + w/2;
		if (first || (textY < lastLabel)) {
			/* Don't actually draw it unless it fits 
				in the image */
			if ((textY - w > baseY) && 
				(textY < (spaceY + baseY))) {
				gdImageStringUp(im, gdFontLarge,
					baseX - gdFontLarge->h,
					textY, s, textColor);	
			}
			/* The extra two pixels are for aesthetic purposes */
			lastLabel = y - w/2 - 2;
			first = 0;
		}
		gdImageLine(im, baseX, y, baseX + spaceX, y, tickColor);	
		tickAccum += tick;
	}

	/* Now draw the graph. */
	for (i=0; (i < points); i++) {
		int x, y, w;
		x = spaceX * (i + 1) / (points + 1) + baseX;	
		y = spaceY - (spaceY * (data[i] - dataMin) / dataRange) 
			+ baseY; 
		if (labels) {
			int label;
   	    		w = strlen(labels[i]) * gdFontLarge->w;
			label = x - w / 2;	
			if ((!i) || (label > lastLabel)) {
				if ((label > 0) && 
					(label + w < baseX + spaceX)) {
					gdImageString(im, gdFontLarge,
						label, baseY +
						spaceY + 2, labels[i], 
						textColor);		
				}
				lastLabel = x + w / 2 + 2;
			}
		}		
				
		if (t & chartBar) {
			/* Compute the width, then assemble the
				points of a polygon. */
			w = (spaceX / (points + 1));

			/* Reduce the width to two-thirds, making
				the bars easier to tell apart. */
			w = w * 2 / 3;
		
			/* Set up the points. */	
			p[0].x = x - w / 2;
			p[0].y = y;
			p[1].x = x + w / 2;
			p[1].y = y;
			p[2].x = x + w / 2;
			p[2].y = baseY + spaceY - 1;
			p[3].x = x - w / 2;
			p[3].y = baseY + spaceY - 1;

			/* Fill the polygon. */
			gdImageFilledPolygon(im, p, 4, barColor);
		}
		if (t & chartLine) {
			if (i) {
				gdImageLine(im, lastX, lastY, x, y, lineColor);
			}
		}
		if (t & chartCross) {
			gdImageLine(im, x-2, y-2, x+2, y+2, crossColor);
			gdImageLine(im, x+2, y-2, x-2, y+2, crossColor);
		}
		lastX = x;
		lastY = y;		
	}

	/* Draw the border lines at left and bottom... */
	gdImageLine(im, baseX, baseY, baseX, baseY + spaceY, borderColor);	
	gdImageLine(im, baseX, baseY + spaceY, 
		baseX + spaceX, baseY + spaceY, borderColor);	

	/* Label the Y axis (units) */
	if (units) {
		int h;
		char label[121];
		if (divisor != 1.0) {
			sprintf(label, "%sx%.0f", units, divisor);
		} else {
			sprintf(label, "%s", units);
		}
		/* Rotated 90 degrees */
		h = gdFontLarge->w * strlen(label);
		gdImageStringUp(im, gdFontLarge,
			0, baseY + (spaceY / 2) + (h / 2),
			label, textColor);		
	} else {
		int h;
		char label[121];
		if (divisor != 1.0) {
			sprintf(label, "x%.0f", divisor);
		} else {
			sprintf(label, "");
		}
		/* Rotated 90 degrees */
		h = gdFontLarge->w * strlen(label);
		gdImageStringUp(im, gdFontLarge,
			0, baseY + (spaceY / 2) + (h / 2),
			label, textColor);		
	}
}
