package mathgame;

import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.effect.InnerShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;
import javafx.util.Duration;

public class MathGame extends Application {
    BouncingBall bb0, bb1, bb2, bb3;
    static int dx = 1;
    static int dy = 1;
    Group root;
    Scene scene;
    Timeline tl;
    boolean inPlay = false;
    int firstNum;
    int[] secondNum = {0,0,0,0};
    int whichIsCorrect;
    int totalAttempts = 0;
    int totalWins = 0;
    //create a console for logging mouse events
    final ListView<String> console = new ListView<String>();
    //create a observableArrayList of logged events that will be listed in console
    final ObservableList<String> consoleObservableList = FXCollections.observableArrayList();
    {
        //set up the console
        console.setItems(consoleObservableList);
        console.setLayoutY(405);
        console.setPrefSize(500, 95);
    }
    
    final HBox hb = new HBox();
    Button createBtn = new Button("PLAY"); 
    {
        createBtn.setOnAction(new EventHandler<ActionEvent>() {
        @Override       
            public void handle(ActionEvent e) {
                inPlay = true;
                mathProbLbl.setText("hit button");
                Random rand = new Random();
                firstNum = rand.nextInt(9)+1;
                int[] numbersTaken = {0,0,0,0,0,0,0,0,0,0};
                for(int x = 0; x < 4; x++) {
                    int n = rand.nextInt(10);
                    while (numbersTaken[n] == 1) {
                        n++;
                        if (n>9) {
                            n=0;
                        }
                    }
                    numbersTaken[n]=1;
                    secondNum[x]=n;
                }
                whichIsCorrect = rand.nextInt(4);
                mathProbLbl.setText(firstNum + " * " + secondNum[whichIsCorrect] + " = ?");
                answerLbl.setText("");
                System.out.println("secondNum is " + secondNum[0] + " " + secondNum[1] + " " + secondNum[2] + " " + secondNum[3]);
                System.out.println("whichIsCorrect is " + whichIsCorrect);
                bb0.start(firstNum*secondNum[0], 0,150, 1, -1, 2);
                bb1.start(firstNum*secondNum[1], 250,0, 1, 1, 1);
                bb2.start(firstNum*secondNum[2], 400,150, -1, 1, 2);
                bb3.start(firstNum*secondNum[3], 250,200, -1, -1, 2);                
            }
        });
    }
    
    Label mathProbLbl = new Label("Drag the ball with the correct answer");
    Label answerLbl = new Label("");
    {
        mathProbLbl.setStyle("-fx-border-color:red; -fx-background-color: blue;");
        mathProbLbl.setStyle("-fx-font-family: \"Comic Sans MS\"; -fx-font-size: 20; -fx-text-fill: darkred;");
        answerLbl.setStyle("-fx-font-family: \"Comic Sans MS\"; -fx-font-size: 20; -fx-text-fill: darkgreen;");        
        //set up the HBox
        hb.getChildren().addAll(createBtn, mathProbLbl, answerLbl);
        hb.setSpacing(3);
        hb.setLayoutY(305);
        hb.setPrefSize(500, 45);  
    }
    
    final HBox hb2 = new HBox();    
    Label scoreLbl = new Label("");
    {
        scoreLbl.setStyle("-fx-font-family: \"Comic Sans MS\"; -fx-font-size: 20; -fx-text-fill: yellow;");        
        //set up the HBox
        hb2.getChildren().addAll(scoreLbl);
        hb2.setSpacing(3);
        hb2.setLayoutY(355);
        hb2.setPrefSize(500, 45);  
    }
    

    //create a rectangle - (500px X 300px) in which our circles can move
    final Rectangle rectangle = RectangleBuilder.create()
            .width(500).height(300)
            .fill(new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, new Stop[] {
                new Stop(1, Color.rgb(156,216,255)),
                new Stop(0, Color.rgb(156,216,255, 0.5))
            }))
            .stroke(Color.BLACK)
            .build();
    //variables for storing initial position before drag of circle
    private double initX;
    private double initY;
    private Point2D dragAnchor;

    private void init(Stage primaryStage) {
        root = new Group();
        primaryStage.setResizable(false);
        scene = new Scene(root, 500,500, Color.BLACK);
        primaryStage.setScene(scene);
        rectangle.setOnMouseMoved(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent me) {
                //log mouse move to console, method listed below
                showOnConsole("Mouse moved, x: " + me.getX() + ", y: " + me.getY() );
            }
        });
        // show all the circle , rectangle and console
        root.getChildren().addAll(rectangle, hb, hb2, console);

        bb0 = new BouncingBall(0, Color.BLACK, 25);
        root.getChildren().add(bb0);
        bb1 = new BouncingBall(1, Color.RED, 25);
        root.getChildren().add(bb1);
        bb2 = new BouncingBall(2, Color.BLACK, 25);
        root.getChildren().add(bb2);
        bb3 = new BouncingBall(3, Color.RED, 25);
        root.getChildren().add(bb3);
    }

    private void showOnConsole(String text) {
         //if there is 8 items in list, delete first log message, shift other logs and  add a new one to end position
         if (consoleObservableList.size()==8) {
            consoleObservableList.remove(0);
         }
         consoleObservableList.add(text);
    }

    
    private class BouncingBall extends StackPane {
        private Timeline tl;
        int whichBall;
        Circle ball;
        Text text;
        int dx = 1;
        int dy = 1;
        float speed;
        StackPane stack;

        public BouncingBall(int n, final Color color, int radius) {
            ball = new Circle(radius, new RadialGradient(0, 0, 0.2, 0.3, 1, true, CycleMethod.NO_CYCLE, new Stop[] {
                new Stop(0, Color.rgb(250,250,255)),
                new Stop(1, color)
            }));
            text = new Text("42");
            text.setStyle("-fx-border-color:red; -fx-background-color: blue;");
            text.setStyle("-fx-font-family: \"Comic Sans MS\"; -fx-font-size: 20; -fx-text-fill: darkred;");

            text.setBoundsType(TextBoundsType.VISUAL); 
            stack = this;
            stack.getChildren().addAll(ball,text);
            whichBall = n;
            ball.setEffect(new InnerShadow(7, color.darker().darker()));
            stack.setCursor(Cursor.HAND);
            stack.setOnMouseClicked(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                    showOnConsole("Clicked on ball#" + whichBall + ", " + me.getClickCount() + "times");
                    //the event will be passed only to the circle which is on front
                    me.consume();
                }
            });
            stack.setOnMouseDragged(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                    //tl.stop();
                    bb0.stop();
                    bb1.stop();
                    bb2.stop();
                    bb3.stop();
                    if (inPlay) {
                        totalAttempts++;
                        if (whichBall==whichIsCorrect) {
                            answerLbl.setText("Correct!  Answer is " + firstNum*secondNum[whichIsCorrect]);
                            totalWins++;
                        } else {
                            answerLbl.setText("Sorry, answer was " + firstNum*secondNum[whichIsCorrect]);
                        }
                        scoreLbl.setText("You got " + totalWins + " out of " + totalAttempts);                        
                        inPlay = false;
                    }
                    double dragX = me.getSceneX() - dragAnchor.getX();
                    double dragY = me.getSceneY() - dragAnchor.getY();
                    //calculate new position of the circle
                    double newXPosition = initX + dragX;
                    double newYPosition = initY + dragY;
                    //if new position do not exceeds borders of the rectangle, translate to this position
                    if ((newXPosition>=ball.getRadius()) && (newXPosition<=500-ball.getRadius())) {
                        stack.setTranslateX(newXPosition); 
                    }
                    if ((newYPosition>=ball.getRadius()) && (newYPosition<=300-ball.getRadius())){
                        stack.setTranslateY(newYPosition);
                    }
                    showOnConsole("ball #" + whichBall + " was dragged (x:" + dragX + ", y:" + dragY +")");
                }
            });
            stack.setOnMouseEntered(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                    //change the z-coordinate of the circle
                    stack.toFront();
                    showOnConsole("Mouse entered ball " + whichBall);
                }
            });
            stack.setOnMouseExited(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                    showOnConsole("Mouse exited ball " + whichBall);
                }
            });
            stack.setOnMousePressed(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                     //when mouse is pressed, store initial position
                    initX = stack.getTranslateX();
                    initY = stack.getTranslateY();
                    dragAnchor = new Point2D(me.getSceneX(), me.getSceneY());
                    showOnConsole("Mouse pressed above ball " + whichBall);
                }
            });
            stack.setOnMouseReleased(new EventHandler<MouseEvent>() {
                public void handle(MouseEvent me) {
                    showOnConsole("Mouse released above ball " + whichBall);
                }
            });

            // set position so its out of view
            stack.setTranslateX(-100);
            stack.setTranslateY(-100);
            

            
        }
        
        public void stop() {
            if (null != tl) {
                tl.stop();
                System.out.println("tl was stopped");
            }    
        }
        
        public void start(int num, int x, int y, int diffX, int diffY, float s) {
            if (null != tl) {
                tl.stop();
                System.out.println("tl was stopped");
            }    
            stack.setTranslateX(x);
            stack.setTranslateY(y);
            
            dx = diffX;
            dy = diffY;
            
            speed = s;
            
            text.setText(""+num);
            
            tl = new Timeline();
            tl.setCycleCount(Animation.INDEFINITE);
            KeyFrame moveBall = new KeyFrame(Duration.seconds(.0200),
                    new EventHandler<ActionEvent>() {

                        public void handle(ActionEvent event) {

                            double xMin = stack.getBoundsInParent().getMinX();
                            double yMin = stack.getBoundsInParent().getMinY();
                            double xMax = stack.getBoundsInParent().getMaxX();
                            double yMax = stack.getBoundsInParent().getMaxY();

                            if (xMin < 0 || xMax > scene.getWidth()) {
                                dx = dx * -1;
                            }
                            //if (yMin < 0 || yMax > scene.getHeight()) {
                            if (yMin < 0 || yMax > 300) {
                                dy = dy * -1;
                            }

                            stack.setTranslateX(stack.getTranslateX() + dx*speed);
                            stack.setTranslateY(stack.getTranslateY() + dy*speed);

                        }
                    });

            tl.getKeyFrames().add(moveBall);
            tl.play();
            
        }
    }
    
    @Override public void start(Stage primaryStage) throws Exception {
        init(primaryStage);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
