package statecapitalsgame;

import java.util.ArrayList;
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;
import static javafx.application.Application.launch;

public class StateCapitalsGame 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;
    
    static int screenWidth = 1100;
    
    private enum Region {
        Midwest,Northeast, Southeast, West, Southwest
    }
    private class State {
        String name, capital;  
        Region region;
        public State (String a, String b, Region r) {
            this.name = a;
            this.capital = b;
            this.region = r;
        }

        public Region getRegion() {
            return region;
        }

        public void setRegion(Region region) {
            this.region = region;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getCapital() {
            return capital;
        }

        public void setCapital(String capital) {
            this.capital = capital;
        }
        
    }
    
    State[] allStates = {
        new State("Illinois", "Springfield", Region.Midwest),
        new State("Indiana", "Indianapolis", Region.Midwest),
        new State("Michigan", "Lansing", Region.Midwest),
        new State("Minnesota", "St. Paul", Region.Midwest),
        new State("Ohio", "Columbus", Region.Midwest),
        new State("Wisconsin", "Madison", Region.Midwest),
        new State("Iowa", "Des Moines", Region.Midwest),
        new State("Kansas", "Topeka", Region.Midwest),
        new State("Missouri", "Jefferson City", Region.Midwest),
        new State("Nebraska", "Lincoln", Region.Midwest),
        new State("North Dakota", "Bismarck", Region.Midwest),
        new State("South Dakota", "Pierre", Region.Midwest)
    };
    
    private class QuestionAnswer {
        String question, answer;
        
        public QuestionAnswer(String q, String a) {
            this.question = q;
            this.answer = a;
        }

        public String getQuestion() {
            return question;
        }

        public void setQuestion(String question) {
            this.question = question;
        }

        public String getAnswer() {
            return answer;
        }

        public void setAnswer(String answer) {
            this.answer = answer;
        }
        
    }
    
    //private QuestionAnswer[] questionAnswers;
    ArrayList<QuestionAnswer> questionAnswers = new ArrayList<QuestionAnswer>();
    
    //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(screenWidth, 95);
    }
    
    final HBox hb = new HBox();
    Button createBtn = new Button("PLAY"); 
    {
        createBtn.setOnAction(new EventHandler<ActionEvent>() {
        @Override       
            public void handle(ActionEvent e) {
                inPlay = true;
                theProbLbl.setText("hit button");
                Random rand = new Random();
                //firstNum = rand.nextInt(9)+1;
                //int[] numbersTaken = {0,0,0,0,0,0,0,0,0,0};

                int[] numbersTaken = new int[questionAnswers.size()];
                for (int a  : numbersTaken) {
                    a = 0;
                }
                for(int x = 0; x < 4; x++) {
                    int n = rand.nextInt(numbersTaken.length);
                    while (numbersTaken[n] == 1) {
                        n++;
                        if (n>(numbersTaken.length-1)) {
                            n=0;
                        }
                    }
                    numbersTaken[n]=1;
                    secondNum[x]=n;
                }
                whichIsCorrect = rand.nextInt(4);
                //mathProbLbl.setText(firstNum + " * " + secondNum[whichIsCorrect] + " = ?");
                theProbLbl.setText(questionAnswers.get(secondNum[whichIsCorrect]).getQuestion());                
                answerLbl.setText("");
                //System.out.println("secondNum is " + secondNum[0] + " " + secondNum[1] + " " + secondNum[2] + " " + secondNum[3]);
                //System.out.println("whichIsCorrect is " + whichIsCorrect);
                bb0.start(questionAnswers.get(secondNum[0]).getAnswer(), 0,150, 1, -1, 2);
                bb1.start(questionAnswers.get(secondNum[1]).getAnswer(), 250,0, 1, 1, 1);
                bb2.start(questionAnswers.get(secondNum[2]).getAnswer(), 400,150, -1, 1, 2);
                bb3.start(questionAnswers.get(secondNum[3]).getAnswer(), 250,110, -1, -1, 2);                
            }
        });
    }
    
    Label theProbLbl = new Label("Drag the ball with the correct answer");
    Label answerLbl = new Label("");
    {
        theProbLbl.setStyle("-fx-border-color:red; -fx-background-color: blue;");
        theProbLbl.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, theProbLbl, answerLbl);
        hb.setSpacing(3);
        hb.setLayoutY(305);
        hb.setPrefSize(screenWidth, 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(screenWidth, 45);  
    }
    

    //create a rectangle - (500px X 300px) in which our circles can move
    final Rectangle rectangle = RectangleBuilder.create()
            .width(screenWidth).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 fillWithRegion(Region r) {
        questionAnswers.clear();
        for (State s  : allStates) {
            if (s.getRegion()==Region.Midwest) {
                questionAnswers.add(new QuestionAnswer("What is the capital of " + s.getName() + "?",s.getCapital()));
            }        
        }
    }

    private void init(Stage primaryStage) {
        fillWithRegion(Region.Midwest);
        root = new Group();
        primaryStage.setResizable(false);
        scene = new Scene(root, screenWidth,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);
        root.getChildren().addAll(rectangle, hb, hb2);

        bb0 = new BouncingBall(0, Color.BLACK, 65);
        root.getChildren().add(bb0);
        bb1 = new BouncingBall(1, Color.RED, 65);
        root.getChildren().add(bb1);
        bb2 = new BouncingBall(2, Color.BLACK, 65);
        root.getChildren().add(bb2);
        bb3 = new BouncingBall(3, Color.RED, 65);
        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 " + questionAnswers.get(secondNum[whichIsCorrect]).getAnswer());
                            totalWins++;
                        } else {
                            answerLbl.setText("Sorry, answer was " + questionAnswers.get(secondNum[whichIsCorrect]).getAnswer());
                        }
                        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<=screenWidth-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(String answer, 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(answer);
            
            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);
    }
}
