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

/** An instance of this class shows a color and its complement as well
  * as sliders for the RGB, HSV, and CMYK color models. Changing any
  * of the sliders changes the color (and the other sliders appropriately).
  */
public class A4 extends JApplet implements ChangeListener, ActionListener {
    /** the JFrame that is the GUI. */
    private JFrame jf= null;
    
    /** Panels that contain the color and its complement, and
      labels that contain information in them. */
    private JPanel image, compImage;
    private JLabel imageLabel, compImageLabel;
    
    /** Below are sliders. In each constructor call, the first argument
      makes it a vertical slider. The next three arguments are the
      minimum value, maximum value, and initial value of the slider. */
    
    /** RGB sliders. */
    private JSlider RSlider= new JSlider(JSlider.VERTICAL, 0, 25500, 0);
    private JSlider GSlider= new JSlider(JSlider.VERTICAL, 0, 25500, 25500);
    private JSlider BSlider= new JSlider(JSlider.VERTICAL, 0, 25500, 0);
    
    /** CMYK sliders. */
    private JSlider CSlider= new JSlider(JSlider.VERTICAL, 0, 10000, 10000);
    private JSlider MSlider= new JSlider(JSlider.VERTICAL, 0, 10000, 0);
    private JSlider YSlider= new JSlider(JSlider.VERTICAL, 0, 10000, 10000);
    private JSlider KSlider= new JSlider(JSlider.VERTICAL, 0, 10000, 0);
    
    /** HSV sliders. */
    private JSlider HSlider= new JSlider(JSlider.VERTICAL, 0, 35999, 12000);
    private JSlider SSlider= new JSlider(JSlider.VERTICAL, 0, 100, 100);
    private JSlider VSlider= new JSlider(JSlider.VERTICAL, 0, 100, 100);
    
    /** The fields for the components of the colors and
      the input buttons */
    private JButton inputrgb=  new JButton("RGB");
    private JTextField rfield= new JTextField();
    private JTextField gfield= new JTextField();
    private JTextField bfield= new JTextField();
    private JButton inputcmyk=  new JButton("CMYK");
    private JTextField cfield= new JTextField();
    private JTextField mfield= new JTextField();
    private JTextField yfield= new JTextField();
    private JTextField kfield= new JTextField();
    private JButton inputhsv= new JButton("HSV");
    private JTextField hfield= new JTextField();
    private JTextField sfield= new JTextField();
    private JTextField vfield= new JTextField();
    
    /** Create and show a GUI that contains:
      * (0) a color panel, with text to describe the color.
      * (1) the complementary color panel, with text to describe it.
      * (2) Sliders for RGB, HSV, and CMYK.
      * Initially, the color is red. 
      */
    public static void main(String[] args) {
        A4 a4 = new A4();
    }
    
    /** Initialize the applet by creating and showing the GUI. */
    public void init() {
        // A4 a4= new A4();
    }
    
    /** Constructor: a GUI that contains:
      * (0) a color panel, with text to describe the color.
      * (1) the complementary color panel, with text to describe it.
      * (2) Sliders for RGB, HSV, and CMYK.
      * Initially, the color is red. 
      */
    public A4() {
        jf= new JFrame("Color");
        jf.setSize(600, 200);
        jf.setResizable(false);
        
        // Create color red in three models.
        Color rgb= new Color(0, 255, 0);
        CMYK cmyk= A4Methods.convertRGBtoCMYK(rgb);
        HSV hsv= A4Methods.convertRGBtoHSV(rgb);
        
        // Create panel image to contain the color.
        image= new JPanel();
        image.setSize(230, 200);
        image.setPreferredSize(new Dimension(230, 200));
        imageLabel= new JLabel("");
        imageLabel.setPreferredSize(new Dimension(230, 160));
        imageLabel.setVerticalAlignment(JLabel.TOP);
        image.add(imageLabel);
        
        // Create panel compImage to contain the complementary color.
        compImage= new JPanel();
        compImage.setSize(230, 200);
        compImage.setPreferredSize(new Dimension(230, 200));
        compImageLabel= new JLabel("");
        compImageLabel.setPreferredSize(new Dimension(230, 160));
        compImageLabel.setVerticalAlignment(JLabel.TOP);
        compImage.add(compImageLabel);
        
        // Create each of the sliders and add them to Box sliders
        // with some space between the three sets of sliders.
        Box sliders= new Box(BoxLayout.X_AXIS);
        
        fixSlider(sliders, RSlider, 'R');
        fixSlider(sliders, GSlider, 'G');
        fixSlider(sliders, BSlider, 'B');
        
        sliders.add(Box.createRigidArea(new Dimension(20,0)));
        
        fixSlider(sliders, CSlider, 'C');
        fixSlider(sliders, MSlider, 'M');
        fixSlider(sliders, YSlider, 'Y');
        fixSlider(sliders, KSlider, 'K');
        
        sliders.add(Box.createRigidArea(new Dimension(20,0)));
        
        fixSlider(sliders, HSlider, 'H');
        fixSlider(sliders, SSlider, 'S');
        fixSlider(sliders, VSlider, 'V');
        
        // Add to Box GUI the color and complementary color panels and
        // the Box of sliders
        Box GUI= new Box(BoxLayout.X_AXIS);
        GUI.add(image);
        GUI.add(compImage);
        GUI.add(sliders);
        
        // Set the initial values of the sliders.
        setSliders(rgb, hsv, cmyk);
        
        // Set the color and complementary color panels
        setColorPanels(rgb, hsv, cmyk);
        
        Box fields= createFields();
        Box finalBox= new Box(BoxLayout.Y_AXIS);
        finalBox.add(GUI);
        finalBox.add(fields);
        
        jf.getContentPane().add(finalBox);
        jf.pack();
        jf.setVisible(true);
    }
    
    /** Create and return a horizontal box with the fields for the color components
      * and the input buttons, registering this class as a listener for the buttons 
      */
    public Box createFields() {
        Box fields=  new Box(BoxLayout.X_AXIS);
        fields.add(inputrgb);
        inputrgb.addActionListener(this);
        fields.add(new JLabel("R"));
        fields.add(rfield);
        fields.add(new JLabel("G"));
        fields.add(gfield);
        fields.add(new JLabel("B"));
        fields.add(bfield);
        
        fields.add(inputcmyk);
        inputcmyk.addActionListener(this);
        fields.add(new JLabel("C"));
        fields.add(cfield);
        fields.add(new JLabel("M"));
        fields.add(mfield);
        fields.add(new JLabel("Y"));
        fields.add(yfield);
        fields.add(new JLabel("K"));
        fields.add(kfield);
        
        fields.add(inputhsv);
        inputhsv.addActionListener(this);
        fields.add(new JLabel("hue"));
        fields.add(hfield);
        fields.add(new JLabel("sat"));
        fields.add(sfield);
        fields.add(new JLabel("val"));
        fields.add(vfield);
        
        return fields;
    }
    
    /** Create a Box with slider s and c above it
      * and add the Box to sliders.
      *  Add this object as a changeListener for s.
      */
    public void fixSlider(Box sliders, JSlider s, char c) {
        Box b= new Box(BoxLayout.Y_AXIS);
        b.add(new JLabel("" + c));
        b.add(s);
        sliders.add(b);
        s.addChangeListener(this);
    }
    
    /** Add change listeners for sliders if b;
      * Remove changelisteners for sliders if !b. 
      */
    public void fixListeners(boolean b) {
        if (b) {
            RSlider.addChangeListener(this);
            GSlider.addChangeListener(this);
            BSlider.addChangeListener(this);
            HSlider.addChangeListener(this);
            SSlider.addChangeListener(this);
            VSlider.addChangeListener(this);
            CSlider.addChangeListener(this);
            MSlider.addChangeListener(this);
            YSlider.addChangeListener(this);
            KSlider.addChangeListener(this);
            
        } else {
            RSlider.removeChangeListener(this);
            GSlider.removeChangeListener(this);
            BSlider.removeChangeListener(this);
            CSlider.removeChangeListener(this);
            MSlider.removeChangeListener(this);
            YSlider.removeChangeListener(this);
            KSlider.removeChangeListener(this);
            HSlider.removeChangeListener(this);
            SSlider.removeChangeListener(this);
            VSlider.removeChangeListener(this);
        }
    }
    
    /** Process a movement in one of the sliders */
    public void stateChanged(ChangeEvent e) {
        Color rgb;
        CMYK cmyk;
        HSV hsv;
        
        // Remove listeners so that user changes during
        // execution of this method will have no effect.
        fixListeners(false);
        
        JSlider source= (JSlider)e.getSource();
        
        if (source.equals(HSlider) || source.equals(SSlider) ||
            source.equals(VSlider)) {
            double H= (double)HSlider.getValue() / 100.0;
            double S= (double)SSlider.getValue() / 100.0;
            double V= (double)VSlider.getValue() / 100.0;
            hsv= new HSV(H, S, V);
            rgb= A4Methods.convertHSVtoRGB(hsv);
            cmyk= A4Methods.convertRGBtoCMYK(rgb);
        }
        else if (source.equals(RSlider) || source.equals(GSlider) ||
                 source.equals(BSlider)) {
            int R= (int) Math.round(RSlider.getValue() / 100.0);
            int G= (int) Math.round(GSlider.getValue() / 100.0);
            int B= (int) Math.round(BSlider.getValue() / 100.0);
            rgb= new Color(R, G, B);
            hsv= A4Methods.convertRGBtoHSV(rgb);
            cmyk= A4Methods.convertRGBtoCMYK(rgb);
        }
        else if (source.equals(CSlider) || source.equals(MSlider) ||
                 source.equals(YSlider) || source.equals(KSlider)) {
            double C= CSlider.getValue() / 100.0;
            double M= MSlider.getValue() / 100.0;
            double Y= YSlider.getValue() / 100.0;
            double K= KSlider.getValue() / 100.0;
            cmyk= new CMYK(C, M, Y, K);
            rgb= A4Methods.convertCMYKtoRGB(cmyk);
            hsv= A4Methods.convertRGBtoHSV(rgb);
        }
        else return;  // event was not a change in a slider
        
        setSliders(rgb, hsv, cmyk);
        setColorPanels(rgb, hsv, cmyk);
        
        // Add the listeners for the sliders
        fixListeners(true);
    }
    
    /** Set the RGB, HSV, CMYK sliders based on rgb, hsv, and cmyk. */
    public void setSliders(Color rgb, HSV hsv, CMYK cmyk) {
        RSlider.setValue(rgb.getRed() * 100);
        GSlider.setValue(rgb.getGreen() * 100);
        BSlider.setValue(rgb.getBlue() * 100);
        
        HSlider.setValue((int)(hsv.hue() * 100.0));
        SSlider.setValue((int)(hsv.sat() * 100.0));
        VSlider.setValue((int)(hsv.val() * 100.0));
        
        CSlider.setValue((int)(cmyk.cyan() * 100.0));
        MSlider.setValue((int)(cmyk.magenta() * 100.0));
        YSlider.setValue((int)(cmyk.yellow() * 100.0));
        KSlider.setValue((int)(cmyk.black() * 100.0));
    }
    
    /** Set background colors and the text in the color window and
      * complementary color window, based on rgb, hsv, and cmyk. Put
      *  the rgb in the title.
      */
    public void setColorPanels(Color rgb, HSV hsv, CMYK cmyk) {
        image.setBackground(rgb);
        Color compRgb= A4Methods.complementRGB(rgb);
        compImage.setBackground(compRgb);
        
        imageLabel.setForeground(compRgb);
        imageLabel.setText("<html>&nbsp;Color<br>&nbsp;RGB:&nbsp;&nbsp;&nbsp;&nbsp;" + A4Methods.toString(rgb) + 
                           "<br>&nbsp;CMYK:&nbsp;" + A4Methods.toString(cmyk) + 
                           "<br>&nbsp;HSV:&nbsp;&nbsp;&nbsp;&nbsp;" + A4Methods.toString(hsv) + "<br><br>" +
                           "&nbsp;R,G,B sliders in: 0..255<br>" +
                           "&nbsp;C,M,Y,K sliders: 0 to 100%<br>" + 
                           "&nbsp;H slider: 0 &lt;= H &lt; 360 degrees<br>" + 
                           "&nbsp;S,V sliders: 0 &lt;= S,V &lt;= 1" + "</html>" );
        compImageLabel.setForeground(rgb);
        HSV compHsv= A4Methods.convertRGBtoHSV(compRgb);
        CMYK compCmyk= A4Methods.convertRGBtoCMYK(compRgb);
        compImageLabel.setText("<html>&nbsp;Complementary Color<br>&nbsp;RGB:&nbsp;&nbsp;&nbsp;&nbsp;" +
                               A4Methods.toString(compRgb) +
                               "<br>&nbsp;CMYK:&nbsp;" + A4Methods.toString(compCmyk) + 
                               "<br>&nbsp;HSV:&nbsp;&nbsp;&nbsp;&nbsp;" + A4Methods.toString(compHsv) + "<br><br>" +
                               "&nbsp;R,G,B sliders in: 0..255<br>" +
                               "&nbsp;C,M,Y,K sliders: 0 to 100%<br>" + 
                               "&nbsp;H slider: 0 &lt;= H &lt; 360 degrees<br>" + 
                               "&nbsp;S,V sliders: 0 &lt;= S,V &lt;= 1" +
                               "</html>");
        jf.setTitle("Color RGB: " + A4Methods.toString(rgb));
    }
    
    /** Process a click on the inRGB, inCMYK, or inHSV buttons. The
      * appropriate fields are read in, the GUI is set to that color,
      * and the other color models are set apropriately. 
      */
    public void actionPerformed(ActionEvent e) {
        // Remove listeners so that user changes during
        // execution of this method will have no effect.
        fixListeners(false);
        
        Color rgb;
        HSV hsv;
        CMYK cmyk;
        if (e.getSource() == inputrgb) {
            int r= getInt(rfield.getText());
            int g= getInt(gfield.getText());
            int b= getInt(bfield.getText());
            
            rfield.setText("" + r);
            gfield.setText("" + g);
            bfield.setText("" + b);
            
            rgb= new Color(r, g, b);
            hsv= A4Methods.convertRGBtoHSV(rgb);
            cmyk= A4Methods.convertRGBtoCMYK(rgb);
            
        } else if (e.getSource() == inputcmyk) {
            double c= getDouble100(cfield.getText());
            double m= getDouble100(mfield.getText());
            double y= getDouble100(yfield.getText());
            double k= getDouble100(kfield.getText());
            
            cfield.setText(A4Methods.roundTo5(c));
            mfield.setText(A4Methods.roundTo5(m));
            yfield.setText(A4Methods.roundTo5(y));
            kfield.setText(A4Methods.roundTo5(k));
            
            cmyk= new CMYK(c, m, y, k);
            rgb= A4Methods.convertCMYKtoRGB(cmyk);
            hsv= A4Methods.convertRGBtoHSV(rgb);
            
        } else {
            double h= getDouble360(hfield.getText());
            double s= getDouble(sfield.getText());
            double v= getDouble(vfield.getText());
            hsv= new HSV(h, s, v);
            rgb= A4Methods.convertHSVtoRGB(hsv);
            cmyk= A4Methods.convertRGBtoCMYK(rgb);
            
            hfield.setText(A4Methods.roundTo5(h));
            sfield.setText(A4Methods.roundTo5(s));
            vfield.setText(A4Methods.roundTo5(v));
            
        }
        
        setColorPanels(rgb, hsv, cmyk);
        setSliders(rgb, hsv, cmyk);
        
        // Add the listeners for the sliders
        fixListeners(true);
    }
    
    /** Yields: if s is not an int, 0 else max(0, min(255, s). */
    public static int getInt(String s) {
        try {
            int i= Integer.parseInt(s.trim());
            return Math.max(0, Math.min(255, i));
        } catch (NumberFormatException e) {
            return 0;
        }
    }
    
    /** Yields: if s is not a double, 0 else max(0.0, min(1.0, s). */
    public static double getDouble(String s) {
        try {
            double d= Double.parseDouble(s.trim());
            return Math.max(0.0, Math.min(1.0, d));
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }
    
    /** Yields: if s is not a double, 0.0 else max(0.0, min(100.0, s). */
    public static double getDouble100(String s) {
        try {
            double d= Double.parseDouble(s.trim());
            return Math.max(0.0, Math.min(100.0, d));
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }
    
    /** Yields: if s is not a double, 0 else max(0.0, min(359.9, s). */
    public static double getDouble360(String s) {
        try {
            double d= Double.parseDouble(s.trim());
            return Math.max(0.0, Math.min(359.9, d));
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }
}