// By Judy D. Halchin, Educational Computing Services, Allegheny College.
// You may freely copy, distribute and reuse this code. 
// Allegheny College and the author disclaim any warranty of any kind, 
// expressed or implied, as to its fitness for any particular use.
// This work was partially supported by a Teacher Preparation grant from the 
// National Science Foundation.

#import "HistogramView.h"
#import "drawHistogram.h"

#import <dpsclient/wraps.h>
#import <appkit/graphics.h>

#import <stdlib.h>
#import <string.h>
#import <math.h>

#define LEFTMARGIN 60
#define RIGHTMARGIN 15
#define TOPMARGIN 35
#define BOTTOMMARGIN 60
#define SPACER 15

@implementation HistogramView

float _mantissaOf(float floatValue);
int _orderMagnitude(float floatValue);

// global variables not open to the public, but used to pass info from the draw
// methods to drawSelf::.  We want all drawing to be done from drawSelf::, so
// that we can print easily

	int graphType, numberOfBars, numTicks, labelType, numberDecPlaces;
	float *frequencyHeights, *midpoints, defaultBarWidth, extraSpace, ticks[5];
	float actualSpacer=0, *boundaryPoints, *frequencyValues;
	char **labels, tickStrings[5][30];


- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	
	graphType = NONE;

	midpoints = (float *)malloc(sizeof(float));
	boundaryPoints = (float *)malloc(sizeof(float));
	frequencyValues = (float *)malloc(sizeof(float));
	frequencyHeights = (float *)malloc(sizeof(float));
	labels = (char **)malloc(sizeof(char*));
	numberOfBars = 0;
	barWidth = -1;
	barStyle = FILLED;
	numberDecPlaces = 0;
	
	graphTitle = (char *)malloc(1);
	horizAxisTitle = (char *)malloc(1);
	vertAxisTitle = (char *)malloc(1);

	strcpy(graphTitle, "");
	strcpy(horizAxisTitle, "");
	strcpy(vertAxisTitle, "");
	
	return self;
}

- drawHistogramNumBars:(int)numBars boundaries:(float *)boundaries 
					accuracy:(int)numDecPlaces frequencies:(float *)frequencies
{
	int bar;
	
	//reallocate arrays and copy parameters to global variables
	  graphType = HISTOGRAM;
	  numberDecPlaces = numDecPlaces;
	  for (bar = 0; bar < numberOfBars; bar++)
	  	  free(labels[bar]);
	  numberOfBars = numBars;
	  frequencyHeights = (float *)realloc(frequencyHeights, 
											numberOfBars * sizeof(float));
	  midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float));
	  boundaryPoints = (float *)realloc(boundaryPoints, 
	  										(numberOfBars+1) * sizeof(float));
	  for (bar = 0; bar < numberOfBars+1; bar++)
		  boundaryPoints[bar] = boundaries[bar];									
	  frequencyValues = (float *)realloc(frequencyValues, 
	  											numberOfBars * sizeof(float));
	  for (bar = 0; bar < numberOfBars+1; bar++)
		  frequencyValues[bar] = frequencies[bar];									
	  labels = (char **)realloc(labels, numberOfBars*sizeof(char *));
	
	// do calculations and draw
		[self calculateAndDrawHistogram];  
			
	return self;
}

- drawHistogramNumBars:(int)numBars frequencies:(float *)frequencies 
										labels:(char **)barLabels
{
	int bar;
	
	//reallocate arrays and copy parameters to global variables
	  graphType = SIMPLEHISTOGRAM;
	  for (bar = 0; bar < numberOfBars; bar++)
	  	  free(labels[bar]);
	  numberOfBars = numBars;
	  labels = (char **)realloc(labels, numberOfBars * sizeof(char *));
	  frequencyValues = (float *)realloc(frequencyValues, 
	  											numberOfBars * sizeof(float));
	  for (bar = 0; bar < numberOfBars+1; bar++)
		  frequencyValues[bar] = frequencies[bar];									
	  frequencyHeights = (float *)realloc(frequencyHeights, 
											numberOfBars * sizeof(float));
	  midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float));

	  for (bar = 0; bar < numberOfBars; bar++)
	  {
	  	  labels[bar] = malloc(strlen(barLabels[bar]) + 1);
	  	  strcpy(labels[bar], barLabels[bar]);
	  }  

	// do calculations and draw
		[self calculateAndDrawSimpleHistogram];  
	
	return self;
}
					
- drawBarChartNumBars:(int)numBars frequencies:(float *)frequencies 
											labels:(char **)barLabels
{
	int bar;
	
	//reallocate arrays and copy parameters to global variables
	  graphType = BARCHART;
	  for (bar = 0; bar < numberOfBars; bar++)
	  	  free(labels[bar]);
		  
	  numberOfBars = numBars;
	  frequencyValues = (float *)realloc(frequencyValues, 
	  											numberOfBars * sizeof(float));
	  for (bar = 0; bar < numberOfBars+1; bar++)
		  frequencyValues[bar] = frequencies[bar];									
	  labels = (char **)realloc(labels, numberOfBars * sizeof(char *));
	  for (bar = 0; bar < numberOfBars; bar++)
	  {
	  	  labels[bar] = malloc(strlen(barLabels[bar]) + 1);
	  	  strcpy(labels[bar], barLabels[bar]);
	  }  
	  frequencyHeights = (float *)realloc(frequencyHeights, 
											numberOfBars * sizeof(float));
	  midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float));

	// do calculations and draw
		[self calculateAndDrawBarChart];  

	return self;
}

- calculateAndDrawHistogram
{
	float maxFreq, vertUnit, horizUnit, axisWidth, axisHeight, maxTick;
	char formatString[30];
	int bar;

	//convert basic measurements to our coordinates
	  axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN;	  
	  defaultBarWidth = (axisWidth - 2 * SPACER) / numberOfBars;
	  horizUnit = (axisWidth - 2 * SPACER) / 
							(boundaryPoints[numberOfBars] - boundaryPoints[0]);
								
	  maxFreq = 0;
	  for (bar = 0; bar < numberOfBars; bar++)
		  if (frequencyValues[bar] > maxFreq)
			  maxFreq = frequencyValues[bar];

	//calculate midpoints to use for bar labels
	  sprintf(formatString, "%%.%df", numberDecPlaces + 1);
	  for (bar = 0; bar < numberOfBars; bar++)
	  {
	  	  labels[bar] = malloc(30);
	  	  midpoints[bar] = (boundaryPoints[bar] + boundaryPoints[bar+1])/2;
	  	  sprintf(labels[bar], formatString, midpoints[bar]);
		  midpoints[bar] = (midpoints[bar] - boundaryPoints[0]) * horizUnit 
		  										+ SPACER + LEFTMARGIN;
	  }

	//format labels and decide how to display them
	  [self chooseLabelFormat];
	  
	//calculate the vertical tick and labels
	  axisHeight = bounds.size.height - BOTTOMMARGIN - TOPMARGIN - extraSpace;
	  [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick];
	  	  
	//calulate the drawing coordinates for bar heights and vert ticks
      vertUnit = (axisHeight - SPACER) / maxTick;
	  for (bar = 0; bar < numberOfBars; bar++)
		  frequencyHeights[bar] = vertUnit * frequencyValues[bar];
		  //frequencyHeights are measured from horizontal axis
		  
	[self display];

	return self;
}

- calculateAndDrawSimpleHistogram
{
	float maxFreq, vertUnit, horizUnit, axisWidth, axisHeight, maxTick;
	int bar;
	
	//convert basic measurements to our coordinates
	  axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN;
	  defaultBarWidth = (axisWidth - 2 * SPACER) / numberOfBars;
	  horizUnit = (axisWidth - 2 * SPACER) / numberOfBars;
							
	  maxFreq = 0;
	  for (bar = 0; bar < numberOfBars; bar++)
		  if (frequencyValues[bar] > maxFreq)
			  maxFreq = frequencyValues[bar];

	//calculate positions for the bar labels
	  for (bar = 0; bar < numberOfBars; bar++)
		  midpoints[bar] = (bar + 0.5) * horizUnit + SPACER + LEFTMARGIN;

	//format labels and decide how to display them
	  [self chooseLabelFormat];
	  
	//calculate the vertical ticks and their labels
	  axisHeight = bounds.size.height - BOTTOMMARGIN - TOPMARGIN - extraSpace;
	  [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick];
	  	  
	//calulate the drawing coordinates for bar heights and vert ticks
      vertUnit = (axisHeight - SPACER) / maxTick;
	  for (bar = 0; bar < numberOfBars; bar++)
		  frequencyHeights[bar] = vertUnit * frequencyValues[bar];
		  //frequencyHeights are measured from horizontal axis
		 
	[self display];
	return self;
}

- calculateAndDrawBarChart
{
	float maxFreq, vertUnit, axisWidth, axisHeight, maxTick;
	float actualBarWidth;
	int bar;
	
	//convert basic measurements to our coordinates
	  axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN;
	  defaultBarWidth = (axisWidth - SPACER) / numberOfBars - SPACER;
							
	  maxFreq = 0;
	  for (bar = 0; bar < numberOfBars; bar++)
		  if (frequencyValues[bar] > maxFreq)
			  maxFreq = frequencyValues[bar];

	//calculate positions for the bar labels
	  if (barWidth < -0.1)
	  	  actualBarWidth = defaultBarWidth;
	  else
	  	  actualBarWidth = barWidth;
	  actualSpacer = (axisWidth - actualBarWidth * numberOfBars) /
	  												(numberOfBars + 1);
	   		  
	  for (bar = 0; bar < numberOfBars; bar++)
	  	  midpoints[bar] = actualSpacer + LEFTMARGIN + bar 
		  				* (actualBarWidth+actualSpacer) + 0.5 * actualBarWidth;					  

	//format labels and decide how to display them
	  [self chooseLabelFormat];
	  
	//calculate vertical ticks and labels
	  [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick];
	  												
	//calulate the drawing coordinates for bar heights
	  axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN;

      vertUnit = (axisHeight - SPACER) / maxTick;
	  for (bar = 0; bar < numberOfBars; bar++)
		  frequencyHeights[bar] = vertUnit * frequencyValues[bar];
		  
	[self display];

	return self;
}

- setHorizAxisTitle:(char *)horizTitle
{
	horizAxisTitle = realloc(horizAxisTitle, strlen(horizTitle) + 1);
	strcpy(horizAxisTitle, horizTitle);
	return self;
}

- setVertAxisTitle:(char *)vertTitle
{
	vertAxisTitle = realloc(vertAxisTitle, strlen(vertTitle) + 1);
	strcpy(vertAxisTitle, vertTitle);
	return self;
}

- setGraphTitle:(char *)title
{
	graphTitle = realloc(graphTitle, strlen(title) + 1);
	strcpy(graphTitle, title);
	return self;
}

- setBarWidth:(float)width
{
	barWidth = width;
	return self;
}

- setBarStyle:(int)style
{
	barStyle = style;
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	float titlePosition=0, axisWidth, axisHeight, actualBarWidth;

	//draw the background and axes
	  axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN;
	  axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN;
	  drawAxes(bounds.size.width, bounds.size.height, LEFTMARGIN, 
	  						BOTTOMMARGIN + extraSpace, axisWidth, axisHeight);
							
	//draw the graph
	  switch (graphType)
	  {
	  	case HISTOGRAM: 	  
	  	case SIMPLEHISTOGRAM: 	  
			drawHistogram(LEFTMARGIN, BOTTOMMARGIN + extraSpace, SPACER, 
								axisWidth, numberOfBars, 
								defaultBarWidth, frequencyHeights);
			break;
			
	    case BARCHART: 
			if (barWidth > -0.1)
				actualBarWidth = barWidth;
			else 
				actualBarWidth = defaultBarWidth;

			if (barStyle == FILLED)		
	  			drawFilledBarChart(LEFTMARGIN, BOTTOMMARGIN + extraSpace,
								actualSpacer, axisWidth, numberOfBars, 
								actualBarWidth, frequencyHeights);
			else					
	  			drawSolidBarChart(LEFTMARGIN, BOTTOMMARGIN + extraSpace,
							actualSpacer, axisWidth, 
							numberOfBars, actualBarWidth, frequencyHeights);
	  		[self drawLabels];
			break;
			
		case NONE: 
			PSsetgray(1);
			NXRectFill(&bounds);
	  }
	  
	//draw bar labels, vertical ticks, titles
		if (graphType != NONE)
		{
	  		[self drawLabels];
	  		drawVertTicks(numTicks, ticks, LEFTMARGIN-10, LEFTMARGIN - 2, 
	  				tickStrings[0], tickStrings[1], tickStrings[2], 
					tickStrings[3], tickStrings[4]);
	
			titlePosition = LEFTMARGIN + (bounds.size.width - RIGHTMARGIN 
														- LEFTMARGIN) / 2;
			drawHorizTitle(15, titlePosition, 14, horizAxisTitle);
		
			titlePosition = BOTTOMMARGIN + extraSpace + (bounds.size.height 
								- BOTTOMMARGIN - extraSpace - TOPMARGIN) / 2;
			drawVertTitle(22, titlePosition, vertAxisTitle);

			drawHorizTitle(bounds.size.height - 25, bounds.size.width /2, 
												24, graphTitle);
		}
	return self;
}

- calculateTicksForMaxValue:(float)maximum maxTick:(float *)maxTick
{
	float maxMantissa, axisHeight, vertUnit, tickSpacing=0, tick;
	int i, formatDigits=0;
	char formatString[20];
	
	//calculate vertical tick spacing
	  maxMantissa = _mantissaOf(maximum);
	  
	  if (maxMantissa == 1)
	  {
	  	  formatDigits = _orderMagnitude(maximum)-1;
	  	  tickSpacing = 2 * pow(10, formatDigits);
	  }
	  else if ((1 < maxMantissa) && (maxMantissa <= 2.5))
	  {
	  	  formatDigits = _orderMagnitude(maximum)-1;
	  	  tickSpacing = 5 * pow(10, formatDigits);
	  }
	  else if ((2.5 < maxMantissa) && (maxMantissa <= 5))
	  {
	  	  formatDigits = _orderMagnitude(maximum);
	  	  tickSpacing = pow(10, formatDigits);
	  }
	  else if (5 < maxMantissa)
	  {
	  	  formatDigits = _orderMagnitude(maximum);
	  	  tickSpacing = 2 * pow(10, formatDigits);
	  }
	  if (tickSpacing == 0)
	  {
	  	  formatDigits = 0;
	  	  tickSpacing = 1;
	  }
	  
	//calculate the tick marks and format the labels
	  numTicks = 0;
	  if (formatDigits < 0)
	  	  sprintf(formatString, "%c.%df", '%', -formatDigits);
	  else
	  	  strcpy(formatString, "%.0f");

	  for (tick = tickSpacing; tick < maximum + tickSpacing; 
	  											tick = tick + tickSpacing)
	  {
		  sprintf(tickStrings[numTicks], formatString, tick);
	  	  numTicks++;
	  }

	  *maxTick = tick - tickSpacing;
	  for (i = numTicks; i < 5; i++)
	  	  strcpy(tickStrings[i], "");
		  
	  axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN;
      vertUnit = (axisHeight - SPACER) / *maxTick;
	  for (i = 0, tick = tickSpacing; i < numTicks; 
	  											i++, tick = tick + tickSpacing)
	  	  ticks[i] = tick * vertUnit + BOTTOMMARGIN + extraSpace; 	  
		  //tick heights are absolute--measured from bottom of view

	return self;
}

- chooseLabelFormat 
{
	//Draws the bar labels in one of three different ways, depending on how
	//much space there is.  If possible, just puts the label for each bar
	//under the bar, horizontally.  If there's not enough space for that, it
	//will try to stagger the labels--every other one is a little lower.  If
	//there still isn't enought space, it will turn the labels sideways.
	//Assumes that the midpoints are evenly spaced.
	
	int bar;
	float maxWidth, width;
	
	//find the width of the widest label
		maxWidth = 0;
		for (bar = 0; bar < numberOfBars; bar++)
		{
			findLabelWidth(labels[bar], &width);
			if (width > maxWidth)
				maxWidth = width;
		}
		
	//decide which type of labels to use
		if (numberOfBars == 1)
			labelType = PLAIN;
		else
			if (maxWidth + 5 < midpoints[1] - midpoints[0])
				labelType = PLAIN;
			else
				if (numberOfBars == 2)
					labelType = STAGGERED;
				else
					if (maxWidth + 5 < midpoints[2] - midpoints[0])
						labelType = STAGGERED;
					else
						labelType = SIDEWAYS;

	//set amount of extra space needed at bottom to accomodate the labels
		switch (labelType)
		{
			case PLAIN:
				extraSpace = 0;
				break;
			case STAGGERED:
				extraSpace = 8;
				break;
			case SIDEWAYS:
				extraSpace = maxWidth;
		}

	return self;
}
	
- drawLabels
{
	int bar;
	
		switch (labelType)
		{
			case PLAIN:
	  			for (bar = 0; bar < numberOfBars; bar++)
	  	  			drawHorizLabel(midpoints[bar], BOTTOMMARGIN + extraSpace 
														- 12, labels[bar]);
				break;
				
			case STAGGERED:
				for (bar = 0; bar < numberOfBars; bar = bar + 2)
				{
	  	  			drawHorizLabel(midpoints[bar], BOTTOMMARGIN + extraSpace 
														- 12, labels[bar]);
					if (bar+1 < numberOfBars)
	  	  				drawHorizLabel(midpoints[bar+1], BOTTOMMARGIN 
										+ extraSpace - 24, labels[bar+1]);
					
				}
				break;
				
			case SIDEWAYS:;
	  			for (bar = 0; bar < numberOfBars; bar++)
	  	  			drawVertLabel(midpoints[bar], BOTTOMMARGIN + extraSpace 
															- 4, labels[bar]);
		}	

	return self;
}
	
- sizeTo:(NXCoord)width :(NXCoord)height	
{
	[super sizeTo:width :height];
	switch (graphType)
	{
		case HISTOGRAM:[self calculateAndDrawHistogram];
			break;
		case SIMPLEHISTOGRAM:[self calculateAndDrawSimpleHistogram];
			break;
		case BARCHART:[self calculateAndDrawBarChart];
			break;
	}
	return self;
}		
	
- setFrame:(const NXRect *)frameRect
{
	[super setFrame:frameRect];
	switch (graphType)
	{
		case HISTOGRAM:[self calculateAndDrawHistogram];
			break;
		case SIMPLEHISTOGRAM:[self calculateAndDrawSimpleHistogram];
			break;
		case BARCHART:[self calculateAndDrawBarChart];
			break;
	}
	return self;
}		
	
float _mantissaOf(float floatValue)
{
	return floatValue / pow(10, _orderMagnitude(floatValue));
}

int _orderMagnitude(float floatValue)
{
	char stringValue[30], *ePointer;
	
	sprintf(stringValue, "%e", floatValue);
	ePointer = strchr(stringValue, 'e');
	return atoi(ePointer + 1);
}

@end
