import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.net.URL;
import java.text.*;
import java.util.Map;
import java.util.Hashtable;
import java.util.jar.Attributes;
//import javax.swing.*;

public class Mod2Plot extends Panel implements MouseListener, Runnable,
                                               AdjustmentListener {

    Mod2State state;

    Point[] topOuter;
    Color[] currentIntensity;
    int[] arrowDirection;
    static int ARROW_UP = 1;
    static int ARROW_DOWN = 2;
    
    int current = 0;
    Point xAxisEndpt;
    Image main;
    Graphics imG;
    int mainWidth, mainHeight;
    
    // Need 2 different delta thetas because much shorter screen 
    // difference for the wire rectangle to travel through the 
    // y and x axis, than the x and -ve y axis, EVEN THOUGH the 
    // real-world distance is the same.  The solution is to use a small
    // delta theta for the short screen distances, and (3 times) larger
    // delta theta for the long screen distances.
    //
    // use short for 0 <= theta <= PI and PI <= theta < 3PI/2
    // use long for PI/2 <= theta < PI and 3PI/2 <= theta < 0
    static double deltaTheta = 0.5;
    static int xAxisAngle = 75;  // degrees
    int halfway;
    
    static int wireRectWidth = 125;
    static int wireRectHeight = 90;
    //static Point origin = new Point(150,220);
    static Point origin = new Point(370,220);
    static int zAxisLength = 125;
    static int yAxisLength = 150;
    static int xAxisLength = 150;
    static double PIOverOneEighty = Math.PI/180.0;
    static double PIhalf = Math.PI/2.0;

    //static Point graphOrigin = new Point(70,330);
    static Point graphOrigin = new Point(30,330);
    int[] ix, iy;
    int iLabelX, iLabelY;
    static Color iColor = Color.red;    
    int icurrent = 0;

    
    static Font boldFont = new Font("Serif",Font.BOLD, 18);
    
    int yBorder, yOffset;
    
    //AnimationTimer timer = new AnimationTimer(this, 50);
    
    
    private Thread tron;
    public boolean IsTronRunning;
    public boolean ThreadStarted;
    private int SleepTime = 50;  // was 25

    private double amplitudeScaler = 100.0;
    
    //static Color bgColor = new Color(194,235,255);
    private final Color bgColor = new Color(236,236,236);
    static Color axisColor = Color.black;
    static Color wireColor = Color.green.darker();
    
    FontMetrics fm;
    int fontHeight;
    Graphics2D g2d;
    
    
    public StartStopClock ssc;
    
    private Label fast, slow;   
    private static final Color maroon = new Color(185,50,50);
    public Scrollbar slider;
    private int SCROLLMIN=0, SCROLLMAX=250;
    


   public synchronized void initialPlot() {
       state.reset();
       ssc.clockcanvas.setStatus(0,state.NTime,state.dtime);
       ssc.clockcanvas.reset();


       //mainWidth = origin.x+yAxisLength+40;
       //mainHeight = xAxisEndpt.y+60+fontHeight+10;
       mainWidth = 540;
       mainHeight = 530;
       //main = createImage(mainWidth, mainHeight);
       //imG = main.getGraphics();

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice(); 
        GraphicsConfiguration gc = gs.getDefaultConfiguration();
        main = gc.createCompatibleImage(mainWidth, mainHeight, Transparency.TRANSLUCENT);
        imG = main.getGraphics();

       g2d = (Graphics2D)imG;
       g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);


       paintImage(imG);
       paintAnimationStuff(imG);
       repaint();
   }


   public Mod2Plot(Mod2State state) {
       super();
       this.state = state;
       IsTronRunning = false;
       ThreadStarted = false;
       setLayout(null);
       setBackground(bgColor);
       getPoints();
       getGraphPoints();
       icurrent = ix.length/4;


       ssc  = new StartStopClock();
       ssc.button1.setEnabled(true);
       ssc.button2.setEnabled(true);
       add(ssc);

       //start stop clock
       //ssc.setBounds(200,470,170,60);
       ssc.setBounds(330,480,170,60);
       ssc.button1.addMouseListener(this);
       ssc.button2.addMouseListener(this);


       setupSpeedAdjust();


       IsTronRunning = false;
       ThreadStarted = false;

       
       // Get font sizes:
       setFont(boldFont);
       fm = getFontMetrics(getFont());
       fontHeight = fm.getHeight();
  }


    private void setupSpeedAdjust() {
        	
	fast = new Label("faster",Label.RIGHT);
	fast.setFont(TheFonts.sanSerif11);
	fast.setBackground(bgColor);
	fast.setForeground(maroon);
	add(fast);
	
	slow = new Label("slower",Label.LEFT);
	slow.setFont(TheFonts.sanSerif11);
	slow.setBackground(bgColor);
	slow.setForeground(Color.blue);
	add(slow);
	
	
	slider = new Scrollbar(Scrollbar.HORIZONTAL,200,1,SCROLLMIN,SCROLLMAX);
	add(slider);
		
	//slider to change animation speed
	slider.setBounds(290,440,250,15);
	//fast.setBounds(479,458,40,10);
	//slow.setBounds(313,458,100,10);
	fast.setBounds(479,458,40,15);
	slow.setBounds(313,458,100,15);
	
	//panels for slider
	Panel slide0 = new Panel();
	    slide0.setBackground(Color.cyan);
	    add(slide0);
	    slide0.setBounds(289,439,252,17);	
	Panel slide1 = new Panel();
	    slide1.setBackground(Color.black);
	    add(slide1);
	    slide1.setBounds(288,438,254,19);

	slider.addAdjustmentListener(this);
    }
 

   
    public synchronized void adjustmentValueChanged(AdjustmentEvent evt){
	  if(evt.getSource()==slider){
              boolean repaint = !IsTronRunning;
              IsTronRunning = false;

              SleepTime = SCROLLMAX - slider.getValue();
              if (SleepTime < 5) SleepTime = 5;
              //amplitudeScaler = 100 * (50/SleepTime);
              amplitudeScaler = 100 * (250.0/(SleepTime+200));
              getGraphPoints();

              if (repaint) {
                  paintImage(imG);
                  repaint();
              } else IsTronRunning = true;
	  }
    }



   public void mouseClicked(MouseEvent evt){mouseHandler(evt);}
   public void mouseEntered(MouseEvent evt){;}
   public void mouseExited(MouseEvent evt){;}
   public void mousePressed(MouseEvent evt){;}
   public void mouseReleased(MouseEvent evt){;}

   private synchronized void mouseHandler(MouseEvent evt){
       if(evt.getSource() == ssc.button1){
           IsTronRunning = true;
           if(!ThreadStarted){
               start();
               ThreadStarted = true;
               ssc.button1.setEnabled(false);
               ssc.button2.setEnabled(true);
           }
           else{
               ssc.button1.setEnabled(false);
               ssc.button2.setEnabled(true);
               notify();
           }
           //c1.setEnabled(false);
           repaint();
       }
       if(evt.getSource() == ssc.button2){
           IsTronRunning = false;
           ssc.button2.setEnabled(false);
           ssc.button1.setEnabled(true);
           //c1.setEnabled(true);
           repaint();
       }
   }



   public void start(){
       if(tron == null){
           tron = new Thread(this);
           tron.start();
       }
   }


   public void run(){

       while(true){
           if(IsTronRunning){
               animate();
               ssc.clockcanvas.increment();
               state.increment();
           }
           try{
               Thread.sleep(SleepTime);
               synchronized(this){
                   while(!IsTronRunning){wait();}
               }
           }
           catch(InterruptedException e){e.printStackTrace();}
       }
   }




  private synchronized void getPoints() {
    // Get the endpoint for the x-axis:
    int xComp = (int) (Math.cos(xAxisAngle*Math.PI/180.0)*xAxisLength);
    int yComp = (int) (Math.sin(xAxisAngle*Math.PI/180.0)*xAxisLength);
    xAxisEndpt = new Point(origin.x - xComp, origin.y + yComp);
    // get the top-outer points of the moving rectangle
    int npts = (int) (360/deltaTheta);
    current = npts/4;
    halfway = npts/2;
    topOuter = new Point[npts];
    currentIntensity = new Color[npts];
    arrowDirection = new int[npts];

    double thetaInRadians;
    Point circleOrigin = new Point(origin.x, origin.y - wireRectHeight);
    double theta = 0.0;
    for (int i = 0; i < npts; i++, theta+=deltaTheta) {
      thetaInRadians = theta * PIOverOneEighty;
      //xComp = (int) (wireRectWidth * Math.cos(thetaInRadians));
      //yComp = (int) (wireRectWidth * Math.sin(thetaInRadians));
      xComp = (int) (wireRectWidth * Math.cos(thetaInRadians-PIhalf));
      yComp = (int) (wireRectWidth * Math.sin(thetaInRadians-PIhalf));
      
      topOuter[i] = new Point(circleOrigin.x+xComp, circleOrigin.y-yComp);

      if (theta >= 0.0 && theta < 90.0) {
          currentIntensity[i] = RedColors.getSinLevel(1.0-((90.0-theta)/90.0));
          arrowDirection[i] = ARROW_UP;
      } else if (theta >= 90.0 && theta < 180.0) {
          currentIntensity[i] = 
              RedColors.getSinLevel(1.0-((theta-90.0)/90.0));
          arrowDirection[i] = ARROW_UP;
      } else if (theta >= 180.0 && theta < 270.0) {
          currentIntensity[i] = 
              RedColors.getSinLevel(1.0-((270-theta)/90.0));
          arrowDirection[i] = ARROW_DOWN;
      } else {
          currentIntensity[i] = 
              RedColors.getSinLevel(1.0-((theta-270)/90.0));
          arrowDirection[i] = ARROW_DOWN;
      }
    }
  }




  private synchronized void getGraphPoints() {
    int iForILabel = 0;
    //int npts = (360/deltaTheta) + 1;
    int npts = (int) (360/deltaTheta);
    //icurrent = npts/4;
    ix = new int[npts];
    iy = new int[npts];
    double thetaInRadians, xPoint;
    double oneOverOneEighty = 1.0/180.0;
    double theta = 0.0;
    for (int i = 0; i < npts; i++, theta+=deltaTheta) {
      if (theta == 90.0) iForILabel = i;
      thetaInRadians = theta * PIOverOneEighty;
      //iy[i] = (int) (100 * Math.sin(thetaInRadians));
      iy[i] = (int) (amplitudeScaler * Math.sin(thetaInRadians));
      xPoint = i*deltaTheta*oneOverOneEighty;
      ix[i] = (int) (xPoint*100); // scale x so 360 degress spans 200;
    }

    // NOW push the sine wave DOWN on the Y-axis, so that the sine
    //  wave appears in the middle of the screen, rather that at the TOP
    for (int i = 0; i < iy.length; i++) {
      iy[i] += graphOrigin.y;
    }
    flipCoordinates(iy,graphOrigin.y);

    // NOW shift the X-axis over by xMin to make room for the vertical
    //  axis line.
    for (int i = 0; i < ix.length; i++) {
      ix[i] += graphOrigin.x;
    }
    iLabelX = ix[iForILabel] + 20;
    iLabelY = iy[iForILabel];
  }

  

  private synchronized void flipCoordinates(int[] coords, int flipAround) {
    int delta;
    for (int i = 0; i < coords.length; i++) {
      delta = coords[i] - flipAround;
      coords[i] = flipAround - delta;
    }
  }



  public synchronized void paintImage(Graphics g) {
      //g.clearRect(0,0,mainWidth,mainHeight);

      g.setColor(bgColor);
      g.fillRect(0,0,mainWidth,mainHeight);

    // Draw x and y axis:
    drawAxis(g);
    g.setColor(wireColor);
    drawLine(g,origin.x,origin.y,origin.x, origin.y - wireRectHeight);

    drawGraphAxis(g);
    g2d.setStroke(new BasicStroke(3,BasicStroke.CAP_ROUND,
                                  BasicStroke.JOIN_ROUND));

    g.setColor(iColor);
    g.drawString("I",iLabelX,iLabelY);
    g2d.drawPolyline(ix,iy,ix.length);

    g2d.setStroke(new BasicStroke(1));


    // Draw the faster/slower arrow tips
    g.setColor(Color.blue);
    MaestroG.drawArrowtip(297,462,8,g);
    MaestroG.drawArrowtip(305,462,8,g);
    
    g.setColor(maroon);
    MaestroG.drawArrowtip(524,462,7,g);
    MaestroG.drawArrowtip(532,462,7,g);
  }



  private void drawGraphAxis(Graphics g) {
    // Draw x and y axis:
    g.setColor(axisColor);
    drawLine(g,graphOrigin.x,graphOrigin.y - 120,
             graphOrigin.x,graphOrigin.y + 120); // y-axis
    drawLine(g,graphOrigin.x,graphOrigin.y,
             graphOrigin.x + 220,graphOrigin.y); // x-axis
    Arrows.drawDblUpArrow(g,graphOrigin.x,graphOrigin.y - 120);
    Arrows.drawDblDownArrow(g,graphOrigin.x,graphOrigin.y + 120);
    Arrows.drawDblRightArrow(g,graphOrigin.x + 220,graphOrigin.y);
    g.drawString("time",graphOrigin.x + 230,
                 graphOrigin.y + (int)(0.5*fontHeight));
  }




  public void update(Graphics g) { paint(g); }

  public void paint(Graphics g) {
    g.drawImage(main,0,0,this);
  }

  public synchronized void paintAnimationStuff(Graphics g) {
    g.setColor(wireColor);
    //NEW line:
    drawLine(g,origin.x, origin.y, origin.x, origin.y - wireRectHeight); //NEW
    drawLine(g,origin.x, origin.y - wireRectHeight,
               topOuter[current].x,topOuter[current].y);
    drawLine(g,topOuter[current].x,topOuter[current].y,
               topOuter[current].x,topOuter[current].y+wireRectHeight);
    drawLine(g,topOuter[current].x,topOuter[current].y+wireRectHeight,
               origin.x,origin.y);
    // draw the current vector:
    g.setColor(Color.red);
    //g.drawString("I",origin.x+10,origin.y-35);
    g.drawString("I",origin.x+20,origin.y-35);
    if (currentIntensity[current] != RedColors.levels[10]) {
      g.setColor(currentIntensity[current]);
      //drawLine(g,origin.x,origin.y-7,origin.x,origin.y-wireRectHeight+7);
      drawLine(g,origin.x+10,origin.y-27,origin.x+10,
               origin.y-wireRectHeight+35);

      //if (current <= halfway)
      //  Arrows.drawDblDownArrow(g,origin.x,origin.y-7);
      //else
      //  Arrows.drawDblUpArrow(g,origin.x,origin.y-wireRectHeight+7);
      
      if (arrowDirection[current] == ARROW_UP) 
          //Arrows.drawDblUpArrow(g,origin.x,origin.y-wireRectHeight+7);
          Arrows.drawDblUpArrow(g,origin.x+10,origin.y-wireRectHeight+35);
      else
          //Arrows.drawDblDownArrow(g,origin.x,origin.y-7);
          Arrows.drawDblDownArrow(g,origin.x+10,origin.y-27);
    }

    if (!ssc.IsStop) {
        //g.setColor(Color.black);
        g.setColor(Color.orange);
        g.fillOval(ix[icurrent]-3,iy[icurrent]-3,6,6);
    }
  }

  private void drawAxis(Graphics g) {
    // Draw x and y axis:
    g.setColor(axisColor);
    drawLine(g,origin.x,origin.y,origin.x,origin.y-zAxisLength); //z-axis
    drawLine(g,origin.x,origin.y,origin.x+yAxisLength,origin.y); // y-axis
    drawLine(g,origin.x,origin.y,xAxisEndpt.x,xAxisEndpt.y); // x-axis
    Arrows.drawDblUpArrow(g,origin.x,origin.y-zAxisLength);
    g.drawString("z",origin.x-(int)(0.5*fm.stringWidth("z")),
                 origin.y-zAxisLength-5);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength,origin.y);
    g.drawString("y",origin.x+yAxisLength+5,origin.y+(fontHeight/2));
    Point[] xArrow =
        Arrows.getDblArrowHeadPts(xAxisEndpt.x,xAxisEndpt.y,
                                  origin.x, origin.y);
    g.drawString("x",xAxisEndpt.x-fm.stringWidth("x"),
                 xAxisEndpt.y + fontHeight);
    Arrows.drawArrowHead(g,xArrow);
    //
    g.setColor(Color.blue);
    drawLine(g,xAxisEndpt.x-40,origin.y+20,origin.x+yAxisLength-20,origin.y+20);
    drawLine(g,xAxisEndpt.x-40,origin.y-20,origin.x+yAxisLength-20,origin.y-20);
    drawLine(g,xAxisEndpt.x-40,origin.y-60,origin.x+yAxisLength-20,origin.y-60);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-20,origin.y+20);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-20,origin.y-20);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-20,origin.y-60);
    g.drawString("B",origin.x+yAxisLength,origin.y-60);
    //
    g.setColor(axisColor);
  }

  private void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
      if (y1 != y2) {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1+1,y1,x2+1,y2);
      } else {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1,y1+1,x2,y2+1);
      }
  }

  public synchronized void animate() {
    current+=4;
    icurrent+=4;
    if (current >= topOuter.length) current = 0;
    if (icurrent >= ix.length) icurrent = 0;
    paintImage(imG);
    paintAnimationStuff(imG);
    repaint();
  }
  
}
