
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Pane;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import java.util.List;


/**
 *  The class Mosaic makes available a window made up of a grid
 *  of colored rectangles.  Routines are provided for opening and
 *  closing the window and for setting and testing the color of rectangles
 *  in the grid.  The program will end when the window is closed, either
 *  because the user click's the window's close box or because the
 *  program calls Mosaic.close().
 *
 *  Each rectangle in the grid has a color.  The color can be
 *  specified by red, green, and blue amounts in the range from
 *  0 to 255.  It can also be given as an object belonging
 *  to the class Color.
 */

public class Mosaic extends Application {
    
    // Note: This class does things with static methods that should really
    // not be done in a JavaFX application!  It is NOT a good example of
    // writing a JavaFX application.  It was written to provide an API
    // to be used as an example in a textbook before objects have been covered.

    private static Stage window;    // The application running the mosaic window (if one is open).
    private static MosaicCanvas canvas;  // A component that actually manages and displays the rectangles.
    private static boolean use3DEffect = true; // When true, 3D Rects and "grouting" are used on the mosaic.
    private static int mosaicRows;      // The number of rows in the mosaic, if the window is open.
    private static int mosaicCols;      // The number of cols in the mosaic, if the window is open.


    /** 
     * Open the mosaic window with a 20-by-20 grid of squares, where each
     * square is 15 pixel on a side.  Has no effect if the window is already open.
     */
    public static void open() {
        open(20,20,15,15);
    }


    /**
     * Opens the  mosaic window containing a specified number of rows and
     * a specified number of columns of square.  Each square is 15 pixels
     * on a side.  Has no effect if the window is already open.
     */
    public static void open(int rows, int columns) {
        open(rows,columns,15,15);
    }


    /**
     * Opens the "mosaic" window on the screen.  If the mosaic window was
     * already open, has no effect.
     *
     * Precondition:   The parameters rows, cols, w, and h are positive integers, and
     *                    the mosaic window is not already open.
     * Postcondition:  A window is open on the screen that can display rows and
     *                   columns of colored rectangles.  Each rectangle is w pixels
     *                   wide and h pixels high.  The number of rows is given by
     *                   the first parameter and the number of columns by the
     *                   second.  Initially, all rectangles are black.
     * Note:  The rows are numbered from 0 to rows - 1, and the columns are 
     * numbered from 0 to cols - 1.
     */
    public static void open(int rows, int columns, int blockHeight, int blockWidth) {
        if ( window != null )
            return;
        mosaicRows = rows;
        mosaicCols = columns;
        new Thread( () -> launch(Mosaic.class, new String[] {""+rows,""+columns,""+blockWidth,""+blockHeight}) ).start();
        do {
            delay(100);
        } while (window == null || canvas == null);
    }


    /**
     * Close the mosaic window, if one is open, and ends the program.
     * The program will also end if the user closes the window.
     */
    public static void close() {
        if (window != null) {
            Platform.runLater( () -> window.close() );
        }
    }


    /**
     * Tests whether the mosaic window is currently open.
     *
     * Precondition:   None.
     * Postcondition:  The return value is true if the window is open when this
     *                   function is called, and it is false if the window is
     *                   closed.
     */
    public static boolean isOpen() {
        return (window != null);
    }


    /**
     * Inserts a delay in the program (to regulate the speed at which the colors
     * are changed, for example).  Note that there is already a short delay
     * of about 1 millisecond between drawing operations.  Calling this method
     * will add to that delay.
     *
     * Precondition:   milliseconds is a positive integer.
     * Postcondition:  The program has paused for at least the specified number
     *                   of milliseconds, where one second is equal to 1000
     *                   milliseconds.
     */
    public static void delay(int milliseconds) {
        if (milliseconds > 0) {
            try { Thread.sleep(milliseconds); }
            catch (InterruptedException e) { }
        }
    }


    /**
     * Gets the color of one of the rectangles in the mosaic.
     * 
     * Precondition:   row and col are in the valid range of row and column numbers.
     * Postcondition:  The color of the specified rectangle is returned as
     *                 object of type color.
     */
    public static Color getColor(int row, int col) {
        if (canvas == null)
            return Color.BLACK;
        return canvas.getColor(row, col);
    }


    /**
     * Gets the red component of the color of one of the rectangles.
     *
     * Precondition:   row and col are in the valid range of row and column numbers.
     * Postcondition:  The red component of the color of the specified rectangle is
     *                   returned as an integer in the range 0 to 255 inclusive.
     */
    public static int getRed(int row, int col) {
        if (canvas == null)
            return 0;
        if (row < 0 || row >= mosaicRows || col < 0 || col >= mosaicCols) {
            throw new IllegalArgumentException("(row,col) = (" + row + "," + col
                    + ") is not in the mosaic.");
        }
        return (int)(255*canvas.getRed(row, col));
    }


    /**
     * Like getRed, but returns the green component of the color.
     */
    public static int getGreen(int row, int col) {
        if (canvas == null)
            return 0;
        if (row < 0 || row >= mosaicRows || col < 0 || col >= mosaicCols) {
            throw new IllegalArgumentException("(row,col) = (" + row + "," + col
                    + ") is not in the mosaic.");
        }
        return (int)(255*canvas.getGreen(row, col));
    }


    /**
     * Like getRed, but returns the blue component of the color.
     */
    public static int getBlue(int row, int col) {
        if (canvas == null)
            return 0;
        if (row < 0 || row >= mosaicRows || col < 0 || col >= mosaicCols) {
            throw new IllegalArgumentException("(row,col) = (" + row + "," + col
                    + ") is not in the mosaic.");
        }
        return (int)(255*canvas.getBlue(row, col));
    }


    /**
     * Sets the color of one of the rectangles in the window.
     *
     * Precondition:   row and col are in the valid range of row and column numbers.
     * Postcondition:  The color of the rectangle in row number row and column
     *                 number col has been set to the color specified by c.
     *                 If c is null, the color of the rectangle is set to black.
     */
    public static void setColor(int row, int col, Color c) {
        if (canvas == null)
            return;
        if (row < 0 || row >= mosaicRows || col < 0 || col >= mosaicCols) {
            throw new IllegalArgumentException("(row,col) = (" + row + "," + col
                    + ") is not in the mosaic.");
        }
        canvas.setColor(row,col,c);
    }


    /**
     * Sets the color of one of the rectangles in the window.
     *
     * Precondition:   row and col are in the valid range of row and column numbers,
     *                   and r, g, and b are in the range 0 to 255, inclusive.
     * Postcondition:  The color of the rectangle in row number row and column
     *                   number col has been set to the color specified by r, g,
     *                   and b.  r gives the amount of red in the color with 0 
     *                   representing no red and 255 representing the maximum 
     *                   possible amount of red.  The larger the value of r, the 
     *                   more red in the color.  g and b work similarly for the 
     *                   green and blue color components.
     */
    public static void setColor(int row, int col, int red, int green, int blue) {
        if (canvas == null)
            return;
        if (row < 0 || row >= mosaicRows || col < 0 || col >= mosaicCols) {
            throw new IllegalArgumentException("(row,col) = (" + row + "," + col
                    + ") is not in the mosaic.");
        }
        canvas.setColor(row,col,red/255.0,green/255.0,blue/255.0);
    }


    /**
     * Fills the entire mosaic with a specified color.  If c is null, the mosaic
     * is filled with black.
     * 
     * Precondition:  The mosaic window must be open.
     */
    public static void fill(Color c) {
        canvas.fill(c);
    }


    /**
     * Fills the entire mosaic with a color that is specified by giving its
     * red, green, and blue components (numbers in the range 0 to 255).
     * 
     * Precondition:  The mosaic window must be open.
     */
    public static void fill(int red, int green, int blue) {
        canvas.fill(red/255.0,green/255.0,blue/255.0);
    }


    /**
     * Fill the entire mosaic window with random colors by setting
     * the color of each rectangle to a randomly selected red/blue/green
     * values.
     * 
     * Precondition:  The mosaic window must be open.
     */
    public static void fillRandomly() {
        canvas.fillRandomly();
    }
    
    
    /**
     *  If use3DEffect is true, which is the default, then rectangles are drawn
     *  as "3D" rects, which is supposed to make them look raised up from their
     *  background, and a 1-pixel gray border is drawn around the outside of
     *  the rectangles, giving better definition to the rows and columns.  If
     *  use3DEffect is set to false, ordinary "flat" rects are used, with no
     *  border between them.  The mosaic window does not have to be open when
     *  this is called.
     */
    public static void setUse3DEffect(boolean use3D) {
        use3DEffect = use3D;
        if (canvas != null) {
            canvas.setGroutingColor(use3DEffect? Color.GRAY : null);
            canvas.setUse3D(use3DEffect);
        }
    }
    
    
    /**
     * Returns the value of the use3DEffect property.
     * @return
     */
    public boolean getUse3DEffect() {
        return use3DEffect;
    }

    
    public void start(Stage stage) {
        window = stage;
        List<String> params = getParameters().getUnnamed();
        if (params.size() != 4)
            canvas = new MosaicCanvas();
        else
            canvas = new MosaicCanvas(Integer.parseInt(params.get(0)),Integer.parseInt(params.get(1)),
                    Integer.parseInt(params.get(2)),Integer.parseInt(params.get(3)));
        if (!use3DEffect)
            canvas.setGroutingColor(null);
        canvas.setUse3D(use3DEffect);
        canvas.forceRedraw();
        Pane pane = new Pane(canvas);
        StackPane root = new StackPane(pane);
        root.setStyle("-fx-border-width: 2px; -fx-border-color: #333");
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setOnCloseRequest( e -> { System.exit(0); } );
        stage.setTitle("Mosaic");
        stage.setResizable(false);
        stage.show();
    }
    

}  // end of class Mosaic
