package visualiser.Controllers;

import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import mock.model.commandFactory.CompositeCommand;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.LatestMessages;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import visualiser.gameController.ControllerClient;
import visualiser.model.VisualiserRaceState;
import visualiser.network.ServerConnection;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;

import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Controller to for waiting for the race to start.
 */
public class StartController extends Controller {

    @FXML private GridPane start;
    @FXML private AnchorPane startWrapper;

    /**
     * The name of the race/regatta.
     */
    @FXML private Label raceTitleLabel;

    /**
     * The time the race starts at.
     */
    @FXML private Label raceStartLabel;

    /**
     * The current time at the race location.
     */
    @FXML private Label timeZoneTime;

    /**
     * Time until the race starts.
     */
    @FXML private Label timer;

    @FXML private TableView<VisualiserBoat> boatNameTable;
    @FXML private TableColumn<VisualiserBoat, String> boatNameColumn;
    @FXML private TableColumn<VisualiserBoat, String> boatCodeColumn;

    /**
     * The status of the race.
     */
    @FXML private Label raceStatusLabel;


    /**
     * The race + connection to server.
     */
    private VisualiserRaceEvent visualiserRaceEvent;


    /**
     * Writes BoatActions to outgoing message queue.
     */
    private ControllerClient controllerClient;








    /**
     * Ctor.
     */
    public StartController() {
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }


    /**
     * Starts the race.
     */
    private void startRace() {

        //Initialise the boat table.
        initialiseBoatTable(this.visualiserRaceEvent.getVisualiserRaceState());

        //Initialise the race name.
        initialiseRaceName(this.visualiserRaceEvent.getVisualiserRaceState());

        //Initialises the race clock.
        initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());

        //Starts the race countdown timer.
        countdownTimer();
    }




    public AnchorPane startWrapper(){
        return startWrapper;
    }


    /**
     * Initialises the boat table that is to be shown on the pane.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseBoatTable(VisualiserRaceState visualiserRace) {

        //Get the boats.
        ObservableList<VisualiserBoat> boats = visualiserRace.getBoats();

        //Populate table.
        boatNameTable.setItems(boats);
        boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
        boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty());
    }

    /**
     * Initialises the race name which is shown on the pane.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseRaceName(VisualiserRaceState visualiserRace) {

        raceTitleLabel.setText(visualiserRace.getRegattaName());

    }

    /**
     * Initialises the race clock/timer labels for the start time, current time, and remaining time.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseRaceClock(VisualiserRaceState visualiserRace) {

        //Start time.
        initialiseRaceClockStartTime(visualiserRace);

        //Current time.
        initialiseRaceClockCurrentTime(visualiserRace);

        //Remaining time.
        initialiseRaceClockDuration(visualiserRace);

    }


    /**
     * Initialises the race current time label.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseRaceClockStartTime(VisualiserRaceState visualiserRace) {

        raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString());

        visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> {
            Platform.runLater(() -> {
                raceStartLabel.setText(newValue);
            });
        });

    }


    /**
     * Initialises the race current time label.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseRaceClockCurrentTime(VisualiserRaceState visualiserRace) {

        visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> {
            Platform.runLater(() -> {
                timeZoneTime.setText(newValue);
            });
        });

    }

    /**
     * Initialises the race duration label.
     * @param visualiserRace The race to get data from.
     */
    private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) {

        visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> {
            Platform.runLater(() -> {
                timer.setText(newValue);
            });
        });

    }

    /**
     * Countdown timer until race starts.
     */
    private void countdownTimer() {
        new AnimationTimer() {
            @Override
            public void handle(long arg0) {

                //Get the current race status.
                RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();

                //Display it.
                raceStatusLabel.setText("Race Status: " + raceStatus.name());


                //If the race has reached the preparatory phase, or has started...
                if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
                    //Stop this timer.
                    stop();

                    //Hide this, and display the race controller.
                    startWrapper.setVisible(false);
                    //start.setVisible(false);//TODO is this needed?

                    parent.beginRace(visualiserRaceEvent, controllerClient);

                }
            }
        }.start();
    }



    /**
     * Show starting information for a race given a socket.
     * @param socket network source of information
     */
    public void enterLobby(Socket socket) {
        try {

            this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT);

            this.controllerClient = visualiserRaceEvent.getControllerClient();


            startWrapper.setVisible(true);

            startRace();

        } catch (IOException e) {
            //TODO should probably let this propagate, so that we only enter this scene if everything works
            Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e);
        }
    }

}
