//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** @author   Casey Bowman
 *  @version  1.1
 *  @date     Wed Mar 11 15:52:54 EDT 2015 
 *  @see      (License) MIT style license
 */

import javafx.application.Application; 
import javafx.scene.Group; 
import javafx.scene.Scene; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;

//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** A class for visualinzing the connection between hexadecimal
 *  color codes and the colors themselves.  Built using javafx.
 *  
 *  @const screenWidth   the width of the overall window
 *  @const screenHeight  the height of the window
 *  @const rectWidth     the width of the color rectangle
 *  @const rectHeight    the height of the color rectangle
 *  @const rectX         the x-coordinate of the rectangle
 *  @const rectY         the y-coordinate of the rectangle
 *  @const arcWidth      the width of the rounded corner
 *  @const arcHeight     the height of the rounded corner
 *  @const spacing       a value to help spacing inside a VBox
 *  @const rect          the rectangle for showing colors
 *  @const submit        a submit button to submit a code
 *  @const tf            a textfield for inputting hex color codes
 *  @const vals          a label for the rgb values of the color
 *  @const error         a label to display any errors of input
 *  @const left          a VBox for submit, tf, vals, and error
 *  @const center        a VBox for rect
 *  @const pane          a BorderPane to hold left and center
 *  @const root          the Group for this application
 *  @const scene         the Scene representing the window
 */      
public class ColorVisualizer extends Application
{
    private int screenWidth  = 420;
    private int screenHeight = 270;
    
    private int rectWidth  = 150;
    private int rectHeight = 150;
   
    private int rectX = (screenWidth  - rectWidth)  / 2;
    private int rectY = (screenHeight - rectHeight) / 2;

    private int arcWidth  = 20;
    private int arcHeight = 20;

    private double spacing = 10.0;

    private Rectangle rect = makeRect (rectX, rectY, rectWidth, rectHeight, arcWidth, arcHeight);
    private Button submit = new Button ("Submit New Color Code");
    private TextField tf = new TextField ("0x");
    private Label vals = new Label ("\nRed = 0\n\nGreen = 0\n\nBlue = 0");
    private Label error = new Label ();

    private VBox left   = new VBox (spacing);
    private VBox center = new VBox (); 
    private BorderPane pane = new BorderPane ();

    private Group root = new Group ();
    private Scene scene = new Scene (root, screenWidth, screenHeight, Color.web ("#aaa"));        

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** the main method calls launch in this javafx application
     *
     *  @param args  the command line arguments
     */
    public static void main (String[] args) { launch (args); }

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** The start method is called by launch and directs the creation of the window.
     *
     *  @param stage  the stage holding the scene
     */
    public void start (Stage stage)
    {
        vals.setStyle ("-fx-border: black;");
        vals.setFont (Font.font (null, FontWeight.BOLD, 16.0));
        addListeners ();
        addComponents ();
        stage.setScene (scene);
        stage.show ();
    } // start

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Add the action listener for the submit button.  When it is pressed, the 
     *  color code in the text field is retrieved and the fill for the rectangle
     *  is updated appropriately, and the two labels are updated as well.
     */
    public void addListeners ()
    {
        submit.setOnAction (new EventHandler  ()
        {
            //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
            /** The handle method for this EventHandler is overridden with our 
             *  specific updates. If the input is invalid, then an exception is
             *  thrown signalling that the update method should print an error 
             *  message instead.  
             *
             *  @param event  an ActionEvent generated by the click of the submit button
             */
            @Override
            public void handle (ActionEvent event)
            {
               String code  = tf.getText ();
               Color newCol;
               boolean cont = true;
               try {
                   newCol = Color.web (code);
               }
               catch (Exception e) { cont = false; newCol = (Color) rect.getFill (); }
               changeState (cont, newCol);
            } // handle

        }); // setOnAction

    } // addListeners

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** A method to change the state of the window appropriately.  If valid is false,
     *  then an error message is displayed in the error label.  Otherwise the rectangle
     *  is updated with the correct color, and the vals label is updated with the 
     *  correct information.
     *
     *  @param valid  a boolean which informs the method if the input was valid.
     *  @param color  the new color to display in the rectangle.
     */
    public void changeState (boolean valid, Color color)
    {
        if (valid) {
            int red   = (int) (color.getRed ()   * 256);
            int green = (int) (color.getGreen () * 256);
            int blue  = (int) (color.getBlue ()  * 256);
            String r = "\nRed = "   + red   + "  (~" + (int)(red   / 2.56) + "%)";
            String g = "\nGreen = " + green + "  (~" + (int)(green / 2.56) + "%)";
            String b = "\nBlue = "  + blue  + "  (~" + (int)(blue  / 2.56) + "%)";
            rect.setFill (color); 
            vals.setText (r + "\n" + g + "\n" + b);
            error.setText ("");        
        } else {
            error.setText ("Invalid color code!");
        }
    } // changeState

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Sets up components and adds them to the Group.  Insets help control 
     *  the spacing of components within content panes.
     */
    public void addComponents ()
    {
        left.setPadding (new Insets (10, 10, 10, 10));
        center.setPadding (new Insets (10, 10, 10, 10));
        left.getChildren ().addAll (submit, tf, vals, error);
        center.getChildren ().add  (rect);
        pane.setPadding (new Insets (20,20,20,20));
        pane.setLeft (left);
        pane.setCenter (center);
        root.getChildren ().add (pane);
    } // addComponents

    //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** makeRect creates rectangles.
     *
     *  @param x   the x-coordinate of the rectangle
     *  @param y   the y-coordinate of the rectangle
     *  @param w   the width of the rectangle
     *  @param h   the height of the rectangle
     *  @param aw  the width of the rounded corner
     *  @param ah  the height of the rounded corner
     */
    public Rectangle makeRect (int x, int y, int w, int h, int aw, int ah) 
    {
        Rectangle r = new Rectangle (x, y, w, h);
        r.setArcWidth  (aw);
        r.setArcHeight (ah);
        r.setStroke (Color.BLACK);
        r.setStrokeWidth (5);
        return r;
    } // makeRect

}