import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.Border;



public class SnowflakeGui extends JFrame implements ActionListener, ChangeListener, MouseListener, MouseMotionListener
{ private static final long serialVersionUID = 1L;
  public static final int defaltCharWidth = 55;
  
  public static final Border raisedBorder = BorderFactory.createRaisedBevelBorder();
  
  private static final int MORPHOLOGY_IMAGE_WIDTH = 445;
  private static final int MORPHOLOGY_IMAGE_HEIGHT = 369;
  
  private static final int MORPHOLOGY_IMAGE_STELLAT_DENDRITE_X = 241;
  private static final int MORPHOLOGY_IMAGE_STELLAT_DENDRITE_Y = 62;
  
  public static final double TEMPERATURE_MIN = -32.0;
  public static final double TEMPERATURE_MAX = 0.0;
  public static final double SATURATION_MIN = 0.0;
  public static final double SATURATION_MAX = 0.3;
  
  private static final int TEMPERATURE_MIN_PIXELS = 50;
  private static final int TEMPERATURE_MAX_PIXELS = 432;
  private static final int SATURATION_MIN_PIXELS = 29;
  private static final int SATURATION_MAX_PIXELS = 317;
  
 
  private Container contentPane;
  
  private DrawPanel drawPanel;
  
  public JComboBox   choice_vertex;
  private static final String[] CHOICE_VERTEX_NAMES =
  { "Vertex 1", "Vertex 2", "Vertex 3", "Vertex 4", "Vertex 5", "Vertex 6", 
    "Vertex 7", "Vertex 8"
  };
  
  private JLabel label_temperature, label_saturation;
  private JLabel label_noise, label_delay;
  
  private JSlider slider_temperature;
  private JSlider slider_saturation;
  private JSlider slider_noise;
  private JSlider slider_delay;
  
  private boolean slidersListening = false;


  private JToggleButton but_pause, but_reset;
 
  private static final Color Amaranth = new Color(229, 43, 80);
  private static final Color Tangerine = new Color(242, 133, 0);
  private static final Color Copper = new Color(184, 115, 51);
  private static final Color Amber = new Color(255, 191, 0);
  private static final Color ForestGreen = new Color(34, 139, 34);
  private static final Color BondiBlue = new Color(0, 149, 182);
  private static final Color RoyalBlue = new Color(0, 35, 102);
  private static final Color Amethyst = new Color(153, 102, 204);
  
  private int vertexStateX[] = new int[8];
  private int vertexStateY[] = new int[8];
  private Color vertexStateColor[] = 
  { Amaranth,Tangerine,Copper,Amber,ForestGreen,BondiBlue,RoyalBlue,Amethyst
  };
  
  private java.util.Random random = new java.util.Random();
  
  
 
  

  

  public final Color dialogBackground = new Color(238,238,238);
  
  private Toolkit toolkit = Toolkit.getDefaultToolkit();
  private MediaTracker tracker;
  private Image morphologyImage;
  
  private static final Font fontPhaseDrawing = new Font("LucidaTypewriterBold.ttf", Font.BOLD, 28);
  int fontPhaseDrawingWidth,fontPhaseDrawingHeight;
  private int frameBorderTop, frameBorderSide;
  

  public SnowflakeGui() 
  { Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    int frameWidth = MORPHOLOGY_IMAGE_WIDTH+6;
    int frameHeight = d.height - 100;
    this.setBounds(d.width-frameWidth, 0, frameWidth, frameHeight);
    this.setResizable(false);
  
    this.setTitle("Digital Snowflake Controls");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    contentPane = getContentPane();
    contentPane.setLayout(null);
    contentPane.setBackground(dialogBackground);

    drawPanel = new DrawPanel();
    drawPanel.setLayout(null);

    contentPane.add( drawPanel , BorderLayout.CENTER );
    drawPanel.setBounds(0,0,MORPHOLOGY_IMAGE_WIDTH, MORPHOLOGY_IMAGE_HEIGHT);
    
   
    choice_vertex = addComboBox(CHOICE_VERTEX_NAMES);

    label_temperature = addLabel("Temperature:");
    label_saturation = addLabel("Saturation:");
    label_noise = addLabel("Noise:");
    label_delay = addLabel("Delay:");


    slider_temperature = addSlider(0, 100);
    slider_saturation = addSlider(0, 100);
    slider_noise = addSlider(0, 100);
    slider_delay = addSlider(0, 100);

    
    but_reset    = addButton("Reset");
    but_pause    = addButton("Start");
    but_pause.setSelected(true);
    slider_delay.setValue(10);
   
   
    this.setVisible(true);
    
    Insets inset = this.getInsets();
    int panelWidth = this.getWidth() - inset.left - inset.right;
    int panelHeight = this.getHeight() - inset.top - inset.bottom;
    
    contentPane.setBounds(0, 0, panelWidth, panelHeight);

    
    loadImage("Resources/SnowflakeMorphologyDiagram.png");
    
    FontMetrics fm = drawPanel.getFontMetrics(fontPhaseDrawing);
    fontPhaseDrawingWidth = fm.stringWidth("X");
    fontPhaseDrawingHeight = fm.getHeight();
    
    frameBorderTop  = inset.top;
    frameBorderSide = inset.left;
    
    
    Font defaultFont = new Font("SansSerif", Font.BOLD, 14);
    fm = contentPane.getFontMetrics(defaultFont);
    int fontWidth = fm.stringWidth("X");
    int fontHeight = fm.getHeight();
    

    
    int boxH  = fontHeight+8;
    int rowH  = boxH + 6;
    int edge = 12;

    
    int col1 = edge;
    int col1_width = fontWidth*14;
    int col2 = edge + col1_width;
    
    int sliderWidth = panelWidth - col2 - edge;
   
    int row1 = MORPHOLOGY_IMAGE_HEIGHT + edge;
    int row2 = row1 + rowH; 
    int row3 = row2 + rowH; 
    int row4 = row3 + rowH; 
    int row5 = row4 + rowH; 
    int row6 = row5 + rowH; 
    
    choice_vertex.setBounds(edge, row1, col1_width, boxH);
    
    label_temperature.setBounds(edge, row2, col1_width, boxH);
    label_saturation.setBounds(edge, row3, col1_width, boxH);
    label_noise.setBounds(edge, row4, col1_width, boxH);
    label_delay.setBounds(edge, row5, col1_width, boxH);


    slider_temperature.setBounds(col2, row2, sliderWidth, boxH);
    slider_saturation.setBounds(col2, row3, sliderWidth, boxH);
    slider_noise.setBounds(col2, row4, sliderWidth, boxH);
    slider_delay.setBounds(col2, row5, sliderWidth, boxH);
    
    int butWidth = (panelWidth - edge*6)/3;
    but_reset.setBounds   (col1,   row6, butWidth, boxH);
    but_pause.setBounds   (col2,   row6, butWidth, boxH);
    
    addMouseListener(this);
    addMouseMotionListener(this);
    
    slider_temperature.addChangeListener(this);
    slider_saturation.addChangeListener(this);
    slider_noise.addChangeListener(this);
    slider_delay.addChangeListener(this);

    setMorphology(MORPHOLOGY_IMAGE_STELLAT_DENDRITE_X, MORPHOLOGY_IMAGE_STELLAT_DENDRITE_Y);
    drawPanel.repaint();
    slidersListening = true;
    
  }
  
  public float getTemperature(int idx)      
  { // g/m3
    
    double rangePixel = TEMPERATURE_MAX_PIXELS - TEMPERATURE_MIN_PIXELS;
    double x = TEMPERATURE_MAX_PIXELS - vertexStateX[idx];
    x = x/rangePixel;
    double rangeTemp = TEMPERATURE_MAX - TEMPERATURE_MIN;
    double temperature = (x*rangeTemp) + TEMPERATURE_MIN;
    
    double noise = slider_noise.getValue();
    if (noise > 0)
    { noise = noise/2.0 - noise*random.nextDouble();
      temperature += noise*rangeTemp/1000.0;
    }
    if (temperature > TEMPERATURE_MAX) temperature=TEMPERATURE_MAX;
    else if (temperature < TEMPERATURE_MIN) temperature=TEMPERATURE_MIN;

    return (float)temperature;
  }   
  
  
  public float getSupersaturation(int idx)
  { // g/m3
    
    double rangePixel = SATURATION_MAX_PIXELS - SATURATION_MIN_PIXELS;
    double y = vertexStateY[idx]-SATURATION_MIN_PIXELS;
    y = y/rangePixel;
    double rangeSat = SATURATION_MAX - SATURATION_MIN;
    double saturation = SATURATION_MAX - ((y*rangeSat) + SATURATION_MIN);
    
    double noise = slider_noise.getValue();
    if (noise > 0)
    { noise = noise/2.0 - noise*random.nextDouble();
      saturation += noise*rangeSat/1000.0;
    }
    if (saturation > SATURATION_MAX) saturation=SATURATION_MAX;
    else if (saturation < SATURATION_MIN) saturation=SATURATION_MIN;

    return (float)saturation;
  }
    

  
  public void setSupersaturation(int vertex, float sat) {}// g/m3
  public void setTemperature(int vertex, float temp) {}  // ºC

  public boolean isPaused()
  { boolean paused = but_pause.isSelected();
    return paused;
  }
  
  
  public boolean isReset()
  { //Once the user presses this button, it stays selected until
    //this method is called.
    //This is done this way, because the GUI cannot call the 
    //snowflake model when this button is pressed. 
    //The GUI must wait for then snowflake model to poll it.
    
    boolean value = but_reset.isSelected();
    if (but_reset.isSelected())
    { but_reset.setEnabled(true);
      but_reset.setSelected(false);
    }
    return value;
  }
  
  public int getMilliSecDelay()
  { //Returns a delay in the range [0, 5000] milliseconds.
    //Goal:
    // Slider Value    millisec delay
    //      0               10
    //     10              150
    //    100             5000
    double x = slider_delay.getValue();
    int delay = (int)((359.0/900.0)*x*x + (901.0/90.0)*x +10.0);
    
    return delay;
  }
  
  
  private void loadImage(String filename)
  { tracker = new MediaTracker(this);
  
    try
    { morphologyImage = toolkit.getImage(this.getClass().getResource(filename));
      tracker.addImage(morphologyImage, 0);
      
      tracker.waitForAll();
    } 
    catch (Exception ex) 
    { System.out.println("Error::"+ex.getMessage());
      ex.printStackTrace();
    }
    
    System.out.println("Done loading image");
  
  }

  
  private JComboBox addComboBox(String[] strArray)
  { JComboBox comboBox;
    if (strArray == null) comboBox = new JComboBox();
    else comboBox = new JComboBox(strArray);
    comboBox.setBackground(dialogBackground);
    comboBox.setForeground(Color.BLACK);
    contentPane.add(comboBox);
    comboBox.addActionListener(this);
    return comboBox;
  }
  
  

  private JLabel addLabel(String str)
  { return addLabel(str, Label.RIGHT);
  }
  
  private JLabel addLabel(String str, int align)
  { JLabel id = new JLabel(str, align);
    contentPane.add(id);
    //id.setFont(fontNormal);
    id.setBackground(dialogBackground);
    id.setForeground(Color.BLACK);
    return id;
  }


  private JToggleButton addButton(String str)
  { JToggleButton id = new JToggleButton(str);
    contentPane.add(id);
    id.setBackground(dialogBackground);
    id.setForeground(Color.BLACK);
    id.setBorder(raisedBorder);
    id.addActionListener(this);
    return id;
 }
 
  
  public JSlider addSlider(int min, int max)
  { JSlider mySlider = new JSlider(JSlider.HORIZONTAL, min, max, min);
    contentPane.add(mySlider);
    mySlider.setBackground(dialogBackground);
    mySlider.setForeground(Color.BLACK);
    return mySlider;
  }



  public void mouseMoved(MouseEvent evt){}
  public void mousePressed(MouseEvent evt){}
  public void mouseClicked(MouseEvent evt)
  { int mouseX = evt.getX() - frameBorderSide;
    int mouseY = evt.getY() - frameBorderTop;
    setMorphology(mouseX, mouseY);
  }
  public void mouseDragged(MouseEvent evt)
  { int mouseX = evt.getX() - frameBorderSide;
    int mouseY = evt.getY() - frameBorderTop;
    setMorphology(mouseX, mouseY);
  }

  public void mouseReleased(MouseEvent evt){}
  public void mouseEntered(MouseEvent evt) {}
  public void mouseExited(MouseEvent evt) {}
  
  
  private void setMorphology(int x, int y)
  { if (x < TEMPERATURE_MIN_PIXELS) return;
    if (x > TEMPERATURE_MAX_PIXELS) return;
    if (y < SATURATION_MIN_PIXELS) return;
    if (y > SATURATION_MAX_PIXELS) return;

    for (int i=0; i<8; i++)
    { vertexStateX[i] = x;
      vertexStateY[i] = y;
    }
    drawPanel.repaint();
    
    slidersListening = false;
    slider_temperature.setValue(getTemperatureSliderPos(x));
    slider_saturation.setValue(getSaturationSliderPos(y));
    slidersListening = true;
  }
  
  
  private int getTemperatureSliderPos(int x)
  { int rangeX = TEMPERATURE_MAX_PIXELS - TEMPERATURE_MIN_PIXELS;
    int sliderX = (100*(x - TEMPERATURE_MIN_PIXELS))/rangeX;
    return sliderX;
  }
  
  
  private int getSaturationSliderPos(int y)
  { int rangeY = SATURATION_MAX_PIXELS - SATURATION_MIN_PIXELS;
    int sliderY = 100-(100*(y - SATURATION_MIN_PIXELS))/rangeY;
    return sliderY;
  }
  


  public void actionPerformed(ActionEvent evt)
  { Object source = evt.getSource();
    if (source == choice_vertex)
    { int idx = choice_vertex.getSelectedIndex();
      int sliderX = getTemperatureSliderPos(vertexStateX[idx]);
      int sliderY = getSaturationSliderPos(vertexStateY[idx]);
      slidersListening = false;
      slider_temperature.setValue(sliderX);
      slider_saturation.setValue(sliderY);
      slidersListening = true;
    }
    else if( source == but_reset)
    { if (but_reset.isSelected())
      { but_reset.setEnabled(false);
        but_pause.setSelected(true);
      }
    }
    if (but_pause.isSelected()) but_pause.setText("Start");
    else but_pause.setText("Pause");
    
  }
  
  
  public void stateChanged(ChangeEvent e) 
  { if (!slidersListening) return;
    JSlider source = (JSlider)e.getSource();
    
    if (source == slider_temperature || source == slider_saturation) 
    { int idx = choice_vertex.getSelectedIndex();
      int sliderX = slider_temperature.getValue();
      int sliderY = slider_saturation.getValue();
      int rangeX = TEMPERATURE_MAX_PIXELS - TEMPERATURE_MIN_PIXELS;
      int rangeY = SATURATION_MAX_PIXELS - SATURATION_MIN_PIXELS;
      
      vertexStateX[idx] = (sliderX*rangeX/100) + TEMPERATURE_MIN_PIXELS;
      vertexStateY[idx] = ((100-sliderY)*rangeY/100) + SATURATION_MIN_PIXELS;
      
      //System.out.println("temp="+getTemperature(idx)+", sat="+getSupersaturation(idx));
      repaint();
      
    }
  }
  
  
  
  class DrawPanel extends JPanel
  { private static final long serialVersionUID = 1L;
    
    public void paintComponent (Graphics g) 
    { super.paintComponent(g);
      //g.drawImage(image_buf,0,0,null);
      g.drawImage(morphologyImage,0,0,null);
      //System.out.println("DrawPanel.paintComponent()");
      g.setFont(fontPhaseDrawing);
      for (int i=0; i<8; i++)
      { g.setColor(vertexStateColor[i]);
        int x = vertexStateX[i]-fontPhaseDrawingWidth/2;
        int y = vertexStateY[i]+fontPhaseDrawingHeight/2;
        g.drawString(String.valueOf(i+1), x, y);
      }
    }
  }
}