package visualiser.Controllers;

import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.AmbientLight;
import javafx.scene.PointLight;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.exceptions.BoatNotFoundException;
import shared.model.*;
import visualiser.app.App;
import visualiser.enums.TutorialState;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.*;
import visualiser.model.Sparkline;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;
import visualiser.utils.GPSConverter;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Controller used to display a running race.
 */
public class RaceViewController extends Controller {
    private VisualiserRaceEvent visualiserRace;
    private VisualiserRaceState raceState;
    private ControllerClient controllerClient;
    private KeyFactory keyFactory = new KeyFactory();
    private boolean infoTableShow = true;  // shown or hidden
    private boolean isHost;
    private TutorialState currentState;
    private ArrayList<TutorialState> tutorialStates;
    private boolean isTutorial = false;
    private String keyToPress;
    private View3D view3D;
    private ObservableList<Subject3D> viewSubjects;

    /**
     * Arrow pointing to next mark in third person
     */
    private Subject3D nextMarkArrow;
    /**
     * Animation loop for rotating mark arrow
     */
    private AnimationTimer pointToMark;

    // note: it says it's not used but it is! do not remove :)
    private @FXML ArrowController arrowController;
    private @FXML GridPane canvasBase;
    private @FXML SplitPane racePane;
    private @FXML StackPane arrowPane;
    private @FXML Label timer;
    private @FXML Label FPS;
    private @FXML Label timeZone;
    private @FXML CheckBox showFPS;
    private @FXML TableView<VisualiserBoat> boatInfoTable;
    private @FXML TableColumn<VisualiserBoat, String> boatPlacingColumn;
    private @FXML TableColumn<VisualiserBoat, String> boatTeamColumn;
    private @FXML TableColumn<VisualiserBoat, Leg> boatMarkColumn;
    private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
    private @FXML LineChart<Number, Number> sparklineChart;
    private @FXML Label tutorialText;
    private @FXML AnchorPane infoWrapper;
    private @FXML AnchorPane lineChartWrapper;

    /**
     * Displays a specified race.
     * Intended to be called on loading the scene.
     * @param visualiserRace   Object modelling the race.
     * @param controllerClient Socket Client that manipulates the controller.
     * @param isHost           is user a host
     */
    public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
        this.visualiserRace = visualiserRace;
        this.raceState = visualiserRace.getVisualiserRaceState();
        this.controllerClient = controllerClient;
        this.isHost = isHost;
        keyFactory.load();

        tutorialCheck();
        initKeypressHandler();
        initialiseRaceVisuals();
    }

    /**
     * Checks if the current game is a tutorial race and sets up initial
     * tutorial displays if it is.
     */
    private void tutorialCheck(){
        if (App.gameType == 4) {
            isTutorial = true;
            tutorialText.setVisible(true);
            tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));

            currentState = tutorialStates.get(0);
            tutorialStates.remove(0);
            searchMapForKey("Upwind");

            tutorialText.setText(
                    "Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " +
                            keyToPress + " to turn upwind.");

        } else {
            isTutorial = false;
            tutorialText.setVisible(false);
        }
    }

    private AnimationTimer arrowToNextMark;

    private void initKeypressHandler() {
        racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            String codeString = event.getCode().toString();

            // tab key
            if (codeString.equals("TAB")){toggleTable();}

            // any key pressed
            ControlKey controlKey = keyFactory.getKey(codeString);
            if(controlKey != null) {
                try {
                    controlKey.onAction();  // Change key state if applicable

                    //Check if current race is a tutorial
                    if (isTutorial){
                        //Check if current tutorial state has the same boat protocol code as key press
                        if (controlKey.getProtocolCode().equals(currentState.getAction())){
                            //Update tutorial
                            checkTutorialState();
                        }
                    }

                    controllerClient.sendKey(controlKey);
                    event.consume();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    Logger.getGlobal().log(Level.WARNING, "RaceViewController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
                    Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // escape key
            if(event.getCode() == KeyCode.ESCAPE) {
                try {
                    if (isHost) {
                        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                        alert.setTitle("Exit Race");
                        alert.setContentText("Do you wish to quit the race? You are the host");
                        Optional<ButtonType> result = alert.showAndWait();
                        if (result.get() == ButtonType.OK) {
                            App.game.endEvent();
                            loadTitleScreen();
                        }
                    } else {
                        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                        alert.setTitle("Exit Race");
                        alert.setContentText("Do you wish to quit the race?");
                        Optional<ButtonType> result = alert.showAndWait();
                        if (result.get() == ButtonType.OK) {
                            loadTitleScreen();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Initialises the various UI components to listen to the {@link #visualiserRace}.
     */
    private void initialiseRaceVisuals() {

        // Import arrow mesh
        URL asset = this.getClass().getClassLoader().getResource("assets/arrow V1.0.4.stl");
        StlMeshImporter importer = new StlMeshImporter();
        importer.read(asset);

        MeshView arrow = new MeshView(importer.getImport());
        PhongMaterial arrowMat = new PhongMaterial(Color.RED);
        arrow.setMaterial(arrowMat);

        this.nextMarkArrow = new Annotation3D(arrow);
        this.nextMarkArrow.setScale(0.1);

        // initialise displays
        initialiseFps();
        initialiseInfoTable();
        initialiseView3D(this.visualiserRace);
        initialiseRaceClock();
        raceTimer();    // start the timer
        new Sparkline(this.raceState, this.sparklineChart);
        timeZone.setText(this.raceState.getRaceClock().getTimeZone());
        arrowController.setWindProperty(this.raceState.windProperty());
    }

    private void initialiseView3D(VisualiserRaceEvent race) {
        viewSubjects = FXCollections.observableArrayList();

        AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF"));
        ambientLight.setTranslateX(250);
        ambientLight.setTranslateZ(210);
        ambientLight.setLightOn(true);

        PointLight pointLight = new PointLight(Color.web("#AAAAFF"));
        pointLight.setTranslateX(250);
        pointLight.setTranslateZ(210);
        pointLight.setLightOn(true);

        // Import boat mesh
        URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
        StlMeshImporter importer = new StlMeshImporter();
        importer.read(asset);

        // Configure camera angles and control
        URL markerAsset = RaceViewController.class.getClassLoader().getResource("assets/Bouy V1.1.stl");
        StlMeshImporter importerMark = new StlMeshImporter();
        importerMark.read(markerAsset);

        URL alternateBoatAsset = RaceViewController.class.getClassLoader().getResource("assets/V1.3 BurgerBoat.stl");
        StlMeshImporter importerBurgerBoat = new StlMeshImporter();
        importerBurgerBoat.read(alternateBoatAsset);

        view3D = new View3D(false);
        view3D.setItems(viewSubjects);
        view3D.setDistance(1050);
        view3D.setBirdsEye();
        view3D.enableTracking();
        view3D.addAmbientLight(ambientLight);
        view3D.addPointLight(pointLight);
        canvasBase.add(view3D, 0, 0);

        // Set up projection from GPS to view
        RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
        final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);

        SkyBox skyBox = new SkyBox(750, 200, 250, 0, 210);
        viewSubjects.addAll(skyBox.getSkyBoxPlanes());

        // Set up sea surface
        SeaSurface sea = new SeaSurface(750, 200);
        sea.setX(250);
        sea.setZ(210);
        viewSubjects.add(sea);

        // Set up sea surface overlay
        SeaSurface seaOverlay = new SeaSurface(4000, 200);
        seaOverlay.setX(250);
        seaOverlay.setZ(210);
        viewSubjects.add(seaOverlay);

        Boundary3D boundary3D = new Boundary3D(visualiserRace.getVisualiserRaceState().getRaceDataSource().getBoundary(), gpsConverter);
        for (Subject3D subject3D: boundary3D.getBoundaryNodes()){
            viewSubjects.add(subject3D);
        }
        // Position and add each mark to view
        for(Mark mark: race.getVisualiserRaceState().getMarks()) {
            MeshView mesh = new MeshView(importerMark.getImport());
            Subject3D markModel = new Subject3D(mesh, mark.getSourceID());

            markModel.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
            markModel.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());

            viewSubjects.add(markModel);
        }
        // Position and add each boat to view
        for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
            MeshView mesh;
            if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
                mesh = new MeshView(importer.getImport());
            } else {
                mesh = new MeshView(importerBurgerBoat.getImport());
            }
            PhongMaterial boatColorMat = new PhongMaterial(boat.getColor());
            mesh.setMaterial(boatColorMat);
            Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());

            viewSubjects.add(boatModel);

            // Track this boat's movement with the new subject
            AnimationTimer trackBoat = new AnimationTimer() {
                @Override
                public void handle(long now) {
                    boatModel.setHeading(boat.getBearing().degrees());
                    boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
                    boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
                }
            };
            trackBoat.start();

            Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
            CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
            view3D.getShape(nextMark.getMark1().getSourceID()).getMesh().setMaterial(markColor);
            if(nextMark.getMark2() != null) {
                view3D.getShape(nextMark.getMark2().getSourceID()).getMesh().setMaterial(markColor);
            }
            Subject3D shockwave = new Shockwave(10);

            viewSubjects.add(shockwave);

            boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
            boat.hasCollidedProperty().addListener((o, prev, curr) -> Platform.runLater(() -> showCollision(boat, shockwave)));
        }
        // Fix initial bird's-eye position
        view3D.updatePivot(new Translate(250, 0, 210));

        view3D.targetProperty().addListener((o, prev, curr)-> {
            if(curr != null && visualiserRace.getVisualiserRaceState().isVisualiserBoat(curr.getSourceID())) {
                addThirdPersonAnnotations(curr);
            } else {
                removeThirdPersonAnnotations();
            }
        });

        // Bind zooming to scrolling
        view3D.setOnScroll(e -> {
            view3D.updateDistance(e.getDeltaY());
        });

        // Bind zooming to keypress (Z/X default)
        racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            ControlKey key = keyFactory.getKey(e.getCode().toString());
            if(key != null) {
                switch (key.toString()) {
                    case "Zoom In":
                        //Check if race is a tutorial
                        if (isTutorial) {
                            //Check if the current tutorial state is zoom-in
                            if (currentState.equals(TutorialState.ZOOMIN)) {
                                try {
                                    //Update tutorial
                                    checkTutorialState();
                                } catch (Exception e1) {
                                    e1.printStackTrace();
                                }
                            }
                        }
                        view3D.updateDistance(-10);
                        break;
                    case "Zoom Out":
                        //Check if race is a tutorial
                        if(isTutorial) {
                            //Check if current tutorial state is zoom-out
                            if (currentState.equals(TutorialState.ZOOMOUT)) {
                                try {
                                    //Update tutorial
                                    checkTutorialState();
                                } catch (Exception e1) {
                                    e1.printStackTrace();
                                }
                            }
                        }
                        view3D.updateDistance(10);
                        break;
                }
            }
        });
    }

    private void showCollision(VisualiserBoat boat, Subject3D shockwave) {
        Subject3D boatModel = view3D.getShape(boat.getSourceID());
        AnimationTimer shockFront = new AnimationTimer() {
            double opacity = 1;

            @Override
            public void handle(long now) {
                shockwave.setX(boatModel.getPosition().getX());
                shockwave.setY(boatModel.getPosition().getY());
                shockwave.setZ(boatModel.getPosition().getZ());

                if(opacity <= 0) {
                    shockwave.getMesh().setMaterial(new PhongMaterial(Color.TRANSPARENT));
                    boat.setHasCollided(false);
                    this.stop();
                }
                else {
                    shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,opacity)));
                    opacity -= 0.1;
                }
            }
        };
        shockFront.start();
    }

    private void addThirdPersonAnnotations(Subject3D subject3D) {
        viewSubjects.add(nextMarkArrow);
        final VisualiserBoat boat;
        try {
            boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
        } catch (BoatNotFoundException e) {
            e.printStackTrace();
            return;
        }
        arrowToNextMark = new AnimationTimer() {
            @Override
            public void handle(long now) {
                CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
                if (target != null) {
                    Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());

                    nextMarkArrow.setX(view3D.getPivot().getX());
                    nextMarkArrow.setY(view3D.getPivot().getY());
                    nextMarkArrow.setZ(view3D.getPivot().getZ() + 15);
                    nextMarkArrow.setHeading(headingToMark.degrees());
                }
            }
        };
        arrowToNextMark.start();
    }

    private void removeThirdPersonAnnotations() {
        viewSubjects.remove(nextMarkArrow);
        if (arrowToNextMark != null) {
            arrowToNextMark.stop();
        }

    }

    /**
     * Swap the colour of the next mark to pass with the last mark passed
     * @param leg boat has started on
     */
    private void swapColours(Leg leg) {
        CompoundMark start = leg.getStartCompoundMark();
        CompoundMark end = leg.getEndCompoundMark();

        //The last leg "finish" doesn't have compound marks.
        if (start == null || end == null ) {
            return;
        }

        Shape3D start1 = view3D.getShape(start.getMark1().getSourceID()).getMesh();
        Shape3D end1 = view3D.getShape(end.getMark1().getSourceID()).getMesh();

        Material nextMark = start1.getMaterial();
        Material lastMark = end1.getMaterial();

        start1.setMaterial(lastMark);
        if(start.getMark2() != null) {
            Shape3D start2 = view3D.getShape(start.getMark2().getSourceID()).getMesh();
            start2.setMaterial(lastMark);
        }

        end1.setMaterial(nextMark);
        if(end.getMark2() != null) {
            Shape3D end2 = view3D.getShape(end.getMark2().getSourceID()).getMesh();
            end2.setMaterial(nextMark);
        }
    }

    /**
     * Initialises the frame rate functionality. This allows for toggling the
     * frame rate, and connect the fps label to the race's fps property.
     */
    private void initialiseFps() {
        // fps toggle listener
        showFPS.selectedProperty().addListener((ov, old_val, new_val) -> {
            if (showFPS.isSelected()) {
                FPS.setVisible(true);
            } else {
                FPS.setVisible(false);
            }
        });

        // fps label display
        this.visualiserRace.getFrameRateProperty().addListener((observable,
                oldValue, newValue) -> {
            Platform.runLater(() ->
                    this.FPS.setText("FPS: " + newValue.toString()));
        });
    }

    /**
     * Initialises the information table view to listen to a given race.
     */
    private void initialiseInfoTable() {
        // list of boats to display data for
        ObservableList<VisualiserBoat> boats = FXCollections
                .observableArrayList(this.visualiserRace.getVisualiserRaceState().getBoats());
        SortedList<VisualiserBoat> sortedBoats = new SortedList<>(boats);
        sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty());

        // update list when boat information changes
        this.visualiserRace.getVisualiserRaceState().getBoats().addListener(
                (ListChangeListener.Change<? extends VisualiserBoat> c) -> Platform.runLater(() -> {
            boats.setAll(this.visualiserRace.getVisualiserRaceState().getBoats());
        }));

        // set table data
        boatInfoTable.setItems(sortedBoats);
        boatTeamColumn.setCellValueFactory(
                cellData -> cellData.getValue().nameProperty());
        boatSpeedColumn.setCellValueFactory(
                cellData -> cellData.getValue().currentSpeedProperty());
        boatMarkColumn.setCellValueFactory(
                cellData -> cellData.getValue().legProperty());
        boatPlacingColumn.setCellValueFactory(
                cellData -> cellData.getValue().placingProperty());

        //Kind of ugly, but allows for formatting an observed speed.
        boatSpeedColumn.setCellFactory(
                new Callback<TableColumn<VisualiserBoat, Number>, TableCell<VisualiserBoat, Number>>() {
                    @Override
                    public TableCell<VisualiserBoat, Number> call(TableColumn<VisualiserBoat, Number> param) {
                        //We return a table cell that populates itself with a Number, and formats it.
                        return new TableCell<VisualiserBoat, Number>(){

                            //Function to update the cell text.
                            @Override
                            protected void updateItem(Number item, boolean empty) {
                                if (item != null) {
                                    super.updateItem(item, empty);
                                    setText(String.format("%.2fkn", item.doubleValue()));
                                }
                            }
                        };
                    }
                });

        //Kind of ugly, but allows for turning an observed Leg into a string.
        boatMarkColumn.setCellFactory(
                new Callback<TableColumn<VisualiserBoat, Leg>, TableCell<VisualiserBoat, Leg>>() {
                    @Override
                    public TableCell<VisualiserBoat, Leg> call(TableColumn<VisualiserBoat, Leg> param) {
                        //We return a table cell that populates itself with a Leg's name.
                        return new TableCell<VisualiserBoat, Leg>(){

                            //Function to update the cell text.
                            @Override
                            protected void updateItem(Leg item, boolean empty) {
                                if (item != null) {
                                    super.updateItem(item, empty);
                                    setText(item.getName());
                                }
                            }
                        };
                    }
                });
    }

    /**
     * Initialises the race clock to listen to the specified race.
     */
    private void initialiseRaceClock() {
        raceState.getRaceClock().durationProperty().addListener((observable,
                oldValue, newValue) -> {
            Platform.runLater(() -> {
                timer.setText(newValue);
            });
        });
    }

    /**
     * Transition from the race view to the finish view.
     * @throws IOException Thrown if the finish scene cannot be loaded.
     */
    private void finishRace() throws IOException {
        RaceFinishController fc =
                (RaceFinishController)loadScene("raceFinish.fxml");
        fc.loadFinish(raceState.getBoats());
    }

    /**
     * Timer which monitors the race.
     */
    private void raceTimer() {
        new AnimationTimer() {
            @Override
            public void handle(long arg0) {
                //If the race has finished, go to finish view.
                if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) {
                    stop(); // stop the timer
                    try {
                        finishRace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    boatInfoTable.sort();
                }

                //Return to main screen if we lose connection.
                if (!visualiserRace.getServerConnection().isAlive()) {
                    try {
                        loadTitleScreen();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //TODO we should display an error to the user
                    //TODO also need to "reset" any state (race, connections, etc...).
                }
            }
        }.start();
    }

    /**
     * toggles if the info table is shown
     */
    private void toggleTable() {
        infoWrapper.setVisible(infoTableShow);
        boatInfoTable.refresh();
        infoTableShow = !infoTableShow;
    }

    /**
     * Get the next tutorial state
     */
    private void updateTutorialState(){
        //Next tutorial state is popped from list
        currentState = tutorialStates.get(0);
        tutorialStates.remove(0);
    }

    /**
     * Search key map for key given string of command
     * @param command the command of the key
     */
    private void searchMapForKey(String command){
        //For loop through keyFactory
        for (Map.Entry<String, ControlKey> entry: keyFactory.getKeyState().entrySet()){
            if(entry.getValue().toString().equals(command)){

                //Found next key required to press
                keyToPress = entry.getKey();
            }
        }
    }

    /**
     * Updates tutorial state and gui display for tutorial text
     * @throws Exception Exception thrown
     */
    private void checkTutorialState() throws Exception {
        //Switch statement to check what the current tutorial state is
        switch (currentState){
            case UPWIND:
                //Set next key to press as the downwind key
                searchMapForKey("Downwind");
                //Update tutorial text
                tutorialText.setText("Nice! To turn downwind press " + keyToPress + ".");
                updateTutorialState();
                break;
            case DOWNWIND:
                //Set next key to press as the tack/gybe key
                searchMapForKey("Tack/Gybe");
                //Update tutorial text
                tutorialText.setText("Nice! To tack or gybe press " + keyToPress + ".");
                updateTutorialState();
                break;
            case TACKGYBE:
                //Set next key to press as the VMG key
                searchMapForKey("VMG");
                //Update tutorial text
                tutorialText.setText("Nice! To use VMG press " + keyToPress + ". This will turn the boat.");
                updateTutorialState();
                break;
            case VMG:
                //Set next key to press as the sails-in key
                searchMapForKey("Toggle Sails");
                //Update tutorial text
                tutorialText.setText("Nice! To sails in press " + keyToPress + ". This will stop the boat.");
                updateTutorialState();
                break;
            case SAILSIN:
                //Set next key to press as the sails-out key
                searchMapForKey("Toggle Sails");
                //Update tutorial text
                tutorialText.setText("Nice! To sails out press " + keyToPress + " again. The will start moving again.");
                updateTutorialState();
                break;
            case SAILSOUT:
                //Set next key to press as the zoom-in key
                searchMapForKey("Zoom In");
                //Update tutorial text
                tutorialText.setText("Nice! To zoom in press " + keyToPress + ".");
                updateTutorialState();
                break;
            case ZOOMIN:
                //Set next key to press as the zoom-out key
                searchMapForKey("Zoom Out");
                //Update tutorial text
                tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. \nTo zoom out press " + keyToPress + ".");
                updateTutorialState();
                break;
            case ZOOMOUT:
                //Finished tutorial. Display pop-up for exiting the tutorial
                tutorialText.setText("Congratulations! You're done!");
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("Finished Tutorial");
                alert.setHeaderText("You have finished the tutorial.");
                alert.setContentText("Now you know the controls you are ready to race!");
                Optional<ButtonType> result = alert.showAndWait();
                if (result.get() == ButtonType.OK) {
                    App.game.endEvent();
                    loadTitleScreen();
                }
                break;
            default:
                //State not found. Exit tutorial to title menu
                App.game.endEvent();
                loadTitleScreen();
                break;
        }
    }

}
