import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Toggle;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.TilePane;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint;
import javafx.scene.paint.ImagePattern;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.geometry.Pos;

/**
 * This program demonstrates ImagePattern and LinearGradient paints.
 * A polygon is drawn that is filled with a paint selected by the user.
 * The user can also drag the vertices of the polygon.
 * This program requires image resource files named face-smile.png
 * and tile.png.
 */
public class PaintDemo extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    //----------------------------------------------------------------------------


    private Canvas canvas;  // The canvas where the polygon is drawn.

    private Paint paint;  // The paint that is used to fill the polygon in the canvas.

    private Image smiley, tile;  // Images for ImagePaint.

    private RadioButton gradientButton1, gradientButton2;  // Select the type of paint.
    private RadioButton patternButton1, patternButton2;

    private ToggleGroup paintStyleButtonGroup;  // Toggle group controlling the radio buttons.

    private double gradientAngle = 45, gradientWidth = 50;  // Settings that affect the paint.
    private double patternOffset = 0, patternScale = 100;

    private Slider slider1, slider2;  // Sliders that control the settings; which 
                                      // setting is affected depends on current paint type.

    private Label label1 = new Label("  Gradient Angle:");  // Labels change, depending
    private Label label2 = new Label("  Gradient Width:");  //   on type of paint.

    private double[] xcoord = {100, 420, 300, 470, 230, 40};  // Coords for vertices of polygon.
    private double[] ycoord = {100, 30, 240, 260, 480, 300};

    private int draggedPoint = -1;  // When a vertex is being dragged, this is the index
                                    // of the vertex coords in the above arrays.  The value
                                    // -1 means no vertex is being dragged.


    /**
     * Set up GUI and event handling.
     */
    public void start(Stage stage) {

        smiley = new Image("face-smile.png");
        tile = new Image("tile.png");

        canvas = new Canvas(500,500);
        slider1 = new Slider(0,360,gradientAngle);
        slider2 = new Slider(10,300,gradientWidth);
        gradientButton1 = new RadioButton("Black/Gray Gradient");
        gradientButton2 = new RadioButton("Red/Green/Blue Gradient");
        patternButton1 = new RadioButton("Smiley Face Pattern");
        patternButton2 = new RadioButton("Tile Pattern");
        gradientButton1.setSelected(true);

        paintStyleButtonGroup = new ToggleGroup();
        gradientButton1.setToggleGroup(paintStyleButtonGroup);
        gradientButton2.setToggleGroup(paintStyleButtonGroup);
        patternButton1.setToggleGroup(paintStyleButtonGroup);
        patternButton2.setToggleGroup(paintStyleButtonGroup);
        
        canvas.setOnMousePressed( this::mousePressed );
        canvas.setOnMouseDragged( this::mouseDragged );
        paintStyleButtonGroup.selectedToggleProperty().addListener( e -> {
            if (paintStyleButtonGroup.getSelectedToggle() != null)
                paintStyleChanged();
        });
        slider1.valueProperty().addListener( e -> {
            if (slider1.isValueChanging())
                setPaint();
        });
        slider2.valueProperty().addListener( e -> {
            if (slider2.isValueChanging())
                setPaint();
        });

        setPaint();
        
        BorderPane root = new BorderPane(canvas);
        root.setStyle("-fx-border-color: #444; -fx-border-width: 4");

        TilePane bottom = new TilePane(12,12);
        bottom.setStyle("-fx-padding: 12px; -fx-border-color: #444; -fx-border-width:3px 0 0 0");
        bottom.setTileAlignment(Pos.CENTER_LEFT);
        bottom.setAlignment(Pos.CENTER);
        bottom.setPrefColumns(2);
        bottom.getChildren().addAll(label1, slider1, label2, slider2,
                         gradientButton1, patternButton1, gradientButton2, patternButton2);
        root.setBottom(bottom);
        
        stage.setScene(new Scene(root));
        stage.setResizable(false);
        stage.setTitle("PaintDemo -- Drag the Vertices");
        stage.show();
    }

    
    /**
     *  Fill the canvas with white, then draw the polygon filled with the
     *  current fillPaint.  Draw small squares at the polygon vertices;
     *  the user can drag these squares to move the vertices.
     */
    private void drawCanvas() {
        GraphicsContext g = canvas.getGraphicsContext2D();
        g.setFill(Color.WHITE);
        g.fillRect(0,0,canvas.getWidth(),canvas.getHeight());
        g.setFill(paint);
        g.fillPolygon(xcoord, ycoord, 6);
        g.setStroke(Color.BLACK);
        g.setLineWidth(2);
        g.strokePolygon(xcoord, ycoord, 6);
        g.setFill(Color.BLACK);
        for (int i = 0; i < 6; i++)
            g.fillRect(xcoord[i] - 4, ycoord[i] - 4, 9, 9);
    }

    
    /**
     * Called when the user presses the mouse on the canvas.  Searches the
     * polygon vertices to find one near the mouse position.  If one is
     * found, the user can drag it.
     */
    private void mousePressed(MouseEvent e) {
        draggedPoint = -1;
        for (int i = 0; i < 6; i++) {
            if (Math.abs(xcoord[i] - e.getX()) < 5 && Math.abs(ycoord[i] - e.getY()) < 5) {
                draggedPoint = i;
                break;
            }
        }
    }

    
    /**
     * Called when the mouse is dragged on the canvas.  If a vertex is being
     * dragged, move it to the current mouse position and redraw the canvas.
     */
    private void mouseDragged(MouseEvent e) {
        if (draggedPoint < 0)
            return;
        double x = Math.max(0, Math.min(e.getX(),canvas.getWidth()));
        double y = Math.max(0, Math.min(e.getY(),canvas.getHeight()));
        xcoord[draggedPoint] = x;
        ycoord[draggedPoint] = y;
        drawCanvas();
    }


    /**
     * Responds when a user clicks on the radio button to select a new paint style.
     * This method sets up the labels and sliders to correspond to the kind of 
     * paint that has been selected.  Calls setPaint() to apply the paint to the
     * canvas.
     */
    private void paintStyleChanged() {
        Toggle currentButton = paintStyleButtonGroup.getSelectedToggle();
        if (currentButton == gradientButton1 || currentButton == gradientButton2) {
            label1.setText("  Gradient Angle:");
            label2.setText("  Gradient Width:");
            slider1.setMin(0);
            slider1.setMax(360);
            slider1.setValue(gradientAngle);
            slider2.setMin(20);
            slider2.setMax(200);
            slider2.setValue(gradientWidth);
            setPaint();
        }
        else if (currentButton == patternButton1 || currentButton == patternButton2){
            label1.setText("  Image Offset:");
            label2.setText("  Image Scale:");
            slider1.setMin(0);
            slider1.setMax(100);
            slider1.setValue(patternOffset);
            slider2.setMin(30);
            slider2.setMax(200);
            slider2.setValue(patternScale);
            setPaint();
        }
    }


    /**
     * Called when the type of paint or the values on the sliders are changed.
     * Creates the new Paint and redraws the canvas to show the change.
     */
    private void setPaint() {
        Toggle currentButton = paintStyleButtonGroup.getSelectedToggle(); // (can be null)
        if (currentButton == gradientButton1 || currentButton == gradientButton2) {
            gradientAngle = slider1.getValue();
            gradientWidth = slider2.getValue();
            double x1 = canvas.getWidth()/2;
            double y1 = canvas.getHeight()/2;
            double x2 = x1 + gradientWidth * Math.cos(gradientAngle/180.0 * Math.PI);
            double y2 = y1 + gradientWidth * Math.sin(gradientAngle/180.0 * Math.PI);
            if (currentButton == gradientButton1)  {
                paint = new LinearGradient(x1,y1,x2,y2,false,CycleMethod.REFLECT,
                        new Stop(0,Color.BLACK), new Stop(1,Color.LIGHTGRAY));
            }
            else {
                paint = new LinearGradient(x1,y1,x2,y2,false,CycleMethod.REFLECT,
                        new Stop(0,Color.RED), new Stop(0.5,Color.GREEN), new Stop(1,Color.BLUE));
            }
            drawCanvas();
        }
        else if (currentButton == patternButton1 || currentButton == patternButton2){
            patternOffset = slider1.getValue();
            patternScale = slider2.getValue();
            Image texture;
            if (currentButton == patternButton1)
                texture = smiley;
            else
                texture = tile;
            double width = texture.getWidth() * patternScale / 100;
            double height = texture.getHeight() * patternScale / 100;
            double offsetX = width * patternOffset / 100;
            double offsetY = height * patternOffset / 100;
            paint = new ImagePattern(texture,offsetX,offsetY,width,height,false);
            drawCanvas();
        }
    }



} // end class PaintDemo

