// Luke Rogers
// ID: 9320530
// ME 498/599 Computer Graphics - M.A. Ganter
//
// Please note to use this program you will need WindowListener.class in the 
// same subdirectory (folder) as this code.
//
// Plots a function on the screen with input from the user
//
import java.io.*;
import java.lang.Class;
import java.lang.reflect.Method;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import WindowListener.*;
import java.awt.Component.*;
//import Evaluate.*;

//**************************************************************************
public class Function extends Frame
{
	static Scrollbar sbXRange = new Scrollbar();
	static Scrollbar sbYRange = new Scrollbar();
	static TextField tfXOrigin = new TextField();
	static TextField tfYOrigin = new TextField();
	static TextField tfFunction = new TextField();
	static TextField tfInfo = new TextField();
	static Choice chCellSize = new Choice();
	static Button buRedraw = new Button();
	static Checkbox cbGrad = new Checkbox();
	static Checkbox cbSolid = new Checkbox();
	static CheckboxGroup cbGroup = new CheckboxGroup();
	static int theFrameWidth;
	static int theFrameHeight;
	static double[][] theArray = new double[441][441];
	static double theMax;
	static double theMin;
	static String name = "Evaluate";

	public Function()
	{
		setLayout(null);
		sbXRange.setOrientation(Scrollbar.HORIZONTAL);
		sbXRange.setValues(100,10,0,1010);
		sbXRange.setBounds(110,60,100,30);
		sbXRange.setUnitIncrement(10);
		sbXRange.setBlockIncrement(10);
		add(sbXRange);
		sbYRange.setOrientation(Scrollbar.HORIZONTAL);
		sbYRange.setValues(100,10,0,1010);
		sbYRange.setBounds(220,60,100,30);
		sbYRange.setUnitIncrement(10);
		sbYRange.setBlockIncrement(10);
		add(sbYRange);
		tfXOrigin.setText("0");
		tfXOrigin.setBounds(10,60,40,30);
		add(tfXOrigin);
		tfYOrigin.setText("0");
		tfYOrigin.setBounds(60,60,40,30);
		add(tfYOrigin);
		tfFunction.setText("");
		tfFunction.setBounds(10,27,310,30);
		add(tfFunction);
		tfInfo.setText("");
		tfInfo.setBounds(10,95,440,30);
		add(tfInfo);
		chCellSize.add("1");
		chCellSize.add("5");
		chCellSize.add("10");
		chCellSize.add("15");
		chCellSize.add("20");
		chCellSize.setBounds(330,20,50,50);
		chCellSize.select("10");
		add(chCellSize);
		buRedraw.setLabel("Redraw");
		buRedraw.setBounds(385,27,55,30);
		add(buRedraw);
		cbGrad.setLabel("Grad");
		cbGrad.setCheckboxGroup(cbGroup);
		cbGrad.setState(true);
		cbGrad.setBounds(330,60,50,30);
		add(cbGrad);
		cbSolid.setLabel("Solid");
		cbSolid.setCheckboxGroup(cbGroup);
		cbSolid.setState(false);
		cbSolid.setBounds(390,60,50,30);
		add(cbSolid);
		setTitle("A Function Plotter Application");
		// Register Listners
		ChangeAction theAction = new ChangeAction();
		AdjustmentAction theAdjustment = new AdjustmentAction();
		MouseAction theMouseAction = new MouseAction();
		ItemAction theItemAction = new ItemAction();
		sbXRange.addAdjustmentListener(theAdjustment);
		sbXRange.addMouseListener(theMouseAction);
		sbYRange.addAdjustmentListener(theAdjustment);
		sbYRange.addMouseListener(theMouseAction);
		tfXOrigin.addActionListener(theAction);
		tfXOrigin.addMouseListener(theMouseAction);
		tfYOrigin.addActionListener(theAction);
		tfYOrigin.addMouseListener(theMouseAction);
		chCellSize.addItemListener(theItemAction);
		chCellSize.addMouseListener(theMouseAction);
		tfFunction.addActionListener(theAction);
		tfFunction.addMouseListener(theMouseAction);
		buRedraw.addActionListener(theAction);
		buRedraw.addMouseListener(theMouseAction);
		cbGrad.addItemListener(theItemAction);
		cbGrad.addMouseListener(theMouseAction);
		cbSolid.addItemListener(theItemAction);
		cbSolid.addMouseListener(theMouseAction);
	}  
   
	//**************************************************************************
	// This is the paint method.  It is called inside of the "show" method.
	public void paint(Graphics theGraphic)
	{
		// Cast into the graphics 2D world
		tfInfo.setText("Drawing the plot...");
		//this.setProperties();
		Graphics2D the2DGraphic = (Graphics2D) theGraphic;
		// Do loop to paint the plot i is the Y variable
		for (int i = 0; i < (theArray.length - 20); i = i + Integer.parseInt(chCellSize.getSelectedItem()))
		{
			// Do loop to paint the plot j is the X variable
			for (int j = 0; j < (theArray.length - 20); j = j + Integer.parseInt(chCellSize.getSelectedItem()))
			{
				// Create empty points for the gradient paint
				Point2D theMaxPoint = new Point2D.Double();
				Point2D theMinPoint = new Point2D.Double();
				int theMinShade = 0;
				int theMaxShade = 255;
				double theShadeScale = 255.0 / Math.abs(theMax - theMin);
				// Offsets to center the function graph
				double theXOffset = 25.0 - Integer.parseInt(chCellSize.getSelectedItem());
				double theYOffset = 124.0 - Integer.parseInt(chCellSize.getSelectedItem()); 
				// Set the rectangle width and height
				int theRectSize = Integer.parseInt(chCellSize.getSelectedItem());
				// Set the rectangle x and y points
				double theRectX = (double)(j + theXOffset);
				double theRectY =  (double)((theArray.length - 0 - i) + theYOffset);
				// Make copys of i and j for the out of bounds condition
				int ii = i;
				int jj = j;
				//if ((i + theRectSize) >= theArray.length)
					//ii = theArray.length - theRectSize - 1;
				//if ((j + theRectSize) >= theArray.length)
					//jj = theArray.length - theRectSize - 1;
				// Figure out the gradient shit
				if (cbGroup.getSelectedCheckbox() == cbGrad)
				{
					//get the values at the corners of the rect and their points
					double theUpperLeftValue = theArray[ii][jj];
					Point2D theUpperLeftPoint = new Point2D.Double(theRectX, theRectY + theRectSize);
					int theUpperLeftShade = (int)((theArray[ii][jj] - theMin) * theShadeScale);
					double theUpperRightValue = theArray[ii][jj + theRectSize];
					Point2D theUpperRightPoint = new Point2D.Double(theRectX + theRectSize, theRectY + theRectSize);
					int theUpperRightShade = (int)((theArray[ii][jj + theRectSize] - theMin) * theShadeScale);
					double theLowerLeftValue = theArray[ii + theRectSize][jj];
					Point2D theLowerLeftPoint = new Point2D.Double(theRectX, theRectY);
					int theLowerLeftShade = (int)((theArray[ii + theRectSize][jj] - theMin) * theShadeScale);
					double theLowerRightValue = theArray[ii + theRectSize][jj + theRectSize];
					Point2D theLowerRightPoint = new Point2D.Double(theRectX + theRectSize, theRectY);
					int theLowerRightShade = (int)((theArray[ii + theRectSize][jj + theRectSize]  - theMin) * theShadeScale);

					//find the maximum value in the rect
					double max1 = Math.max(theUpperLeftValue, theUpperRightValue);
					double max2 = Math.max(theLowerLeftValue, theLowerRightValue);
					double max = Math.max(max1, max2);
					//find the minimum value in the rect
					double min1 = Math.min(theUpperLeftValue, theUpperRightValue);
					double min2 = Math.min(theLowerLeftValue, theLowerRightValue);
					double min = Math.min(min1, min2);

					// Figure out which damn points it is
					if (max == theUpperLeftValue)
					{
						theMaxPoint = theUpperLeftPoint;
						theMaxShade = theUpperLeftShade;
					}
					else if (max == theUpperRightValue)
					{
						theMaxPoint = theUpperRightPoint;
						theMaxShade = theUpperRightShade;
					}
					else if (max == theLowerLeftValue)
					{
						theMaxPoint = theLowerLeftPoint;
						theMaxShade = theLowerLeftShade;
					}
					else if (max == theLowerRightValue)
					{
						theMaxPoint = theLowerRightPoint;
						theMaxShade = theLowerRightShade;
					}
					if (min == theUpperLeftValue)
					{
						theMinPoint = theUpperLeftPoint;
						theMinShade = theUpperLeftShade;
					}
					else if (min == theUpperRightValue)
					{
						theMinPoint = theUpperRightPoint;
						theMinShade = theUpperRightShade;
					}
					else if (min == theLowerLeftValue)
					{
						theMinPoint = theLowerLeftPoint;
						theMinShade = theLowerLeftShade;
					}
					else if (min == theLowerRightValue)
					{
						theMinPoint = theLowerRightPoint;
						theMinShade = theLowerRightShade;
					}
					// create the colors
					Color theMinColor = new Color(theMinShade, theMinShade, theMinShade);
					Color theMaxColor = new Color(theMaxShade, theMaxShade, theMaxShade);
					//create a gradient between the max and min
					GradientPaint theGradient = new GradientPaint(theMinPoint, theMinColor, theMaxPoint, theMaxColor);
					Rectangle2D theRect = new Rectangle2D.Double(theRectX, theRectY, theRectSize, theRectSize);
					the2DGraphic.setPaint(theGradient);
					the2DGraphic.fill(theRect);
				}
				else // Must want to draw solid rectangles
				{
					int theShade = (int)((theArray[i][j] - theMin) * theShadeScale);
					Color theColor = new Color(theShade, theShade, theShade);
					Rectangle2D theRect = new Rectangle2D.Double(theRectX, theRectY, theRectSize, theRectSize);
					the2DGraphic.setPaint(theColor);
					the2DGraphic.fill(theRect);
				}
			}
		}
		tfInfo.setText("");
	}
	//**************************************************************************
	public double[][] fillArray(int theSize, int theXOrigin, int theYOrigin, double theXRange, double theYRange)
	{
		//double[][] theArray = new double[theSize + 1][theSize + 1];
		//Class theClassName = Evaluate.class;
		Class theEvalClass = Function.class;
		String theMethodName = "getAns";
		// Initialize the YStep increment variable
		double theYStep = theYRange / theSize;
		// Initialize Y at its minimum value
		double Y = theYOrigin - (theYRange / 2);
		// Dynamically load the class
		tfInfo.setText("Populating the array...");
		try
		{
			theEvalClass = Class.forName(name);
		}
       		catch (ClassNotFoundException e)
       		{
               		tfInfo.setText("Class not found");
      		}
		Method theMethods[] = theEvalClass.getMethods();
		//System.out.println(""+theMethods[0]);
		//theClassName = theMethods[0].getDeclaringClass();
		//theMethodName = ""+theMethods[0].getName();
		//System.out.println(""+theClassName);
		//System.out.println(""+theMethodName);
		for (int i = 0; i < theArray.length; i++)
		{
			// Initialize the XStep increment variable
			double theXStep = theXRange / theSize;
			// Initialize X at its minimum value
			double X = theXOrigin - (theXRange / 2);
			for (int j = 0; j < theArray.length; j++)
			{
				//Populate the Array
				// Call the Evaluate.class function getAns
				Object EvalArgs[] = new Object[1];
				double d[] = new double[2];
				EvalArgs[0] = d;
				d[0] = X;
				d[1] = Y;
				//theArray[i][j] = Evaluate.getAns(d);
				try
				{
					theArray[i][j] = ((Double)theMethods[0].invoke(theMethods[0],EvalArgs)).doubleValue();
				}
				catch (IllegalAccessException e)
				{
					tfInfo.setText("IllegalAccessException");
				}
				catch (java.lang.reflect.InvocationTargetException e)
				{
					tfInfo.setText("InvocationTargetException");
				}
				catch (IllegalArgumentException e)
				{
					tfInfo.setText("IllegalArgumentException");
				}
				// Increment the X variable
				X = X + theXStep;
			}	
			// Increment the Y variable
			Y = Y + theYStep;
		}
		return theArray;
	}
	//**************************************************************************
	public double getMax(double[][] theArray)
	{
		double theMax = -99999999999999999999999999999999999999999999999999999.9;
		for (int i = 0; i < theArray.length; i++)
		{
			for (int j = 0; j < theArray.length; j++)
			{
				if (theArray[i][j] > theMax)
					theMax = theArray[i][j];
			}
		}
		return theMax;
	}
	//**************************************************************************
	public double getMin(double[][] theArray)
	{
		double theMin = 999999999999999999999999999999999999999999999999999999.9;
		for (int i = 0; i < theArray.length; i++)
		{
			for (int j = 0; j < theArray.length; j++)
			{
				if (theArray[i][j] < theMin)
					theMin = theArray[i][j];
			}
		}
		return theMin;
	}
	//**************************************************************************
	public void setProperties()
	{
		// Get the screen size
		Dimension theScreenDimensions = Toolkit.getDefaultToolkit().getScreenSize();
		// set the frame's size
		//theFrameWidth = theScreenDimensions.width;
		//theFrameHeight = theScreenDimensions.height;
		//Or set the frame dimensions using integers
		int theFrameWidth = 460;
		int theFrameHeight = 580;
		this.setSize(theFrameWidth, theFrameHeight);
		this.setLocation(theScreenDimensions.width/2 - theFrameWidth/2, theScreenDimensions.height/2 - theFrameHeight/2);
		this.setResizable(false);
		this.setBackground(Color.white);
	}
	//**************************************************************************
	//**************************************************************************
	static public void main(String args[])
	{
		WindowListener theWindowListener = new WindowListener();
		Function theFunction = new Function();
		// Add the window close capability to the Canvas
		theFunction.addWindowListener(theWindowListener);
		// Evaluate the fuction and create an array
		theFunction.writeFunction("x * x + y * y - 1");
		tfFunction.setText("x * x + y * y - 1");
		double[][] theArray = theFunction.fillArray(440, Integer.parseInt(tfXOrigin.getText()), Integer.parseInt(tfYOrigin.getText()), sbXRange.getValue(), sbYRange.getValue());
		theMax = theFunction.getMax(theArray);
		theMin = theFunction.getMin(theArray);
		theFunction.setProperties();
		theFunction.setVisible(true);
	}

	//**************************************************************************
	//**************************************************************************
	class MouseAction extends java.awt.event.MouseAdapter
	{
		public void mouseEntered(java.awt.event.MouseEvent event)
		{
			Object theObject = event.getSource();
			if (theObject == sbXRange)
				sbXRange_MouseOn(event);
			else if (theObject == sbYRange)
				sbYRange_MouseOn(event);
			else if (theObject == tfXOrigin)
				tfXOrigin_MouseOn(event);
			else if (theObject == tfYOrigin)
				tfYOrigin_MouseOn(event);
			else if (theObject == chCellSize)
				chCellSize_MouseOn(event);
			else if (theObject == tfFunction)
				tfFunction_MouseOn(event);
			else if (theObject == buRedraw)
				buRedraw_MouseOn(event);
			else if (theObject == cbGrad)
				cbGrad_MouseOn(event);
			else if (theObject == cbSolid)
				cbSolid_MouseOn(event);
		}
		public void mouseExited(java.awt.event.MouseEvent event)
		{
			Object theObject = event.getSource();
			if (theObject == sbXRange)
                               feature_MouseOff(event);
                        else if (theObject == sbYRange)
                                feature_MouseOff(event);
                        else if (theObject == tfXOrigin)
                                feature_MouseOff(event);
                        else if (theObject == tfYOrigin)
                                feature_MouseOff(event);
                        else if (theObject == chCellSize)
                                feature_MouseOff(event);
                        else if (theObject == tfFunction)
                                feature_MouseOff(event);
                        else if (theObject == buRedraw)
                                feature_MouseOff(event);
                        else if (theObject == cbGrad)
                                feature_MouseOff(event);
                        else if (theObject == cbSolid)
                                feature_MouseOff(event);
		}
	}
	//**************************************************************************
	class ChangeAction implements java.awt.event.ActionListener
	{
		public void actionPerformed(java.awt.event.ActionEvent event)
		{
			Object theObject = event.getSource();
			if (theObject == tfXOrigin)
				tfXOrigin_ActionPerformed(event);
			else if (theObject == tfYOrigin)
				tfYOrigin_ActionPerformed(event);
			else if (theObject == tfFunction)
				tfFunction_ActionPerformed(event);
			else if (theObject == buRedraw)
				buRedraw_ActionPerformed(event);
		}
	}

	//**************************************************************************
	class AdjustmentAction implements java.awt.event.AdjustmentListener
   	{
      		public void adjustmentValueChanged(java.awt.event.AdjustmentEvent event)
      		{
         		Object theObject = event.getSource();
         		if (theObject == sbXRange)
            			sbXRange_AdjustmentPerformed(event);
         		else if (theObject == sbYRange)
            			sbYRange_AdjustmentPerformed(event);
      		}
   	}
	//**************************************************************************
	class ItemAction implements java.awt.event.ItemListener
	{
		public void itemStateChanged(java.awt.event.ItemEvent event)
		{
			Object theObject = event.getSource();
			if (theObject == chCellSize)
				chCellSize_ItemChanged(event);
			else if (theObject == cbGrad)
				cbGrad_ItemChanged(event);
			else if (theObject == cbSolid)
				cbSolid_ItemChanged(event);
		}
	}
	//**************************************************************************
	void sbXRange_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Adjusts the X range");
	}
	void sbYRange_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Adjusts the Y range");
	}
	void tfXOrigin_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Sets the X origin");
	}
	void tfYOrigin_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Sets the Y origin");
	}
	void chCellSize_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Sets cell size from 1 x 1 to 20 x 20 pixels");
	}
	void tfFunction_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Enter function here");
	}
	void buRedraw_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Redraws the Function Graph");
	}
	void cbGrad_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Fill cells with a gradient color");
	}
	void cbSolid_MouseOn(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("Fill cells with a solid color");
	}
	void feature_MouseOff(java.awt.event.MouseEvent event)
	{
		tfInfo.setText("");
	}
	void sbXRange_AdjustmentPerformed(java.awt.event.AdjustmentEvent event)
   	{
		int theXRange = sbXRange.getValue();
		int theXOrigin = Integer.parseInt(tfXOrigin.getText());
		int theXMin = theXOrigin - (theXRange / 2);
		int theXMax = theXOrigin + (theXRange / 2);
		tfInfo.setText("Changing X Range to: "+theXMin+" to "+theXMax);
   	}
   	void sbYRange_AdjustmentPerformed(java.awt.event.AdjustmentEvent event)
   	{
		int theYRange = sbYRange.getValue();
		int theYOrigin = Integer.parseInt(tfYOrigin.getText());
		int theYMin = theYOrigin - (theYRange / 2);
		int theYMax = theYOrigin + (theYRange / 2);
		tfInfo.setText("Changing Y Range to: "+theYMin+" to "+theYMax);
   	}
   	void tfXOrigin_ActionPerformed(java.awt.event.ActionEvent event)
   	{
      		try
	  	{
	     		int theXOrigin = Integer.parseInt(tfXOrigin.getText());
	     		tfInfo.setText("Changing the X Origin to: "+theXOrigin);
	  	}
	  	catch (NumberFormatException e)
	  	{
	     		tfInfo.setText("Not a valid integer!");
	     		tfXOrigin.setText("0");
      		}
   	}
	void tfYOrigin_ActionPerformed(java.awt.event.ActionEvent event)
	{
		try
		{
			int theYOrigin = Integer.parseInt(tfYOrigin.getText());
			tfInfo.setText("Changing the Y Origin to: "+theYOrigin);
		}
		catch (NumberFormatException e)
		{
			tfInfo.setText("Not a valid integer!");
			tfYOrigin.setText("0");
		}
	}
	void chCellSize_ItemChanged(java.awt.event.ItemEvent event)
	{
		int theCellSize = Integer.parseInt(chCellSize.getSelectedItem());
		tfInfo.setText("Changing the cell size to: "+theCellSize);
	}
	void cbGrad_ItemChanged(java.awt.event.ItemEvent event)
	{
		tfInfo.setText("Changing color scheme to Gradient");
	}
	void cbSolid_ItemChanged(java.awt.event.ItemEvent event)
	{
		tfInfo.setText("Changing color scheme to solid");
	}
   	void tfFunction_ActionPerformed(java.awt.event.ActionEvent event)
   	{
      		String theFunction = tfFunction.getText();
		writeFunction(theFunction);
   	}
   	void buRedraw_ActionPerformed(java.awt.event.ActionEvent event)
   	{
		theArray = this.fillArray(440, Integer.parseInt(tfXOrigin.getText()), Integer.parseInt(tfYOrigin.getText()), sbXRange.getValue(), sbYRange.getValue());
		theMax = this.getMax(theArray);
		theMin = this.getMin(theArray);
		this.repaint();
   	}
	//***************************************************************************
	public void writeFunction(String theFunction)
	{
		String unique;
		unique = ""+Math.round(Math.round(Math.random() * 1000));
		name = "Evaluate"+unique;
		// this is where it will grab the string or it will be passed
		String expression = theFunction;
		try
		{
			// open an output file and connect it to a printwriter
        		PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(name+".java")));
        		out.println("");
        		out.println("public class "+name+"  {");
        		out.println("  public static double getAns ( double arg[]) {" );
        		out.println("  double x = arg[0];");
        		out.println("  double y = arg[1];");
        		out.println("  return ("+theFunction+");");
        		out.println("  }");
        		out.println("}");
        		out.close();
		}
		catch (IOException e)
		{
        		System.out.println("IOE : the stack trace is:");
        		e.printStackTrace();
        	}

		// Start the dynamic compile / load process
		// Check for class file and delete it
  		File classFile = new File( name+".class" );
   		if ( classFile.exists())
		{
        		classFile.delete();
   		}
		// Compile
  		tfInfo.setText("Compiling...");
  		String[ ] compile = { "javac", name+".java" };
  		try
		{
               		Runtime.getRuntime( ).exec( compile );
               	}
		catch (IOException e)
		{
               		tfInfo.setText("IOE in the compile");
               	}
		
		// Wait for the compile to happen
  		int count = 100;
  		while ( (!classFile.exists()) && (count > 0) ) 
		{
    			classFile = new File( name+".class" );
         		try
			{
            			Thread.sleep( 200 );
                 		count--;
                 	}
         		catch (InterruptedException e)
			{
            			tfInfo.setText("IntEx in the sleep");
                 	}
         		if (count <= 0) tfInfo.setText("Compiler Errors!!!");
  		}
		// Dynamically load the class file
  		tfInfo.setText("Loading...");
        	try
		{
           		// set up argument list
        		Object funk_args[] = new Object[1];
      			double d[]= new double[2];
                	funk_args[0]=d;
                	Double Z;
                	double z;
                	// dynamically load class  Test.class which we just compiled
      			Class C = Class.forName(name);
                	// get the classes methods
      			Method M[] = C.getMethods();
                	// set the arguments
      			d[0] = 3.0; d[1] = 1.0 ;
                	// call the function
                	Z = (Double)M[0].invoke(M[0],funk_args);
                	z = Z.doubleValue();
                	//System.out.println("Func("+d[0]+","+d[1]+")="+z);
			tfInfo.setText("Compile successful. Select Redraw to see plot.");
                }
        	catch (IllegalAccessException e)
		{
      			tfInfo.setText("I A E");
      		}
   		catch (java.lang.reflect.InvocationTargetException e)
		{
      			tfInfo.setText("I T E");
      		}
   		catch (ClassNotFoundException e)
		{
      			tfInfo.setText("Class not found. Illegal mathematical function?");
      		}
		// Check for class file and delete it
  		File theClassFile = new File( name+".class" );
   		if ( theClassFile.exists())
		{
        		theClassFile.delete();
   		}
		// Check for java file and delete it
  		File theJavaFile = new File( name+".java" );
   		if ( theJavaFile.exists())
		{
        		theJavaFile.delete();
   		}
	}
}
