import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import javax.swing.*;
import javax.swing.event.*;

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

    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
    private double deltaTheta = 0.5;
    private int xAxisAngle = 75;  // degrees
    int halfway;
    
    private int wireRectWidth = 125;
    private int wireRectHeight = 90;
    //static Point origin = new Point(150,220);
    private Point origin = new Point(370,220);
    private int zAxisLength = 125;
    private int yAxisLength = 150;
    private int xAxisLength = 150;
    private double PIOverOneEighty = Math.PI/180.0;
    private double PIhalf = Math.PI/2.0;

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

    
    private 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 JSlider 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 = state.s540;
       mainHeight = state.s530 - state.s60;
       //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);
       
        wireRectWidth = state.s125;
        wireRectHeight = state.s90;
        origin = new Point(state.s370,state.s220);
        zAxisLength = state.s125;
        yAxisLength = state.s150;
        xAxisLength = state.s150;
        PIOverOneEighty = Math.PI/180.0;
        PIhalf = Math.PI/2.0;
        graphOrigin = new Point(state.s30,state.s330);
       
       getPoints();
       getGraphPoints();
       icurrent = ix.length/4;


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

       //start stop clock
       ssc.setBounds(state.s318,state.s480,state.s194,state.s60); 
       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);
        
        int shiftx, shifty;
        // To control positioning of slider to
        // draw the faster/slower arrow tips
        //On top of clock
        shiftx = 0; 
        shifty = 0; 
    
        // On Left of cloack
        shiftx = state.s250;
        shifty = state.s62;
	
        slider = new JSlider(SCROLLMIN,SCROLLMAX);
        //slider = new Scrollbar(Scrollbar.HORIZONTAL,200,1,SCROLLMIN,SCROLLMAX);
	add(slider);

	//slider to change animation speed
	slider.setBounds(state.s290-shiftx,state.s440+shifty,state.s250,state.s15);
	fast.setBounds(state.s479-shiftx,state.s458+shifty,state.s40,state.s15);
	slow.setBounds(state.s313-shiftx,state.s458+shifty,state.s100,state.s15);
        /*
	slider.setBackground(Color.white);
	//panels for slider
	Panel slide0 = new Panel();
	    slide0.setBackground(Color.cyan);
	    add(slide0);
	    slide0.setBounds(state.s290-shiftx-1,state.s440+shifty-1,state.s250+2,state.s15+2);	
	Panel slide1 = new Panel();
	    slide1.setBackground(Color.black);
	    add(slide1);
	    slide1.setBounds(state.s290-shiftx-2,state.s440+shifty-2,state.s250+4,state.s15+4);
	*/

	slider.addChangeListener(this);
    }
 

   
    public synchronized void stateChanged(ChangeEvent 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] + state.s20;
    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));


    int shiftx, shifty;
        // To control positioning of slider to
        // draw the faster/slower arrow tips
        //On top of clock
        shiftx = 0; 
        shifty = 0; 
    
        // On Left of cloack
        shiftx = state.s250;
        shifty = state.s62;
    
    g.setColor(Color.blue);
    MaestroG.drawArrowtip(state.s297-shiftx,state.s465+shifty,8,g);
    MaestroG.drawArrowtip(state.s305-shiftx,state.s465+shifty,8,g);
    
    g.setColor(maroon);
    MaestroG.drawArrowtip(state.s525-shiftx,state.s465+shifty,7,g);
    MaestroG.drawArrowtip(state.s533-shiftx,state.s465+shifty,7,g);
  }



  private void drawGraphAxis(Graphics g) {
    // Draw x and y axis:
    g.setColor(axisColor);
    drawLineThick(g,(double)graphOrigin.x, (double)(graphOrigin.y-state.s118),(double)graphOrigin.x, (double)(graphOrigin.y + state.s118), state.s1, axisColor);
    drawLineThick(g,(double)graphOrigin.x, (double)(graphOrigin.y),(double)(graphOrigin.x+state.s218), (double)(graphOrigin.y), state.s1, axisColor);
    g.setFont(new Font("Serif",Font.BOLD, state.font18));
    g.drawString("time",graphOrigin.x + state.s230,
                 graphOrigin.y + (int)(0.5*fontHeight));
    
    Arrows.drawDblUpArrow(g,graphOrigin.x,graphOrigin.y - state.s120, state.sfactor);
    Arrows.drawDblDownArrow(g,graphOrigin.x,graphOrigin.y + state.s120, state.sfactor);
    Arrows.drawDblRightArrow(g,graphOrigin.x + state.s220,graphOrigin.y, state.sfactor);
    g.drawString("time",graphOrigin.x + state.s230,
                 graphOrigin.y + (int)(0.5*fontHeight));
  }



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

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

  public synchronized void paintAnimationStuff(Graphics g) {
    g.setColor(wireColor);
    //NEW line:
    drawLineThick(g,(double)origin.x, (double)(origin.y),(double)origin.x, (double)(origin.y - wireRectHeight), state.s3, wireColor);
    drawLineThick(g,(double)origin.x, (double)(origin.y - wireRectHeight),(double)topOuter[current].x, (double)(topOuter[current].y), state.s3, wireColor);
    drawLineThick(g,(double)topOuter[current].x, (double)(topOuter[current].y),(double)topOuter[current].x, (double)(topOuter[current].y+wireRectHeight), state.s3, wireColor);
    drawLineThick(g,(double)topOuter[current].x, (double)(topOuter[current].y+wireRectHeight),(double)origin.x, (double)origin.y, state.s3, wireColor);
    
    // draw the current vector:
    g.setFont(new Font("Serif",Font.BOLD, state.font18));
    g.setColor(Color.red);
    g.drawString("I",origin.x+state.s20,origin.y-state.s35);
    if (currentIntensity[current] != RedColors.levels[10]) {
      //g.setColor(currentIntensity[current]);
      //drawLine(g,origin.x+state.s10,origin.y-state.s27,origin.x+state.s10,
      //         origin.y-wireRectHeight+state.s35);
      
      drawLineThick(g,(double)(origin.x+state.s10), (double)(origin.y-state.s27),(double)(origin.x+state.s10), (double)(origin.y-wireRectHeight+state.s35), state.s1, currentIntensity[current]);

      //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+state.s10,origin.y-wireRectHeight+state.s35, state.sfactor);
      else
          Arrows.drawDblDownArrow(g,origin.x+state.s10,origin.y-state.s27, state.sfactor);
    }

    //if (!ssc.IsStop) {
    //    //g.setColor(Color.black);
    //    g.setColor(Color.orange);
    //    g.fillOval(ix[icurrent]-3,iy[icurrent]-3,6,6);
    //}
    
    if (!ssc.IsStop) {g.setColor(Color.yellow);
        g.fillOval(ix[current]-state.s4,iy[current]-state.s4,state.s9,state.s8);
        g.setColor(Color.black);
        g.drawOval(ix[current]-state.s4,iy[current]-state.s4,state.s9,state.s8);
    }
  }

  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
    drawLineThick(g,(double)origin.x, (double)(origin.y),(double)origin.x, (double)(origin.y-zAxisLength), state.s1, axisColor);
    
    //drawLine(g,origin.x,origin.y,origin.x+yAxisLength,origin.y); // y-axis
    drawLineThick(g,(double)origin.x, (double)(origin.y),(double)(origin.x+yAxisLength), (double)(origin.y), state.s1, axisColor);
    
    //drawLine(g,origin.x,origin.y,xAxisEndpt.x,xAxisEndpt.y); // x-axis
    drawLineThick(g,(double)origin.x, (double)(origin.y),(double)(xAxisEndpt.x), (double)(xAxisEndpt.y), state.s1, axisColor);
    
    Arrows.drawDblUpArrow(g,origin.x,origin.y-zAxisLength, state.sfactor);
    g.setFont(new Font("Serif",Font.BOLD, state.font18));
    g.drawString("z",origin.x-(int)(0.5*fm.stringWidth("z")),
                 origin.y-zAxisLength-5);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength,origin.y, state.sfactor);
    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);
    drawLineThick(g,(double)(xAxisEndpt.x-state.s40), (double)(origin.y+state.s20),(double)(origin.x+yAxisLength-state.s10), (double)(origin.y+state.s20), state.s1, Color.blue);
    
    //drawLine(g,xAxisEndpt.x-40,origin.y-20,origin.x+yAxisLength-20,origin.y-20);
    drawLineThick(g,(double)(xAxisEndpt.x-state.s40), (double)(origin.y-state.s20),(double)(origin.x+yAxisLength-state.s10), (double)(origin.y-state.s20), state.s1, Color.blue);
    
    //drawLine(g,xAxisEndpt.x-40,origin.y-60,origin.x+yAxisLength-20,origin.y-60);
    drawLineThick(g,(double)(xAxisEndpt.x-state.s40), (double)(origin.y-state.s60),(double)(origin.x+yAxisLength-state.s10), (double)(origin.y-state.s60), state.s1, Color.blue);
    
    
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-state.s8,origin.y+state.s20, state.sfactor);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-state.s8,origin.y-state.s20, state.sfactor);
    Arrows.drawDblRightArrow(g,origin.x+yAxisLength-state.s8,origin.y-state.s60, state.sfactor);
    g.drawString("B",origin.x+yAxisLength,origin.y-state.s60);
    //
    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 static void drawLineThick(Graphics g, double x1, double y1, double x2, double y2, int thick, Color color){
	
        Graphics2D g2d = (Graphics2D)g;
        g2d.setPaint(color);
        g2d.setStroke(new BasicStroke(thick,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND));
        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        Line2D.Double line = new Line2D.Double(x1,y1,x2,y2);
        g2d.draw(line);
  
        g2d.setStroke(new BasicStroke(1));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    
    public static void drawLineThickB(Graphics g, double x1, double y1, double x2, double y2, int thick, int pat1, int pat2, Color color){
	
        Graphics2D g2d = (Graphics2D)g;
        g2d.setPaint(color);
        float[] dashPattern = {pat1,pat2};
        g2d.setStroke(new BasicStroke(thick,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER,10.0F,dashPattern,0));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        Line2D.Double line = new Line2D.Double(x1,y1,x2,y2);
        g2d.draw(line);
 
        g2d.setStroke(new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }

  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();
  }
  
}
