// TangentLine Applet
// Concept by Bill Ziemer
// Programming by Brendon Cheves & Bill Ziemer
// e-mail: brendon@csulb.edu  wziemer@csulb.edu

// Copyright 1996 by California State University, Long Beach
// expr package Copyright 1996 by Darius Bacon

// Created 10/22/96
// Last update 10/22/96


// java packages
import java.applet.*;
import java.awt.*;
import java.lang.Math;


// our packages
import expr.*;			// f(x) parser by Darius Bacon
import cool_utils.*;	// cool utilities we made


public class TangentLine extends Applet
{
	static final boolean DEBUG = false;

	double first, last, xTangent, h, step, secantSlope, realSlope;	
	
	Color current_color = Color.black;

	Button helpButton, aboutButton, plusdx, minusdx, okBtn;

	Label sliderLabel, secantSlopeLabel, realSlopeLabel, errorLabel, stepLabel;
   		
    CardLayout
    	theCards;
    	    
    Canvas	theCanvas;
    
    Panel	theDrawingPanel, theFeedbackPanel, okBtnPanel,
    		mainCd, aboutCd, helpCd, theAboutPanel, theHelpPanel;
        	
    Image offscreenImage;

   	Graphics offscreenGraphics, offscreenInstance;

	MultiLineLabel aboutLabel;
	
	Dimension d;
	
	// EZGridLayout manager stuff
	EZGridSettings gridSettings;
	EZGridLayout EZGL;

	// Our function class, based on the Java Polygon class
	ZFunction theFunction;

	
    public void init()
    {
     	resize(640,420);
   	   	   	   	
  	   	theFunction = new ZFunction();
   	   	
    	// create a card layout
    	theCards = new CardLayout(10,10);
		this.setLayout(theCards);
 		
 		// create the main card
 		mainCd = new Panel();
 		this.add("main",mainCd);
 		mainCd.setLayout(new BorderLayout(10,10));
 		
 		// create the about card
 		aboutCd = new Panel();
 		this.add("about",aboutCd);
  		aboutCd.setLayout(new BorderLayout(10,10));
		
 		// create the help card
 		helpCd = new Panel();
 		this.add("help",helpCd);
  		helpCd.setLayout(new BorderLayout(10,10));
  

    	// create the drawing panel and add it to the main cd
   		theDrawingPanel = new Panel();
		mainCd.add("Center",theDrawingPanel);
 		theDrawingPanel.setLayout(new BorderLayout(10,10));

		  		  	
	  	// create the EZGridLayout
  		EZGL = new EZGridLayout(12,2);
	
		
		// create the feedback panel and add it to the main cd
 		theFeedbackPanel = new Panel();
		mainCd.add("South",theFeedbackPanel);
 		theFeedbackPanel.setLayout(EZGL);
 		 
 		minusdx = new Button("-");
		gridSettings = new EZGridSettings(minusdx,1,1,1,1);
		theFeedbackPanel.add(minusdx);
 		EZGL.addLayoutInfo(gridSettings);


  		sliderLabel = new Label();
		sliderLabel.setText("h = " + String.valueOf(h - ((1E3 * h) % 1) / 1E3));
		gridSettings = new EZGridSettings(sliderLabel,3,1,2,1);
		theFeedbackPanel.add(sliderLabel);
		EZGL.addLayoutInfo(gridSettings);
		 		
 		plusdx = new Button("+");
		gridSettings = new EZGridSettings(plusdx,2,1,1,1);
		theFeedbackPanel.add(plusdx);
		EZGL.addLayoutInfo(gridSettings);
		
		//stepLabel = new Label();
		//gridSettings = new EZGridSettings(stepLabel,4,1,2,1);
		//theFeedbackPanel.add(stepLabel);
		//EZGL.addLayoutInfo(gridSettings);
				 				
		secantSlopeLabel = new Label();
		gridSettings = new EZGridSettings(secantSlopeLabel,7,1,3,1);
		theFeedbackPanel.add(secantSlopeLabel);
		EZGL.addLayoutInfo(gridSettings);

		realSlopeLabel = new Label();
		gridSettings = new EZGridSettings(realSlopeLabel,10,1,3,1);
		theFeedbackPanel.add(realSlopeLabel);
		EZGL.addLayoutInfo(gridSettings);
		
		errorLabel = new Label();
		gridSettings = new EZGridSettings(errorLabel,7,2,3,1);
		theFeedbackPanel.add(errorLabel);
		EZGL.addLayoutInfo(gridSettings);
		
		helpButton = new Button("Help");
		gridSettings = new EZGridSettings(helpButton,1,2,1,1);
		theFeedbackPanel.add(helpButton);
		EZGL.addLayoutInfo(gridSettings);

		aboutButton = new Button("About...");
		gridSettings = new EZGridSettings(aboutButton,2,2,1,1);
		theFeedbackPanel.add(aboutButton);
		EZGL.addLayoutInfo(gridSettings);
		
		theFeedbackPanel.resize(preferredSize());
		
		// create the about panel and add it to the about cd
		theAboutPanel = new Panel();
		aboutCd.add("Center",theAboutPanel);
		theAboutPanel.setLayout(new FlowLayout());
		
		// create the OK button panel and add it to the about cd
		okBtnPanel = new Panel();
		aboutCd.add("South",okBtnPanel);
		
		// add the stuff to the about panel
		Font theFont = new Font("TimesRoman", Font.BOLD, 18);
		aboutLabel = new MultiLineLabel(
			"Riemann Sum Applet\nCopyright 1996 by California State University, Long Beach\nWritten by Bill Ziemer and Brendon Cheves\nFunction Parsing Copyright 1996 by Darius Bacon",
			10, 10, MultiLineLabel.CENTER);
		aboutLabel.setFont(theFont);
		theAboutPanel.add(aboutLabel);
		
		// add the OK button
		okBtn = new Button("OK");
		okBtnPanel.add(okBtn);

		// initialize the offscreen stuff
		d = theDrawingPanel.size();

		offscreenImage = this.createImage(600,500);
		offscreenGraphics = offscreenImage.getGraphics();
		if (offscreenGraphics == null) System.out.println("Error: Null offscreen graphics");
		
		// set up the initial screen
		drawFunction("(1-x^2) / 2", "0.2", "0.05");
		
		// finally, show the main card
		theCards.show(this,"main");
    }
    
    
    public void paint(Graphics g)
    {
    	// who cares about g, we know where we want to draw
		offscreenGraphics.clearRect(0,0,600,500);
		offscreenGraphics.setColor(Color.white);
		offscreenGraphics.fillRect(0,0,600,500);
		
		theFunction.plot(offscreenGraphics);
		 
		drawSecant(offscreenGraphics);
				
		drawAxis(offscreenGraphics);
 		drawLimits(offscreenGraphics);
		drawLabels(offscreenGraphics);
		
		// force the offscreen buffer to draw into the drawing panel
		theDrawingPanel.getGraphics().drawImage(offscreenImage,0,0,theDrawingPanel);   		
	}
     
    
	// Override update() for double buffering
	public void update(Graphics g)
	{
		paint(g);
	}


	public Insets insets()
	{
		return new Insets(10,10,10,10);
	}

 	
 	// handle GUI events
    public boolean action(Event e, Object arg)
	{
		
       	if(e.target == minusdx)
      	{
       		if( -20*step < h)
      		{
      			h -= step;
      			paint(theDrawingPanel.getGraphics());

      		}
      		return true;
      	}
      	else if(e.target == plusdx)
      	{
      		if( h < 20*step )
      		{
 				h += step;
 				paint(theDrawingPanel.getGraphics());
 			}
      		
      		return true;
      	}
 
      	else if(e.target == aboutButton)
      	{
			theCards.show(this,"about");
      		return true;
      	}
      	else if(e.target == okBtn)
      	{
 			theCards.show(this,"main");
     		return true;
      	}

 		else return super.action(e,arg);	
	}

	
	// draw the axis
	public void drawAxis(Graphics g)
	{
 	  	g.setColor(Color.black);
		
		// x axis
		if( (theFunction.yMin < theFunction.yRealToPixel(0)) && (theFunction.yRealToPixel(0) < theFunction.yMax))
		{
	        g.drawLine(35,theFunction.yRealToPixel(theFunction.yRealToPixel(0)),580,theFunction.yRealToPixel(theFunction.yRealToPixel(0)));	
	        g.drawString("X",585,theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 4);
		}
		else
		{
	        g.drawLine(0,theFunction.yRealToPixel(theFunction.yMin),580,theFunction.yRealToPixel(theFunction.yMin));	
	        g.drawString("X",585,theFunction.yRealToPixel(theFunction.yMin) + 4);
		}

        // y axis
        g.drawLine(40,20,40,500);       
        g.drawString("Y",38,17);
        
	}


	// draw all the necessary labels
	public void drawLabels(Graphics g)
	{
		g.setColor(Color.black);
		
	 	//stepLabel.setText("h step size =" + String.valueOf(step - ((1E3 * step) % 1) / 1E3));
	 	sliderLabel.setText("h = " + String.valueOf(h - ((1E8 * h) % 1) / 1E8));
	 	if( (h - ((1E8 * h) % 1) / 1E8) != 0.0)
	 	{
			secantSlopeLabel.setText("Secant Slope =" + String.valueOf( secantSlope - ((1E6 * secantSlope) % 1) / 1E6));
			errorLabel.setText("Error = " + String.valueOf( realSlope - secantSlope) );
		}
		else
		{
			secantSlopeLabel.setText("Secant Slope = UNDEFINED" );
			errorLabel.setText("Error = UNDEFINED" );
		}

		realSlopeLabel.setText("Tangent Slope = " + String.valueOf(realSlope - ((1E6 * realSlope) % 1) / 1E6));
	}

	
	// draw limits in the proper places
	public void drawLimits(Graphics g)
	{
      	g.setColor(Color.red);
		
		// x limit		
		if( (theFunction.yMin < theFunction.yRealToPixel(0)) && (theFunction.yRealToPixel(0) < theFunction.yMax))
		{			
			g.drawString(String.valueOf(last),theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 14);
			g.drawLine(theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2);
			
			g.drawString(String.valueOf(xTangent), theFunction.xRealToPixel(xTangent), theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 14);
			g.drawLine(theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2);
			
			g.drawString(String.valueOf(xTangent+h), theFunction.xRealToPixel(xTangent+h), theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 28);
			g.drawLine(theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2);
		}
		else
		{
			g.drawString(String.valueOf(last),theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) + 14);
			g.drawLine(theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) + 2);
			
			g.drawString(String.valueOf(xTangent), theFunction.xRealToPixel(xTangent), theFunction.yRealToPixel(theFunction.yMin) + 14);
			g.drawLine(theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yMin) + 2);
			
			g.drawString(String.valueOf(xTangent+h), theFunction.xRealToPixel(xTangent+h), theFunction.yRealToPixel(theFunction.yMin) + 28);
			g.drawLine(theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yMin) + 2);
		}
		
		// y limit
		theFunction.xvar.set_value(last);
		g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3);
		g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value()));
		
		theFunction.xvar.set_value(xTangent);
		g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3);
		g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value()));
		
		theFunction.xvar.set_value(xTangent+h);
		g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3);
		g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value()));
				
  	}

 	    
	public void drawSecant(Graphics g)
	{
		int x1,x2,x3,x4,y1,y2,y3,y4;
		
		
		x1 = theFunction.xRealToPixel(xTangent);
		y1 = theFunction.yRealToPixel(theFunction.value(xTangent));
		
		if( (h - ((1E8 * h) % 1) / 1E8) != 0.0)
		{
			g.setColor(Color.darkGray);
			
			secantSlope = (theFunction.value(xTangent+h)-theFunction.value(xTangent))/h;
			
			x2 = theFunction.xRealToPixel(xTangent+h);
			y2 = theFunction.yRealToPixel(theFunction.value(xTangent+h));
			
			if(h > 0)
			{
				x3 = theFunction.xRealToPixel(last);
				y3 = theFunction.yRealToPixel( secantSlope*(last-xTangent)+theFunction.value(xTangent));
				x4 = theFunction.xRealToPixel(first);
				y4 = theFunction.yRealToPixel( secantSlope*(first-xTangent)+theFunction.value(xTangent));
			}
			else
			{
				x3 = theFunction.xRealToPixel(first);
				y3 = theFunction.yRealToPixel( secantSlope*(first-xTangent)+theFunction.value(xTangent));
				x4 = theFunction.xRealToPixel(last);
				y4 = theFunction.yRealToPixel( secantSlope*(last-xTangent)+theFunction.value(xTangent));
			
			}
			
			g.drawLine( x1, y1, x2, y2);
						
			g.drawLine(	x2, y2, x3, y3);
						
			g.drawLine(	x1, y1, x4, y4);
		}
	
		 //draw the tangent line
        g.setColor(Color.red);
        drawDashedLine(x1, y1, theFunction.xRealToPixel(first), theFunction.yRealToPixel(realSlope*(first-xTangent)+theFunction.value(xTangent)),g);
        drawDashedLine(x1, y1, theFunction.xRealToPixel(last), theFunction.yRealToPixel(realSlope*(last-xTangent)+theFunction.value(xTangent)),g);
				
				
	}
	
	// Return info for an about box browser option
	public String getAppletInfo()
	{
		return "Fixed Point Java Applet by Bill Ziemer & Brendon Cheves";
	}

	
	// Called from JavaScript only
	public void drawFunction(String f, String g, String s)
	{
		theFunction.parse(f);
		Double gWrap = new Double(g);
		Double sWrap = new Double(s);
		xTangent = gWrap.doubleValue();
		step = sWrap.doubleValue();
		theFunction.establishScale(xTangent-20*step,xTangent+20*step);
		first = theFunction.xMin;
		last = theFunction.xMax;		
		h = 12*step;
	
		realSlope = (theFunction.value(xTangent+1E-5)-theFunction.value(xTangent-1E-5))/2E-5;
		
		//paint(theDrawingPanel.getGraphics());
		repaint();
	}
	
	
	// Unitl we can get CoolGraphics.class working, got to put these cool utilities here
	public void drawArrowheadLine(int x1, int y1, int x2, int y2, Graphics g)
	{
		int a = 0;
		int arrowW = 26, arrowH = 26;
		int arrowX, arrowY;
		
		// draw the line first
		g.drawLine(x1, y1, x2, y2);
		
		// calculate arrowhead locations
		arrowX = x2 - (arrowW / 2);
		arrowY = y2 - (arrowH / 2);
		
		if(x1 == x2)
		{
			if (y2 > y1)
			{
				a = 270;
			}
			else
			{
				a = 90;
			}
		}
		else
		{
			a = (int)Math.atan((double)(y2 - y1) / (double)(x2 - x1));	// -90 to 90
			if(x2 < x1)
			{
				a = 180 - a;
			}
		}

		// draw the arrowhead
		g.fillArc(arrowX, arrowY, arrowW, arrowH, a + 165, 30);
	}

	public void drawDashedLine(int x1, int y1, int x2, int y2, int dashLength, Graphics g)
	{
		dashedLine(x1, y1, x2, y2, dashLength,g);
	}
	
	public void drawDashedLine(int x1, int y1, int x2, int y2, Graphics g)
	{
		dashedLine(x1, y1, x2, y2, 4,g);
	}

	private void dashedLine(int x1, int y1, int x2, int y2, int dashLength, Graphics g)
	{
		float m;
		int i, tmp;
		if(x2 < x1)
		{
			tmp = x1;
			x1 = x2;
			x2 = tmp;
			
			tmp = y1;
			y1 = y2;
			y2 = tmp;
		}
		
		if(x2 != x1)
		{
			m = (float)(y2 - y1) / (float)(x2 - x1);
			
			for(i = x1; i < x2; i += (2 * dashLength))
			{
				g.drawLine( i, (int)((m * (i - x1)) + y1),  (int)(i + Math.sqrt(dashLength / (1 + (m*m)))), (int)((m * ((i + Math.sqrt(dashLength / (1 + (m*m)))) - x1)) + y1));
			}
			
			// guarantee last dash
			if( (i * 2 * dashLength + x1 < x2))
			{
				g.drawLine( i * 2 * dashLength + x1, (int)((m * (i * 2 * dashLength)) + y1),  x2, y2);
			}
				
				
		}
		else	// vertical
		{
			if(y2 < y1)
			{
				tmp = y1;
				y1 = y2;
				y2 = tmp;
			}
			
			for(i = y1; i < y2; i += (2 * dashLength))
			{
				g.drawLine( x1, i, x1, i + dashLength);
			}
			
			// guarantee last dash	
			if( (i * 2 * dashLength + y1 < y2))
			{
				g.drawLine( x1, i * 2 * dashLength + y1,  x1, y2);
			}
		}
	}


}


