
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;

/**
 * MosaicDraw is a JavaFX Application whose scene includes a MosaicCanvas and a
 * MenuBar.  The mosaic is made up of rows and columns of squares.  The user 
 * can click-and-drag the mouse to color the squares.  A menu bar with some 
 * options is shown at the top of the window. 
 * 
 * Note that this class depends on MosaicCanvas.java.
 */
public class MosaicDraw extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    
    //----------------------------------------------------------------------------------
    
    private final static int ROWS = 40;        // rows in the mosaic
    private final static int COLUMNS = 40;     // columns in the mosaic
    private final static int SQUARE_SIZE = 15; // size of each square

    private final static int DRAW_TOOL = 0;      // possible values for currentTool
    private final static int ERASE_TOOL = 1;
    private final static int DRAW_3x3_TOOL = 2;
    private final static int ERASE_3x3_TOOL = 3;

    private int currentTool;  // The current tool; this is changed when the
                              // user makes a selection from the Tools menu.

    private int currentRed, currentGreen, currentBlue;  // The current color.
                    // These change when the user selects from the Color menu
                    // They are used whenever a square is painted.  (NOTE:
                    // I am using three integers to represent the color, rather
                    // than a variable of type Color, to make it easier to add
                    // in the random color variation.)

    private MosaicCanvas mosaic;     // The panel where the drawing takes place.

    private CheckMenuItem useRandomness;  // If selected, then a small random variation is
                                          // added to the current color whenever a 
                                          // square is painted.  This menu item is added
                                          // to the Control menu in createMenuBar().
    
    private CheckMenuItem useSymmetry;  // If selected, then whenever a square is painted
                                        // or erased, the three symmetrical squares
                                        // obtained by reflecting the square vertically
                                        // and horizontally are also painted or erased.
                                        // This menu item is added to the Control menu
                                        // in createMenuBar().

    /**
     * Create the canvas and menu bar and add them to the window.
     * Set up mouse handling on the canvas.
     */
    public void start(Stage stage) {
        
        mosaic = new MosaicCanvas(ROWS, COLUMNS, SQUARE_SIZE, SQUARE_SIZE);
        mosaic.setOnMousePressed( e -> doMouse(e));
        mosaic.setOnMouseDragged( e -> doMouse(e));
        mosaic.clear();
        currentRed = 255;
        currentGreen = 0;
        currentBlue = 0;

        BorderPane root = new BorderPane();
        Scene scene = new Scene(root);
        
        root.setCenter(mosaic);
        root.setTop( createMenuBar() );
        
        stage.setTitle("Mosaic Draw");
        stage.setScene(scene);
        stage.setResizable(false);
        stage.setX(150);  // Put the window at screen coords (150,100).
        stage.setY(100);
        stage.show();
    }    
    

    /**
     * Creates and returns a menu bar that contains options that affect the
     * drawing that is done on the MosaicCanvas.
     */
    private MenuBar createMenuBar() {
        
        MenuBar menuBar = new MenuBar();

        Menu controlMenu = new Menu("Control");
        MenuItem fill = new MenuItem("Fill");
        controlMenu.getItems().add(fill);
        fill.setOnAction(e -> doFill());
        MenuItem clear = new MenuItem("Clear");
        controlMenu.getItems().add(clear);
        clear.setOnAction(e -> mosaic.clear());
        controlMenu.getItems().add(new SeparatorMenuItem());
        useRandomness = new CheckMenuItem("Use Randomness");
        useRandomness.setSelected(true);
        useSymmetry = new CheckMenuItem("Use Symmetry");
        CheckMenuItem useGrouting = new CheckMenuItem("Use Grouting");
        mosaic.setGroutingColor(null);  // turn off grouting to match state of menu item
        useGrouting.setOnAction( e -> doUseGrouting(useGrouting.isSelected()));
        controlMenu.getItems().addAll(useRandomness, useSymmetry, useGrouting);
        
        Menu colorMenu = new Menu("Color");
        String[] colorNames = { "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "Gray" };
        ToggleGroup colorGroup = new ToggleGroup();
        for (int i = 0; i < colorNames.length; i++) {
            String colorName = colorNames[i];
            RadioMenuItem item = new RadioMenuItem(colorName);
            item.setOnAction( e -> doColorChoice(colorName) );
            item.setToggleGroup(colorGroup);
            colorMenu.getItems().add(item);
            if (i == 0)
                item.setSelected(true);
        }
        
        Menu toolMenu = new Menu("Tools");
        ToggleGroup toolGroup = new ToggleGroup();
        toolGroup.selectedToggleProperty().addListener( e -> doToolChoice(toolGroup.getSelectedToggle()) );
        addRadioMenuItem(toolMenu,"Draw",toolGroup, true);
        addRadioMenuItem(toolMenu,"Erase",toolGroup, false);
        addRadioMenuItem(toolMenu,"Draw 3x3",toolGroup, false);
        addRadioMenuItem(toolMenu,"Erase 3x3",toolGroup, false);
        
        menuBar.getMenus().addAll(controlMenu,colorMenu,toolMenu);
        return menuBar;
        
    }  // end createMenuBar()
    

    /**
     * Utility method to create a radio menu item, add it to a ToggleGroup, and add it to a menu.
     */
    private void addRadioMenuItem(Menu menu, String command, ToggleGroup group, boolean selected) {
        RadioMenuItem menuItem = new RadioMenuItem(command);
        menuItem.setToggleGroup(group);
        menu.getItems().add(menuItem);
        if (selected) {
            menuItem.setSelected(true);
        }
    }


    /**
     * Erases the square in a specified row and column.  If symmetry is turned
     * on, the three symmetrical squares are also erased.
     */
    private void eraseSquare(int row, int col) {
        mosaic.setColor(row, col, null);
        if (useSymmetry.isSelected()) {
            mosaic.setColor(mosaic.getRowCount() - 1 - row, col, null);
            mosaic.setColor(row, mosaic.getColumnCount() - 1 - col, null);
            mosaic.setColor(mosaic.getRowCount() - 1 - row, mosaic.getColumnCount() - 1 - col, null);
        }
    }
    

    /**
     * Applies the current drawing color to the square in a given row and column.
     * If randomness is turned on, a random amount is added to the red, green, and 
     * blue components of the drawing color.  If symmetry is turned on, then the
     * three symmetrical squares are also painted.
     */
    private void paintSquare(int row, int col) {
        int r = currentRed;
        int g = currentGreen;
        int b = currentBlue;
        if (useRandomness.isSelected()) {
            if (r < 60)
                r = (int)(60*Math.random());
            else if (r > 255-60)
                r = 255 - (int)(60*Math.random());
            else
                r = r + (int)(60*Math.random() - 30);
            if (g < 60)
                g = (int)(60*Math.random());
            else if (g > 255-60)
                g = 255 - (int)(60*Math.random());
            else
                g = g + (int)(60*Math.random() - 30);
            if (b < 60)
                b = (int)(60*Math.random());
            else if (b > 255-60)
                b = 255 - (int)(60*Math.random());
            else
                b = b + (int)(60*Math.random() - 30);
        }
        Color color = Color.rgb(r,g,b);
        mosaic.setColor(row, col, color);
        if (useSymmetry.isSelected()) {
            mosaic.setColor(mosaic.getRowCount() - 1 - row, col, color);
            mosaic.setColor(row, mosaic.getColumnCount() - 1 - col, color);
            mosaic.setColor(mosaic.getRowCount() - 1 - row, mosaic.getColumnCount() - 1 - col, color);
        }
    }

    
    /**
     * This method is called when the user clicks the mouse or drags it over the
     * square in the specified row and column.  It takes the appropriate action,
     * depending on which drawing tool is currently selected.
     */
    private void applyCurrentTool(int row, int col) {
        int minrow, mincol, maxrow, maxcol;
        switch (currentTool) {
        case DRAW_TOOL:
            paintSquare(row,col);
            break;
        case ERASE_TOOL:
            eraseSquare(row,col);
            break;
        case DRAW_3x3_TOOL:
            minrow = Math.max(0, row-1);
            maxrow = Math.min(mosaic.getRowCount()-1, row+1);
            mincol = Math.max(0, col-1);
            maxcol = Math.min(mosaic.getColumnCount()-1, col+1);
            for (int i = minrow; i <= maxrow; i++)
                for (int j = mincol; j <= maxcol; j++)
                    paintSquare(i,j);
            break;
        case ERASE_3x3_TOOL:
            minrow = Math.max(0, row-1);
            maxrow = Math.min(mosaic.getRowCount()-1, row+1);
            mincol = Math.max(0, col-1);
            maxcol = Math.min(mosaic.getColumnCount()-1, col+1);
            for (int i = minrow; i <= maxrow; i++)
                for (int j = mincol; j <= maxcol; j++)
                    eraseSquare(i,j);
            break;
        }
    }
    
    
    /**
     * Fill the mosaic with squares of the current drawing color.
     * (If symmetry is on, each square actually has its color set
     * four times, so efficiency could be improved!)
     */
    private void doFill() {
        mosaic.setAutopaint(false); // Turning off autopaint lets all the squares
                                    // be draws without inserting any delays;
                                    // when autopaint is on, there is a one
                                    // millisecond delay for each square drawn.
        for (int row = 0; row < mosaic.getRowCount(); row++)
            for (int col = 0; col < mosaic.getColumnCount(); col++)
                paintSquare(row,col);
        mosaic.setAutopaint(true);
    }
    

    /**
     * Apply the current tool to the square that contains the mouse pointer.
     * This method is called in response to both MousePressed and MouseDragged
     * event.  The mouse handlers that called it are added to the mosaic
     * in the start() method.
     */
    private void doMouse(MouseEvent evt) {
        int row = mosaic.yCoordToRowNumber((int)evt.getY());
        int col = mosaic.xCoordToColumnNumber((int)evt.getX());
        if (row >= 0 && row < mosaic.getRowCount() && col >= 0 && col < mosaic.getColumnCount()) {
               // (the test in this if statement will be false if the user drags the
               //  mouse outside the canvas after pressing the mouse on the canvas)
            applyCurrentTool(row,col);
        }
    }

    
    /**
     * This method is called when the user clicks the "Use Grouting"
     * command in the Control menu.  The parameter tells whether
     * the CheckMenuItem is checked.  The handler that calls this
     * method is added to the menu item in the createMenuBar() method.
     */
    public void doUseGrouting(boolean use) {
        if (use)
            mosaic.setGroutingColor(Color.GRAY);
        else
            mosaic.setGroutingColor(null);  // Turns grouting off.
    }
    
    
    /**
     * Changes the current drawing color when the user clicks one of the
     * colors in the Color menu.  A handler to call this method is added
     * to each color menu item in the createMenuBar() method.  (Note that
     * this method will be called even if the user picks the color that
     * is already selected, but that is harmless.)
     */
    private void doColorChoice( String colorName ) {
        if (colorName.equals("Red")) { 
            currentRed = 255;
            currentGreen = 0;
            currentBlue = 0;
        }
        else if (colorName.equals("Green")) {
            currentRed = 0;
            currentGreen = 180;
            currentBlue = 0;
        }
        else if (colorName.equals("Blue")) {
            currentRed = 0;
            currentGreen = 0;
            currentBlue = 255;
        }
        else if (colorName.equals("Cyan")) {
            currentRed = 0;
            currentGreen = 255;
            currentBlue = 255;
        }
        else if (colorName.equals("Magenta")) {
            currentRed = 255;
            currentGreen = 0;
            currentBlue = 255;
        }
        else if (colorName.equals("Yellow")) {
            currentRed = 255;
            currentGreen = 255;
            currentBlue = 0;
        }
        else if (colorName.equals("Gray")) {
            currentRed = 180;
            currentGreen = 180;
            currentBlue = 180;
        }
    }
    
    
    /**
     * This method is called when the selected item in the
     * Tools menu is changed for any reason.  The handler
     * is added to the ToggleGroup that controls the menu
     * items.  Apparently, it is called twice when the user
     * changes the selection, once when the current item is
     * deselected (with parameter value null) and once
     * when the new item is selected.  The current tool is only
     * changed when the parameter is non-null.
     */
    private void doToolChoice( Toggle toggle ) {
        if (toggle == null)
            return;
        String toolName = ((RadioMenuItem)toggle).getText();
        if (toolName.equals("Draw"))
            currentTool = DRAW_TOOL;
        else if (toolName.equals("Erase"))
            currentTool = ERASE_TOOL;
        else if (toolName.equals("Draw 3x3"))
            currentTool = DRAW_3x3_TOOL;
        else if (toolName.equals("Erase 3x3"))
            currentTool = ERASE_3x3_TOOL;
    }
    

} // end MosaicDraw
