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.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.MeshView;
import mock.app.Event;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RequestToJoinEnum;
import visualiser.app.App;
import visualiser.gameController.ControllerClient;
import visualiser.layout.SeaSurface;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceEvent;
import visualiser.model.VisualiserRaceState;

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

/**
 * Controller for Hosting a game.
 */
public class InGameLobbyController extends Controller {
    @FXML
    private ImageView imageView;

    @FXML
    GridPane playerContainer;


    @FXML
    private Label playerLabel;

    @FXML
    private Label playerLabel2;

    @FXML
    private Label playerLabel3;

    @FXML
    private Label playerLabel4;

    @FXML
    private Label playerLabel5;

    @FXML
    private Label playerLabel6;

    @FXML
    private Label countdownLabel;

    @FXML
    private AnchorPane countdownTenPane;

    @FXML
    private Label countdownTenText;

    private Event game;

    private View3D playerBoat;

    private VisualiserRaceEvent visualiserRaceEvent;

    private boolean isHost;

    private ControllerClient controllerClient;

    private ArrayList<Label> allPlayerLabels;

    private ObservableList<Subject3D> subjects = FXCollections.observableArrayList();

    private StlMeshImporter importer;

    private PopulatePlayers lobbyUpdateListener;


    public void initialize() {
        allPlayerLabels = new ArrayList(Arrays.asList(playerLabel, playerLabel2,
                playerLabel3,
                playerLabel4,
                playerLabel5,
                playerLabel6));

        URL asset = HostGameController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
        importer = new StlMeshImporter();
        importer.read(asset);
        lobbyUpdateListener = new PopulatePlayers();

    }

    private void resetLobby(){
        for (Label label: allPlayerLabels){
            label.setText("No Player");
        }
        List<Node> nodeCopy = new ArrayList(playerContainer.getChildren());
        for (Node node: nodeCopy){
            if (node instanceof View3D){
                playerContainer.getChildren().remove(node);
            }
        }
    }

    public class PopulatePlayers implements ListChangeListener {

        @Override
        public void onChanged(Change change) {
            Platform.runLater(
                    () -> {
                        while (change.next()){
                            if (change.wasAdded() || change.wasRemoved() || change.wasUpdated()){
                                try{
                                    resetLobby();
                                    int count = 0;
                                    int row = 0;
                                    for (VisualiserBoat boat :visualiserRaceEvent.getVisualiserRaceState().getBoats()) {
                                        View3D playerBoatToSet = new View3D();
                                        playerBoatToSet.setItems(subjects);

                                        playerContainer.add(playerBoatToSet, (count % 3) , row);
                                        playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));

                                        SeaSurface sea = new SeaSurface(750, 200, 250, 0, 210);
                                        subjects.add(sea.getSurface());

                                        MeshView mesh = new MeshView(importer.getImport());
                                        Subject3D subject = new Subject3D(mesh);
                                        subjects.add(subject);

                                        playerBoatToSet.setDistance(50);
                                        playerBoatToSet.setYaw(45);
                                        playerBoatToSet.setPitch(20);



                                        AnimationTimer rotate = new AnimationTimer() {
                                            @Override
                                            public void handle(long now) {
                                                subject.setHeading(subject.getHeading().getAngle() + 0.1);
                                            }
                                        };
                                        rotate.start();

                                        allPlayerLabels.get(count).setText("Player: " + boat.getSourceID());
                                        allPlayerLabels.get(count).toFront();
                                        count += 1;
                                        if (count > 2){
                                            row = 1;
                                        }
                                    }
                                }
                                catch(ConcurrentModificationException e){
                                }
                            }
                        }
                    }
            );
        }
    }
    /*
    private void populatePlayers(ListChangeListener.Change change){
    }*/

    /**
     * Starts the race.
     */
    private void startRace() {
         //Initialises the race clock.
        initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState());


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

    /**
     * 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) {
        //Remaining time.
        initialiseRaceClockDuration(visualiserRace);

    }


    /**
     * 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(() -> {
                countdownLabel.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();

                //Try catch for getting interval times
                try {
                    long interval = ChronoUnit.MILLIS.between(visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getCurrentTime(), visualiserRaceEvent.getVisualiserRaceState().getRaceClock().getStartingTime());
                    if(interval<=10000){
                        countdownLabel.setVisible(false);
                        countdownTenPane.setVisible(true);
                        countdownTenText.setVisible(true);
                    }
                    countdownText(interval);
                } catch (Exception e){

                }


                //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.
                    try {
                        visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener);

                        RaceViewController rvc = (RaceViewController)
                                loadScene("raceView.fxml");
                        rvc.startRace(visualiserRaceEvent, controllerClient,
                                isHost);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }
        }.start();
    }

    public void enterGameLobby(Socket socket, boolean isHost){
        try {

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

            this.visualiserRaceEvent.getVisualiserRaceState().getBoats().addListener(this.lobbyUpdateListener);

            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);
        }
    }

    /**
     * Menu button pressed. Prompt alert then return to menu
     * @throws IOException socket erro
     */
    public void menuBtnPressed() throws IOException {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("Quitting race");
        alert.setContentText("Do you wish to quit the race?");
        alert.setHeaderText("You are about to quit the race");
        Optional<ButtonType> result = alert.showAndWait();
        if(result.get() == ButtonType.OK){
            visualiserRaceEvent.terminate();

            try{
                if(isHost) {
                    App.game.endEvent();
                }
                loadScene("title.fxml");

            }catch (IOException ignore){};
        }
    }

    /**
     * Start button pressed. Currently only prints out start
     */
    public void startBtnPressed(){
        //System.out.println("Should start the race. This button is only visible for the host");
    }

    public void joinSpecPressed(){
        //System.out.println("Spectator list pressed. Joining spectators");
    }

    public void joinRacePressed(){
        //System.out.println("Empty race user pane pressed. Joining racers");
    }

    /**
     * Countdown interval checker that updates the countdown text
     * @param interval Countdown interval to check
     */
    private void countdownText(long interval){

        //Do nothing if 5 seconds or less to go
        if (interval <=5000){
            countdownTenPane.setVisible(false);
            //countdownTenText.setText("5");
            return;
        }

        //6 seconds left. Display 1 second for countdown
        if (interval <=6000){
            countdownTenText.setText("1");
            return;
        }

        //7 seconds left. Display 2 seconds for countdown
        if (interval <=7000){
            countdownTenText.setText("2");
            return;
        }

        //8 seconds left. Display 3 seconds for countdown
        if (interval <=8000){
            countdownTenText.setText("3");
            return;
        }

        //9 seconds left. Display 4 seconds for countdown
        if (interval <=9000){
            countdownTenText.setText("4");
            return;
        }

        //10 seconds left. Display 5 seconds for countdown
        if (interval <=10000){
            countdownTenText.setText("5");
            return;
        }
    }

}
