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

// Module 1 Exercise 2

public class Mod2Plot extends Panel implements MouseListener, Runnable {
    Mod2State state;

    Image image;
    int[] xListGreen;
    int[] yListGreen;
    int[] xListRed;
    int[][] yListRed;
    int cols;
    int currentRow = 0;
    int yMin, yMax, xMin, xMax, yMid;
    int yBorder, yOffset, yExtra;
    
    Image main;
    int mainWidth, mainHeight;
    Graphics imG;
    
    //MyAnimationTimer timer = new MyAnimationTimer(this, 25); // was 50

    private Thread tron;
    public boolean IsTronRunning;
    public boolean ThreadStarted;
    private int SleepTime = 25;  // was 50

    public static int delayInMsec = 25;
    public static double numberSecondsPerClick = delayInMsec*0.001;
    public static double numberClicksPerSecond = 1.0/numberSecondsPerClick;
    public static double numberSecondsPerWave = 1.0/Mod2State.REDfrequency;
    static int rows = (int) (numberSecondsPerWave/numberSecondsPerClick);
    
    // private static final Color bgColor = Color.lightGray;
    private static final Color bgColor = new Color(236,236,236);
    Color lineColor;
    
    boolean anyGreenWave = false;
    
    double deltaY, deltaX;
    FontMetrics fm;
    int fontHeight,ticLength;
    
    private Font boldFont, normalfont;
    //static Font smallFont = new Font("Serif",Font.PLAIN, 14);
    String red1, red2, red3;
    String green1, green2, green3;
    int endingX;
    
    DecimalFormat dec = new DecimalFormat("#.###");
    
    int numberSeconds = 0, numberMinutes = 0, numberClicks = 0;
    Label stopWatch = new Label("0:00  ");
    long totalNumberSeconds = 0;
    
    static double oneOver30 = 1.0/30.0;  // 30 "clicks" to each second
    static double oneOver180 = 1.0/180;
    
    public StartStopClock ssc;
    public Button reset;
    
    Graphics2D g2d;
    
    
    public synchronized void initialPlot() {
        state.reset();
       
        boldFont = new Font("Serif",Font.BOLD, state.font18);
        normalfont = new Font("Serif",Font.PLAIN, state.font12);
        
        ssc.clockcanvas.setStatus(0,state.NTime,state.dtime);
        ssc.clockcanvas.reset();

        //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 Mod2Plot(Mod2State state) {
        super();
        this.state = state;
        IsTronRunning = false;
        ThreadStarted = false;
        setLayout(null);
        setBackground(bgColor);
        
        getRedText();
        getPointsRedwave();
        //lineColor = Mod1State.line1Color;
        
        ssc  = new StartStopClock(state);
        ssc.button1.setEnabled(true);
        ssc.button2.setEnabled(true);
        add(ssc);
        
        //start stop clock
        ssc.setBounds(state.s328,state.s10,state.s194,state.s60);
        ssc.button1.addMouseListener(this);
        ssc.button2.addMouseListener(this);
        
        reset = new Button("Reset");
        //reset.setFont(TheFonts.sanSerif12);
        reset.setFont(new Font("SanSerif",Font.PLAIN, state.font12));
        add(reset);
        reset.setBounds(state.s685,state.s10,state.s70,state.s20);
        
        Panel preset = new Panel();
        preset.setBackground(Color.black);
        preset.setBounds(state.s685-1,state.s10-1,state.s70+2,state.s20+2);
        add(preset);
        
        IsTronRunning = false;
        ThreadStarted = false;
        
        // Get font sizes:
        setBackground(bgColor);
        setFont(TheFonts.sanSerif12);
        fm = getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        ticLength  = fm.stringWidth("w")/2; // just a wide letter.
        
        yExtra = state.s220;  // was 180
        
        mainWidth = xMax+yBorder;
        mainHeight = yMax+yBorder+yOffset+yExtra;
    }


    public void reset() {
        IsTronRunning = false;
        ssc.button2.setEnabled(false);
        ssc.button1.setEnabled(true);
        ssc.IsStop = true;
        ssc.repaint();
        anyGreenWave = false;
        paintImage(imG);
        paintAnimationStuff(imG);
        repaint();
    }


    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 getRedText() {
        red1 = "f = 0.5 [Hz]";
        red2 = Character.toString((char)0x03bb)+" = 2 [cm]";
        red3 = STR.BOLDPHI+Character.toString((char)0x03d5)+
           STR.ENDBOLDPHI+STR.SUB+"0"+STR.ENDSUB+" = 0"+
           Character.toString((char)0x00b0);
    }


    public synchronized void getGreenText() {
        green1 = "f = "+dec.format(state.frequency)+" [Hz]";
        green2 = Character.toString((char)0x03bb)+" = "+
           dec.format(state.lambda)+" [cm]";
        green3 = STR.BOLDPHI+Character.toString((char)0x03d5)+
           STR.ENDBOLDPHI+STR.SUB+"0"+STR.ENDSUB+" = "+
           Integer.toString(state.phi)+Character.toString((char)0x00b0);
    }


    private synchronized void getPointsRedwave() {
        // 72 points for one full cycle  // Changed to 360 for a smoother scaled curve
        int ncycles=3;
        int npts = ncycles*state.s72;  // increased to be smoother in large size app
        double xPoint, yPoint, xInRadians, xInCm;
        xListRed = new int[npts];
        //yListRed = new int[npts];
        yListRed = new int[rows][npts];

        // create a wave with y in (-100,100), x in (0,600)
        double timeIncrement = numberSecondsPerWave/(double)rows;
        for (int n = 0; n < rows; n++) {
            double xShift = (double)n*timeIncrement;
            double xShiftInRadians = Math.PI*xShift;

            for (int i = 0; i < xListRed.length; i++) {
                xPoint = ((double)i*5)/180;
                // each x point == 5 degrees; 180 degrees = pi(radians)
                xInRadians = xPoint * 3.1415927;
                xInCm = i/36.0;  // 72 points per 2 cm
                yPoint = Math.cos(xShiftInRadians - (Math.PI*xInCm));
                xListRed[i] = (int) (xPoint*state.s100); // scale x so 360 degress spans 200
                yListRed[n][i] = (int) (yPoint*state.s100); // (5 Volts) y from -100 to 100
            }
        }

        // where to draw on the screen:
        //yOffset = 10;
        yOffset = state.s90;  // was 40
        yBorder = state.s15;
        //xMin = 35; xMax = (200 * ncycles) + xMin;
        //xMin = 85; xMax = (200 * ncycles) + xMin;
        xMin = state.s125; 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 < rows; i++){
            for (int j = 0; j < npts; j++) {
                //yListRed[i][j] += yMid;
                yListRed[i][j] = yMid - yListRed[i][j];
                if (i == 0) {
                    xListRed[j] += xMin;
                }
            }
        }
    }


    public synchronized void checkGetPointsGreenwave () {
        // change to input values; only need to get new greenWave points
        //  if not currently animated (otherwise will be called automatically
        //   by the animate thread.
        //if (start_stop.getLabel().equals("Start Animation"))
        if (!IsTronRunning)
            getPointsGreenwave();
        else anyGreenWave = true;
    }


    public synchronized void getPointsGreenwave() {
        // Scale the animation time with a "normal" frequency of 0.5 (red line's)
            double time = (double)totalNumberSeconds +
                (numberClicks*numberSecondsPerClick);
        // 72 points for one full cycle
        int ncycles=3;
        int npts = ncycles*state.s72;
        double xPoint, yPoint, xInRadians, xInCm;

        cols = npts;

        xListGreen = new int[cols];
        yListGreen = new int[cols];

        anyGreenWave = false;

        // create a wave with y in (-100,100), x in (0,600)
        for (int i = 0; i < cols; i++) {
            xPoint = ((double)i*5)*oneOver180;
            // each x point == 5 degrees; 180 degrees = pi(radians)
            xInRadians = xPoint * Math.PI;
            xInCm = i/36.0;  // 72 points per 2 cm
            yPoint = Math.cos((2.0*Math.PI*state.frequency*time) -
                (2.0*Math.PI*xInCm/state.lambda))
                *Math.exp(-state.alpha*xInCm);
            yListGreen[i] = (int) (yPoint*state.s100); // (5 Volts) y from -100 to 100
            xListGreen[i] = (int) ((xPoint)*state.s100); // scale x so 360 degress spans 200
            //yPoint = Math.exp(-0.053*xInRadians);
        }

        // 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
        // AND shift the X-axis over by xMin to make room for the vertical
        //  axis line.
        for (int i = 0; i < cols; i++) {
            //yListGreen[i] += yMid;
            yListGreen[i] = yMid - yListGreen[i];
            xListGreen[i] += xMin;
        }
        anyGreenWave = true;
        //if (start_stop.getLabel().equals("Start Animation")) {
        if (!IsTronRunning) {
            paintImage(imG);
            paintAnimationStuff(imG);
            repaint();
        }
    }


    private synchronized void paintImage(Graphics g) {
        //g.clearRect(0,0,mainWidth,mainHeight);
        g.setColor(bgColor);
        g.fillRect(0,0,mainWidth,mainHeight);

        // Set up stuff for drawing tick marks:
        deltaY = (yMax - yMin)/10.0;
        deltaX = (xMax - xMin)/12.0;
        // Get font sizes:
        fm = getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        ticLength  = fm.stringWidth("w"); // just a wide letter.

        // Fill the background
        g.setColor(bgColor);
        g.fillRect(0,0,xMax,yMax+yBorder+yOffset);
        
        // Draw x and y axis:
        drawAxis(g);

        //Graphics2D g2d = (Graphics2D)g;
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        //                     RenderingHints.VALUE_ANTIALIAS_ON);     
        // Draw axis tick marks:
        drawAxisTicks(g);

        g.setColor(state.redWaveColor);
        //g.setFont(TheFonts.bold14); //new
        g.setFont(new Font("SanSerif",Font.BOLD,state.font14));
        
        endingX = STR.displayString(red1,g,state.s220,state.s30);
        endingX = STR.displayString(red2,g,state.s220,state.s50);
        endingX = STR.displayString(red3,g,state.s220,state.s70);

        if (anyGreenWave) {
            g.setColor(state.greenWaveColor);
            endingX = STR.displayString(green1,g,state.s560,state.s30);
            endingX = STR.displayString(green2,g,state.s560,state.s50);
            endingX = STR.displayString(green3,g,state.s560,state.s70);
        }
    }

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

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

        //g.drawImage(image,0,0,this);
        g.setColor(state.redWaveColor);
        //drawPolyline(g,xListRed,yListRed[currentRow],xListRed.length);
        g2d.drawPolyline(xListRed,yListRed[currentRow],xListRed.length);
        if (anyGreenWave) {
            g.setColor(state.greenWaveColor);
            //drawPolyline(g,xListGreen,yListGreen,cols);
            g2d.drawPolyline(xListGreen,yListGreen,cols);
        }

        g2d.setStroke(new BasicStroke(1));
    }

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

    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(state.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.s2,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_OFF);     
        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) {
        int deltaX = Math.abs(x1-x2);
        int deltaY = Math.abs(y1-y2);
        if (deltaY > deltaX) {
            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);     
        // Draw x and y axis:
        g.setColor(state.axisColor);
        //g.setFont(TheFonts.sanSerif12);
        g.setFont(new Font("SanSerif",Font.BOLD,state.font14));
        FontMetrics fm;
        fm = g.getFontMetrics();
    
        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);
          
            //drawHorizontalDottedLine(g,y);
            int v = 5-i;
            if (v == 5) {
                String vs = String.valueOf(v);
                g.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-state.s5,
                       y+(int)(0.5*fontHeight));
            }
            else if (v == -5) {
                String vs = String.valueOf(v);
                g.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-state.s5,
                       y+(int)(0.5*fontHeight));
            } 
        }
        //g.drawString("[Volts]",xMin+ticLength,(int)(1.5*fontHeight)+yOffset-10);
        g.drawString("[ Volts ]",xMin+ticLength+state.s5,(int)(1.5*fontHeight)+yOffset-state.s20);
        
        
        // On the x-axis:
        // dots per cm:
        //int screenRes = (int) (getToolkit().getScreenResolution()/2.54);
        for(int i=1;i<13;i++){
            int x = (int)(i*deltaX) +xMin;
            if( x > xMax) break;
            //g.drawLine(x,yMid+ticLength,x,yMid);
            g.drawLine(x,yMid+state.s5,x,yMid);

            //drawVerticalDottedLine(g,x);
            //String xs = String.valueOf(i*0.5);
            String xs = dec.format(i*0.5);
            g.drawString(xs,x-(int)(0.5*fm.stringWidth(xs)),
                     yMid+ticLength+fontHeight);
        }
        g.drawString("x [cm]",xMax+yBorder-fm.stringWidth("  x [cm]"),
                yMid+(2*fontHeight)+state.s10);
    }

    private void drawHorizontalDottedLine(Graphics g, int y) {
        //Graphics2D g2d = (Graphics2D)g;
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        //                     RenderingHints.VALUE_ANTIALIAS_OFF);     
        Color oldColor = g.getColor();
        g.setColor(Color.gray);
        Font oldFont = g.getFont();
        g.setFont(TheFonts.sanSerif10);
        for (int i = xMin; i <= xMax; i+=5)
        g.drawString(".",i,y);
        g.setColor(oldColor);
        g.setFont(oldFont);
    }

    private void drawVerticalDottedLine(Graphics g, int x) {
        //Graphics2D g2d = (Graphics2D)g;
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        //                     RenderingHints.VALUE_ANTIALIAS_OFF);     
        Color oldColor = g.getColor();
        g.setColor(Color.gray);
        Font oldFont = g.getFont();
        g.setFont(TheFonts.sanSerif10);
        for (int i = yMin; i <= yMax+2; i+=5)
            g.drawString(".",x,i);
        g.setColor(oldColor);
        g.setFont(oldFont);
    }

    public synchronized void animate() {
        // NEW
        numberClicks++;
        if (numberClicks >= numberClicksPerSecond) {
            numberClicks = 0;
            incrementStopwatch();
            //paintStopwatch(imG);
        }
        //repaint();  should i do this?

        // "shift" the x-axis to the left:
        currentRow++;
        if (currentRow >= rows) currentRow = 0;

        if (anyGreenWave){getPointsGreenwave();}
        paintImage(imG);

        paintAnimationStuff(imG);
        repaint();
    }


    private synchronized void incrementStopwatch() {
        totalNumberSeconds++;
        numberSeconds++;
        if (numberSeconds > 59) {
            numberSeconds = 0;
            numberMinutes++;
            if (numberMinutes > 9){
                numberMinutes = 0;
            }
        }
    }

    private synchronized void paintStopwatch(Graphics g) {
        String display = String.valueOf(numberMinutes)+":";
        String seconds = String.valueOf(numberSeconds);
        if (seconds.length() == 1){seconds = "0"+seconds;}
        display += seconds;
        stopWatch.setText(display);
 }

}
