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

// Module 1 Exercise 1
public class Mod1Plot extends Panel implements ChangeListener {
    
    Mod1State state;
    
    int yMin, yMax, xMin, xMax, yMid;
    int yBorder, yOffset, yExtra;
    
    //static int degreesPerX = 2;      // was 5
    //static int degreesPerX = 1;      // was 5
    //static double degreesPerX = 0.5;      // was 5
    
    static double degreesPerX = 2.0;      // was 5
    
    //static double newDegreesPerX = ((double)degreesPerX) / 180.0;
    static double newDegreesPerX = degreesPerX / 180.0;
    static int ncycles=3;
    //static int npts = ncycles*(360/degreesPerX);
    static int npts = (int) (ncycles*(360.0/degreesPerX));
    
    int[] xList1, yList1;  // only instantiate ONCE, in call to getPoints1()
    int npts2;
    int[] yList2;
    int npts2Index = 0;
    
    static double piOver8 = Math.PI/8;
    static double piOver4 = Math.PI/4;
    static double piOver2 = Math.PI/2;
    //static double oneEightyOverPI = 180.0/Math.PI;
    
    Image main;
    Graphics imG;
    int mainWidth, mainHeight;
    
    Color lineColor;
    
    double deltaY, deltaX;
    FontMetrics fm;
    int fontHeight, ticLength;
    
    String red1, red2, red3, red4, blue1, blue2, blue3, blue4;
    int endingX;
    
    protected boolean anyLine2 = false;
    
    private Font tmpFont = new Font("Serif",Font.PLAIN,12);
    //private static final Color bgColor = Color.lightGray;
    private static final Color bgColor = new Color(236,236,236);
    public int ScrollMax = 10000;
    public JSlider sliderx;
    private double redY, blueY, xSliderVal;
    private int redDotX, redDotY, blueDotX, blueDotY;
    boolean anyDot = false;
    DecimalFormat dec = new DecimalFormat("#.###");
    
    Graphics2D g2d;
    
    
    public Mod1Plot(Mod1State state) {
        super();
        this.state = state;
        tmpFont = new Font("Serif",Font.BOLD,state.font12);
    }
    
    public void initialPlot() {
        setLayout(null);
        setBackground(bgColor);

	sliderx = new JSlider(0,ScrollMax+1);
	//sliderx = new Scrollbar(Scrollbar.HORIZONTAL,0,1,0,ScrollMax+1);
	//sliderx.setValue((int)(ScrollMax/2);
        //sliderx.setBackground(Color.white);
	sliderx.setValue(0);
        redY = 5.0;
        blueY = 5.0;
        getRedAndBlueYText();

	add(sliderx);

        getRedText();
        getPoints1();
        lineColor = Mod1State.line1Color;
        
        deltaY = (yMax - yMin)/10.0;
        deltaX = (xMax - xMin)/6.0;
        // Get font sizes:
        //setFont(TheFonts.sanSerif14);
        //fm = getFontMetrics(TheFonts.sanSerif14);
        setFont(tmpFont);              //TEMP
        fm = getFontMetrics(tmpFont);  //TEMP
        fontHeight = fm.getHeight();
        ticLength  = fm.stringWidth("w")/2; // just a wide letter.
        
        yExtra = state.s100;

	//sliderx.setBounds(xMin,yMax+20,xMax-xMin,20);
	//sliderx.setBounds(xMin-25,yMax+20,xMax-xMin+50,20);
	sliderx.setBounds(xMin-state.s20,yMax+state.s20,xMax-xMin+state.s40,state.s20);
	/*
	Panel ps1 = new Panel();
	    ps1.setBackground(Color.white);
	    add(ps1);
	    ps1.setBounds(xMin-state.s20-1,yMax+state.s20-1,xMax-xMin+state.s40+2,state.s20+2);
	    
	Panel ps2 = new Panel();
	    ps2.setBackground(Color.black);
	    add(ps2);
	    ps2.setBounds(xMin-state.s20-2,yMax+state.s20-2,xMax-xMin+state.s40+4,state.s20+4);
	*/
	//Listeners
	sliderx.addChangeListener(this);

        mainWidth = xMax+yBorder;
        mainHeight = yMax+yBorder+yOffset+yExtra;
        //main = createImage(mainWidth, mainHeight);

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice(); 
        GraphicsConfiguration gc = gs.getDefaultConfiguration();

        main = gc.createCompatibleImage(mainWidth, mainHeight);
        imG = main.getGraphics();

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

        paintImage(imG);
        paintAnimationStuff(imG);
    }


    public void stateChanged(ChangeEvent evt){//NEED HERE
        // whole slider cooresponds to 1.5 seconds
        if (evt.getSource()==sliderx) {
            if(sliderx.getValue()>=ScrollMax){
                sliderx.setValue(ScrollMax);
                xSliderVal = 1.5;
	    } else {
                xSliderVal = 1.5*sliderx.getValue()/ScrollMax;
                xSliderVal = MaestroA.rounder(xSliderVal,7);
	    }
            redY = getRedYforX(xSliderVal);
            redY = MaestroA.rounder(redY,7);
            blueY = getBlueYforX(xSliderVal);
            blueY = MaestroA.rounder(blueY,7);
            getDotCoordinates();
            getRedAndBlueYText();
            paintImage(imG);
            paintAnimationStuff(imG);
            repaint();

	    //sliderx.requestFocusInWindow();
	    
	    //textx.setText(""+MaestroA.rounder(xSliderVal,7));
        }
    }


    private void getRedAndBlueYText() {
        //red4 = "y = "+dec.format(redY)+" [volts]";
        //blue4 = "y = "+dec.format(MaestroA.rounder(blueY,7))
        //    +" [volts]";
    }

    private double getRedYforX(double x) {
        double y;
        y = 5.0*Math.cos(4*Math.PI*x);
        return y;
    }


    private double getBlueYforX(double x) {
        double y;
        double xRadiansOffset = (state.angle*Math.PI)/180.0;
        y = state.amplitude*Math.cos((2*Math.PI*state.frequency*x)+xRadiansOffset);
        return y;
    }


    private void getRedText() {
        red1 = "A = 5 [volts]";
        red2 = "f = 2 [Hz]";
        red3 = STR.BOLDPHI+Character.toString((char)0x03d5)+
            STR.ENDBOLDPHI+STR.SUB+"0"+STR.ENDSUB+" = 0"+
            Character.toString((char)0x00b0);        
    }


    public void getBlueText() {
        blue1 = "A = "+Double.toString(state.amplitude)+" [volts]";
        blue2 = "f = "+Double.toString(state.frequency)+" [Hz]";
        blue3 = STR.BOLDPHI+Character.toString((char)0x03d5)+
            STR.ENDBOLDPHI+STR.SUB+"0"+STR.ENDSUB+" = "+
            Double.toString(state.angle)+Character.toString((char)0x00b0);
    }


  private void getPoints1() {
    double xPoint, yPoint, xInRadians;
    xList1 = new int[npts];
    yList1 = new int[npts];

    // create a wave with y in (-100,100), x in (0,600)
    for (int i = 0; i < xList1.length; i++) {
      xPoint = ((double)i*degreesPerX)/180.0;
      // each x point == 'degreesPerX' degrees; 180 degrees = pi(radians)
      xInRadians = xPoint * 3.1415927;
      yPoint = Math.cos(xInRadians);
      // 100 * (2/5) = 40
      xList1[i] = (int) (xPoint*state.s100); // scale x so 360 degress spans 200
      yList1[i] = (int) (yPoint*state.s100); // scale y from -100 to 100
    }

    // where to draw on the screen:
    //yOffset = 80;
    yOffset = state.s75;
    yBorder = state.s15;
    //xMin = 35; xMax = (200 * ncycles) + xMin;
    //xMin = 85; xMax = (200 * ncycles) + xMin;
    xMin = state.s95; xMax = (state.s200 * ncycles) + xMin;
    yMin = yBorder+yOffset; 
    yMax = state.s200+yBorder+yOffset; 
    yMid = (int)(0.5*(yMax+yMin));

    // NOW push the sine wave DOWN on the Y-axis (AND flip), so that the sine
    //  wave appears in the middle of the screen, rather that at the TOP
    for (int i = 0; i < yList1.length; i++)
        //yList1[i] += yMid;
        yList1[i] = yMid - yList1[i];
    // NOW shift the X-axis over by xMin to make room for the vertical
    //  axis line.
    for (int i = 0; i < xList1.length; i++)
      xList1[i] += xMin;
  }



  public void getPoints2() {
      double xPoint, yPoint, xInRadians, xRadiansOffset, amplitudeMultiplier;

    xRadiansOffset = (state.angle*Math.PI)/180.0;
    double newFrequency = state.frequency/Mod1State.REDfrequency;
    amplitudeMultiplier = state.amplitude/Mod1State.REDamplitude;

    // ONE cycle only, for blue line; unless the frequency is so small that
    //  one cycle can't fit on the screen.
    if (newFrequency < 0.00001) newFrequency = 0.00001;
    npts2 = (int)(360.0/(degreesPerX*newFrequency));
    npts2 = Math.min(npts2,npts);
    yList2 = new int[npts2];

    anyLine2 = false;

    // create a wave with y in (-100,100), x in (0,600)
    for (int i = 0; i < yList2.length; i++) {
      xPoint = (double)i*newDegreesPerX;
      // each x point == 'degreesPerX' degrees; 180 degrees = pi(radians)
      xInRadians = xPoint * 3.1415927;
      yPoint = Math.cos((newFrequency*xInRadians)+
                        xRadiansOffset)*amplitudeMultiplier;
      // 100 * (2/5) = 40
      //xList2[i] = (int) (xPoint*100); // scale x so 360 degress spans 200
      yList2[i] = (int) (yPoint*state.s100); // scale y from -100 to 100
    }

    // NOW push the sine wave DOWN on the Y-axis (AND flip), so that the sine
    //  wave appears in the middle of the screen, rather that at the TOP
    for (int i = 0; i < yList2.length; i++)
        //yList2[i] += yMid;
        yList2[i] = yMid - yList2[i];
    anyLine2 = true;

    blueY = getBlueYforX(xSliderVal);
    //blueY = MaestroA.rounder(blueY,7);
    getDotCoordinates();
    getRedAndBlueYText();

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



    private void getDotCoordinates() {
        redDotX = (int) ((xMax-xMin)*(xSliderVal/1.5)) + xMin;
        redDotY = (int) ((redY/5.0)*state.s100);
        redDotY = yMid - redDotY;
        
        blueDotX = redDotX;
        //blueDotY = (int) ((blueY/5.0)*100);
        //blueDotY = yMid - blueDotY;
        
        if (anyLine2) {
            int xIndex = (int)(xSliderVal*(xList1.length)/1.5);
            if (xIndex < yList2.length) blueDotY = yList2[xIndex];
            else {
                int i = xIndex/yList2.length;
                int yIndex = xIndex-(i*yList2.length);
                blueDotY = yList2[yIndex];
            }
        }
        anyDot = true;
    } 

  public void paintImage(Graphics g) {
      Graphics2D g2d = (Graphics2D)g;
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

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

    // Draw x and y axis:
    drawAxis(g);
    // Draw axis tick marks:
    drawAxisTicks(g);

    g.setColor(state.line1Color);
    //g.setFont(TheFonts.sanSerif14);
    //g.setFont(TheFonts.bold14);
    g.setFont(new Font("SanSerif",Font.BOLD,state.font14));
    
    endingX = STR.displayString(red1,g,state.s200,state.s30);
    endingX = STR.displayString(red2,g,state.s200,state.s50);
    endingX = STR.displayString(red3,g,state.s200,state.s70);
    //endingX = STR.displayString(red4,g,200,90);
    
    if (anyLine2) {
        g.setColor(state.line2Color);
        endingX = STR.displayString(blue1,g,state.s500,state.s30);
        endingX = STR.displayString(blue2,g,state.s500,state.s50);
        endingX = STR.displayString(blue3,g,state.s500,state.s70);
        //endingX = STR.displayString(blue4,g,500,90);
    }
  }      

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


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


    public void paintAnimationStuff(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        g2d.setStroke(new BasicStroke(3,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));

        g.setColor(Mod1State.line1Color);
        //drawPolyline(g,xList1,yList1,yList1.length);
        g2d.drawPolyline(xList1,yList1,yList1.length);

        if (anyLine2) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

            g.setColor(Mod1State.line2Color);
            /*
            int[] ix = new int[2];
            int[] iy = new int[2];
            int yIndex = yList2.length - npts2Index;
            if (yIndex == yList2.length) yIndex = 0;
            for (int i = 0; i < (xList1.length-1); i++) {
                ix[0] = xList1[i];
                ix[1] = xList1[i+1];
                iy[0] = yList2[yIndex];
                yIndex++;
                if (yIndex >= yList2.length) yIndex = 0;
                iy[1] = yList2[yIndex];
                //drawPolyline(g,ix,iy,2);
                g2d.drawPolyline(ix,iy,2);
            }
            */

            int[] ix = new int[xList1.length];
            int[] iy = new int[xList1.length];
            int yIndex = yList2.length - npts2Index;
            if (yIndex == yList2.length) yIndex = 0;
            for (int i = 0; i < (xList1.length-1); i++) {
                ix[i] = xList1[i];
                ix[i+1] = xList1[i+1];
                iy[i] = yList2[yIndex];
                yIndex++;
                if (yIndex >= yList2.length) yIndex = 0;
                    iy[i+1] = yList2[yIndex];
            }
            g2d.drawPolyline(ix,iy,xList1.length);

        }
        if (anyDot) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON); //need???
            g2d.setStroke(new BasicStroke(1,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));
            //g.setColor(Mod1State.line1Color);
            g.setColor(Color.yellow);
            g.fillOval(redDotX-state.s3,redDotY-state.s3,state.s6,state.s6);
            g.setColor(Color.black);
            g.drawOval(redDotX-state.s3,redDotY-state.s3,state.s6,state.s6);
        
            if (anyLine2) {
                //g.setColor(Mod1State.line2Color);
                g.setColor(Color.cyan);
                g.fillOval(blueDotX-state.s3,blueDotY-state.s3,state.s6,state.s6);
                g.setColor(Color.black);
                g.drawOval(blueDotX-state.s3,blueDotY-state.s3,state.s6,state.s6);
            }
            //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            //                     RenderingHints.VALUE_ANTIALIAS_ON); //need???
        }

        g2d.setStroke(new BasicStroke(1));
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        //                     RenderingHints.VALUE_ANTIALIAS_ON);


	sliderx.requestFocusInWindow();
	
    }


    private void drawAxis(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

        // Draw x and y axis:
        g.setColor(Mod1State.axisColor);
        g2d.setStroke(new BasicStroke(state.s0,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));
        drawLine(g,xMin,yOffset+state.s5,xMin,yMax+yBorder-state.s5); // the y-axis
        drawLine(g,xMin,yMid,xMax+yBorder-state.s5,yMid); // the x-axis
    
        g2d.setStroke(new BasicStroke(state.s0,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));
        MyArrows.drawDblUpArrow(g,xMin,yOffset,state.sfactor);
        MyArrows.drawDblDownArrow(g,xMin,yMax+yBorder,state.sfactor);
        MyArrows.drawDblRightArrow(g,xMax+yBorder,yMid,state.sfactor);
    }

    private void drawPolyline(Graphics g, int[] x, int[] y, int len) {
        //Graphics2D g2d = (Graphics2D)g;
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
      
        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);
        }
    }

    private void drawAxisTicks(Graphics g) {
        //Graphics2D g2d = (Graphics2D)g;
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
 
        g2d.setStroke(new BasicStroke(state.s0,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));
        // Draw x and y axis:
        g.setColor(Mod1State.axisColor);
        //g.setFont(TheFonts.sanSerif14);
        g.setFont(new Font("SanSerif",Font.BOLD,state.font14));
        FontMetrics fm;
        fm = g.getFontMetrics();
    
        g2d.setStroke(new BasicStroke(state.s0,BasicStroke.CAP_ROUND,
                                    BasicStroke.JOIN_ROUND));
        for(int i=0;i<11;i++){
            int y = (int)(i*deltaY)+yOffset+yBorder;
      
            //g.drawLine(xMin-ticLength,y,xMin,y);
            g.drawLine(xMin-state.s5,y,xMin,y);
      
            int v = 5-i;
            if (v == 5 || v == -5) {
                String vs = String.valueOf(v);
                g.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-state.s5,
                        y+(int)(fm.getHeight()/4));
            }
        }
        g.drawString("[ Volts ]",xMin+ticLength+state.s5,(int)(1.5*fontHeight)+yOffset-state.s20);

        // On the x-axis:
        // dots per cm:
        for(int i=1;i<7;i++){
            int x = (int)(i*deltaX)+xMin;
            //g.drawLine(x,yMid+ticLength,x,yMid);
            g.drawLine(x,yMid+state.s5,x,yMid);
      
            String xs = String.valueOf(i*0.25);
            g.drawString(xs,x-(int)(0.5*fm.stringWidth(xs)),
                      yMid+ticLength+fontHeight);
        }
        g.drawString("t [sec]",xMax-fm.stringWidth(" t [sec]") + state.s13,yMid-state.s10);
    }
}
