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

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

    Mod1State state;
    //static int deltaTheta = 5;
    private double deltaTheta = 0.5;
    int[] bx, by, ix, iy;
    int bLabelX, bLabelY, iLabelX, iLabelY;
    private int meterDeltaPhi = 60;
    private Point[][] meterArrowHeads;
    private Point[] meterEndPts;
    int current = 0;
    
    private int meterArrowLength = 20;
    private int meterArrowHalfLength = 10;
    private Point meterOrigin = new Point(119,147);
    private Point graphOrigin = new Point(70,330);
    static int numberColorLevels = 7;
    Image[] posMags, negMags;
    int[] magIndexes;
    Image negIm, posIm;
    
    static double PIOverOneEighty = Math.PI/180.0;
    
    int yBorder, yOffset;    

    private Thread tron;
    public boolean IsTronRunning;
    public boolean ThreadStarted;
    private int SleepTime = 50;  // was 25
        
    //static Color bgColor = new Color(194,235,255);
    private final Color bgColor = new Color(236,236,236);
    static Color axisColor = Color.black;
    static Color meterColor = Color.black;
    static Color bColor = Color.blue;
    static Color bPointColor = Color.blue;
    static Color iColor = Color.red;
    static Color iPointColor = Color.red;

    static Color color = new Color(2,126,237);
    
    Image main;
    int mainWidth, mainHeight;
    Graphics imG, imG_neg, imG_pos;
    private Font boldFont;
    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 Mod1Plot(Mod1State state) {
        super();
        this.state = state;
        IsTronRunning = false;
        ThreadStarted = false;
        setLayout(null);
        setBackground(bgColor);
        
        boldFont = new Font("Serif",Font.BOLD, state.font18);
        
        // Scale ammeter reference
        meterOrigin = new Point(state.s119,state.s147);
        meterArrowLength = state.s20;
        meterArrowHalfLength = state.s10;
        // Scale graph origin
        graphOrigin = new Point(state.s175,state.s330);

        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;
       
        //posMags=ImageDissolver.createImages(basePosMag,numberColorLevels,this);
        //negMags=ImageDissolver.createImages(baseNegMag,numberColorLevels,this);
       
        // Get font sizes:
        setFont(boldFont);
        fm = getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        
        //Determines the points for plots
        getPoints();
    }
    
    public synchronized void initialPlot() {
        state.reset();
        ssc.clockcanvas.setStatus(0,state.NTime,state.dtime);
        ssc.clockcanvas.reset();
       
        mainWidth = graphOrigin.x + state.s480; // was + 420
        mainHeight = graphOrigin.y + state.s200 - 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);

        //negIm = createImage(115,115);  // was 125X125
        negIm = gc.createCompatibleImage(state.s115,state.s115);
        imG_neg = negIm.getGraphics();
        //posIm = createImage(115,115);
        posIm = gc.createCompatibleImage(state.s115,state.s115);
        imG_pos = posIm.getGraphics();
       
        createNegImg();
        createPosImg();
        //createWireLoop();
       
        posMags=ImageDissolver.createImages(posIm,numberColorLevels,this);
        negMags=ImageDissolver.createImages(negIm,numberColorLevels,this);
       
        paintImage(imG);
        repaint();
    }


    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 JSlider(SCROLLMIN,SCROLLMAX);
	//slider = new Scrollbar(Scrollbar.HORIZONTAL,200,1,SCROLLMIN,SCROLLMAX);	
	add(slider);
        
        int shiftx = state.s250;
        int shifty = state.s62;
		
	//slider to change animation speed
	slider.setBounds(state.s290-shiftx,state.s440+shifty,state.s250,state.s15);
	//fast.setBounds(479,458,40,10);
	//slow.setBounds(313,458,100,10);
	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 void stateChanged(ChangeEvent evt){
	  if(evt.getSource()==slider){
		SleepTime = SCROLLMAX - slider.getValue();
                if (SleepTime < 5) SleepTime = 5;
	  }
    }

    private void createPosImg() {
        Graphics2D g2d = (Graphics2D)imG_pos;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        String s;
        int endingX;
        g2d.setColor(color);
        
        g2d.setStroke(new BasicStroke(2,BasicStroke.CAP_ROUND,
                                      BasicStroke.JOIN_ROUND));
        Arc2D.Double arc;

        for (int x = state.s5; x < state.s134; x+=state.s31) {
            for (int y = state.s4; y < state.s133; y+=state.s31) {
                arc = new Arc2D.Double(x,y,state.s11,state.s11,0,360,0);
                g2d.draw(arc);
            }
        }
        g2d.setColor(Color.black);
        for (int x = state.s5; x < state.s134; x+=state.s31) {
            for (int y = state.s4; y < state.s133; y+=state.s31) {
                g2d.fillOval(x+state.s4,y+state.s4,state.s3,state.s3);
            }
        }
        return;
    }

    private void createNegImg() {
        Graphics2D g2d = (Graphics2D)imG_neg;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        String s;
        int endingX;
        g2d.setColor(color);
        
        g2d.setStroke(new BasicStroke(2,BasicStroke.CAP_ROUND,
                                      BasicStroke.JOIN_ROUND));
        Arc2D.Double arc;

        for (int x = state.s5; x < state.s134; x+=state.s31) {
            for (int y = state.s4; y < state.s133; y+=state.s31) {
                arc = new Arc2D.Double(x,y,state.s11,state.s11,0,360,0);
                g2d.draw(arc);
            }
        }
        g2d.setColor(Color.black);
        for (int x = state.s5; x < state.s134; x+=state.s31) {
            for (int y = state.s4; y < state.s133; y+=state.s31) {
                g2d.drawLine(x+state.s2,y+state.s2,x+state.s8,y+state.s8);
                g2d.drawLine(x+state.s2,y+state.s8,x+state.s8,y+state.s2);
            }
        }
        return;
    }        
    
    private void createWireLoop() {
        String s;
        int endingX;
        imG.setFont(boldFont);
        imG.setColor(color);
        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        
        g2d.setStroke(new BasicStroke(4,BasicStroke.CAP_ROUND,
                                      BasicStroke.JOIN_ROUND));
        g2d.drawLine(state.s65,state.s40,state.s65,state.s145);
        g2d.drawLine(state.s65,state.s145,state.s107,state.s145); 
        
        /*
          Arc2D.Double arc = new Arc2D.Double(xCenter-Radius,
          yCenter-Radius,
          2*Radius,2*Radius,
          startAngle, deltaAngle, 0);
        */
        
        // Draw Ammeter
        Arc2D.Double arc1 = new Arc2D.Double(state.s107,state.s133,state.s25,state.s25,0,360,0);
        g2d.draw(arc1);
        
        g2d.drawLine(state.s135,state.s145,state.s175,state.s145);
        
        
        g2d.drawLine(state.s175,state.s145,state.s175,state.s103);
        g2d.drawLine(state.s175,state.s103,state.s270,state.s103);
        
        Arc2D.Double arc2 = new Arc2D.Double(state.s270,state.s5,state.s175,state.s175, 188,344, 0);
        g2d.draw(arc2);
        
        g2d.drawLine(state.s270,state.s82,state.s175,state.s82);
        g2d.drawLine(state.s175,state.s82,state.s175,state.s40);
        
        // top line with resistor:
        g2d.drawLine(state.s65,state.s40,state.s105,state.s40);
        
        // zig-zag
        g2d.drawLine(state.s105,state.s40,state.s109,state.s33);
        g2d.drawLine(state.s109,state.s33,state.s113,state.s46);
        g2d.drawLine(state.s113,state.s46,state.s117,state.s33);
        g2d.drawLine(state.s117,state.s33,state.s121,state.s46);
        g2d.drawLine(state.s121,state.s46,state.s125,state.s33);
        g2d.drawLine(state.s125,state.s33,state.s129,state.s46);
        g2d.drawLine(state.s129,state.s46,state.s132,state.s40);
        
        g2d.drawLine(state.s132,state.s40,state.s175,state.s40);
        
        
        // TEXT:
        g2d.setStroke(new BasicStroke(1));
        g2d.setColor(Color.black);
        
        s = STR.ITAL+"R"+STR.ENDITAL+STR.SUBITAL+"L"+STR.ENDSUBITAL;
        endingX = STR.displayString(s,imG,state.s107,state.s20);
        
        s = "Ammeter";
        endingX = STR.displayString(s,imG,state.s80,state.s180);
        
        g2d.setColor(Color.red);
        s = "I";
        endingX = STR.displayString(s,imG,state.s185,state.s131);
        
        g2d.setColor(Color.blue);
        s = "B";
        endingX = STR.displayString(s,imG,state.s350,state.s25);
    }

    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() {
    int iForBLabel = 0, iForILabel = 0;
    //int npts = (360/deltaTheta) + 1;
    int npts = (int)(360/deltaTheta);
    bx = new int[npts];
    by = new int[npts];
    ix = new int[npts];
    iy = new int[npts];
    meterEndPts = new Point[npts];
    meterArrowHeads = new Point[npts][3];
    magIndexes = new int[npts];
    double thetaInRadians, xPoint;
    double oneOverOneEighty = 1.0/180.0;
    int meterPhi;
    double theta = 0.0;
    for (int i = 0; i < npts; i++, theta+=deltaTheta) {
      if (theta == 0.0) iForBLabel = i;
      if (theta == 90.0) iForILabel = i;
      thetaInRadians = theta * PIOverOneEighty;
      by[i] = (int) (state.s100 * Math.cos(thetaInRadians));
      iy[i] = (int) (state.s100 * Math.sin(thetaInRadians));
      xPoint = i*deltaTheta*oneOverOneEighty;
      bx[i] = (int) (xPoint*state.s100); // scale x so 360 degress spans 200
      ix[i] = bx[i];

      // DRAW ANNETER NEEDLE
      // When sin = 0, meterPhi = 90, otherwise, meterPhi = 30;
      // (meterDeltaPhi = 60)
      meterPhi = 90 - (int)(Math.sin(thetaInRadians)*meterDeltaPhi);
      meterEndPts[i] = Arrows.getEndPoint(meterOrigin,meterPhi,
                                          meterArrowHalfLength);
      meterArrowHeads[i] = 
        Arrows.getDblArrowHeadPts(meterEndPts[i].x,meterEndPts[i].y,
                                  meterOrigin.x,meterOrigin.y);
      magIndexes[i] = 
        (int) (Math.cos(thetaInRadians)*(numberColorLevels-1));
      magIndexes[i] = Math.abs(magIndexes[i]);
    }

    // NOW push the sine waves 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 < by.length; i++) {
      by[i] += graphOrigin.y;
      iy[i] += graphOrigin.y;
    }
    flipCoordinates(by,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 < bx.length; i++) {
      bx[i] += graphOrigin.x;
      ix[i] += graphOrigin.x;
    }
    bLabelX = bx[iForBLabel] + state.s20;
    bLabelY = by[iForBLabel];
    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.setFont(boldFont);

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

    // Draw x and y axis:
    //g.drawImage(wireLoop,0,0,this);
    createWireLoop();
    drawAxis(g);
    // Draw axis tick marks:
    //drawAxisTicks(g);
        
        g2d.setStroke(new BasicStroke(state.s2,BasicStroke.CAP_ROUND,
                                      BasicStroke.JOIN_ROUND));

    g.setColor(bColor);
    g.drawString("B",bLabelX,bLabelY);
    //drawPolyline(g,bx,by,bx.length);
    g2d.drawPolyline(bx,by,bx.length);

    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
    int shiftx = state.s250;
    int 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);
  }

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

  public void drawTrplUpArrow(Graphics imG, int x, int y) {
    int[] arrowXPts = new int[3];
    int[] arrowYPts = new int[3];
    arrowXPts[0] = x;    arrowYPts[0] = y-1;
    arrowXPts[1] = x-state.s5;  arrowYPts[1] = y+state.s12;
    arrowXPts[2] = x+state.s5;  arrowYPts[2] = y+state.s12;
    imG.fillPolygon(arrowXPts,arrowYPts,3);
  }
  public void drawTrplDownArrow(Graphics imG, int x, int y) {
    int[] arrowXPts = new int[3];
    int[] arrowYPts = new int[3];
    arrowXPts[0] = x;    arrowYPts[0] = y;
    arrowXPts[1] = x-state.s5;  arrowYPts[1] = y - state.s12;
    arrowXPts[2] = x+state.s5;  arrowYPts[2] = y - state.s12;
    imG.fillPolygon(arrowXPts,arrowYPts,3);
  }

    public void TESTpaint(Graphics g) {
        int x=0, y=0;
        for (int i = 0; i < posMags.length; i++) {
            g.drawImage(posMags[i],x,y,this);
            x += posMags[i].getWidth(this);
            if (x >=390) {x = 0; y+=posMags[i].getHeight(this);}
        }
    }

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

  public synchronized void paintAnimationStuff(Graphics g) {
    g.setColor(Color.red);
    if (magIndexes[current] != numberColorLevels-1)
      if (iy[current] < graphOrigin.y) {
        drawTrplUpArrow(g,state.s176-1,state.s125 - state.s6);
      } else {
        drawTrplDownArrow(g,state.s176-1,state.s125 + state.s6);
      }
    if (by[current] < graphOrigin.y)
        //g.drawImage(posMags[magIndexes[current]],295,35,this);
        g.drawImage(posMags[magIndexes[current]],state.s302,state.s33,this);
    else
        //g.drawImage(negMags[magIndexes[current]],295,35,this);
        g.drawImage(negMags[magIndexes[current]],state.s302,state.s33,this);

    g.setColor(meterColor);
    drawLine(g,meterOrigin.x,meterOrigin.y,
             meterOrigin.x-(meterEndPts[current].x-meterOrigin.x),
             meterOrigin.y+(meterOrigin.y-meterEndPts[current].y));
    drawLine(g,meterOrigin.x,meterOrigin.y,
             meterEndPts[current].x,meterEndPts[current].y);
    Arrows.drawArrowHead(g,meterArrowHeads[current]);
    
    g.setColor(Color.cyan);
    g.fillOval(bx[current]-state.s4,by[current]-state.s4,state.s9,state.s8);
    g.setColor(Color.black);
    g.drawOval(bx[current]-state.s4,by[current]-state.s4,state.s9,state.s8);
    
    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,graphOrigin.x,graphOrigin.y - state.s120,
    //         graphOrigin.x,graphOrigin.y + state.s120); // y-axis
    //drawLine(g,graphOrigin.x,graphOrigin.y,
    //         graphOrigin.x + state.s220,graphOrigin.y); // x-axis
    
    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);
    
    
    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));
  }

  private void drawPolyline(Graphics g, int[] x, int[] y, int len) {
    for (int i = 0; i < (len-1); i++)
      drawLine(g,x[i],y[i],x[i+1],y[i+1]);
  }

  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++;
      current+=4;
      if (current >= bx.length) current = 0;
      paintImage(imG);
      paintAnimationStuff(imG);
      repaint();
  }
  
}
