package mock.model;

import javafx.animation.AnimationTimer;
import mock.model.commandFactory.CompositeCommand;
import mock.model.commandFactory.CommandFactory;
import network.Messages.Enums.BoatActionEnum;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.LatestMessages;
import shared.model.RunnableWithFramePeriod;


public class RaceLogic implements RunnableWithFramePeriod {
    /**
     * State of current race modified by this object
     */
    private MockRace race;
    /**
     * High-level interface to AC35 protocol server
     */
    private RaceServer server;

    private CompositeCommand commands;

    /**
     * Initialises race loop with state and server message queue
     * @param race state of race to modify
     * @param messages to send to server
     * @param compositeCommand Commands from clients to execute.
     */
    public RaceLogic(MockRace race, LatestMessages messages, CompositeCommand compositeCommand) {
        this.race = race;
        this.server = new RaceServer(race, messages);
        this.commands = compositeCommand;
    }

    /**
     * Initialise boats and start countdown timer
     */
    @Override
    public void run() {
        race.initialiseBoats();

        countdown();

        raceLoop();
    }


    /**
     * Countdown timer until race starts.
     */
    private void countdown() {

        long previousFrameTime = System.currentTimeMillis();

        while (race.getRaceStatusEnum() != RaceStatusEnum.STARTED) {

            long currentTime = System.currentTimeMillis();

            //Update race time.
            race.updateRaceTime(currentTime);

            //Update the race status based on the current time.
            race.updateRaceStatusEnum();

            //Provide boat's with an estimated time at next mark until the race starts.
            race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime());

            //Parse the race snapshot.
            server.parseSnapshot();


            // Change wind direction
            race.changeWindDirection();


            if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) {
                race.setBoatsStatusToRacing();
            }

            waitForFramePeriod(previousFrameTime, currentTime, 50);
            previousFrameTime = currentTime;

        }
    }


    /**
     * Timer that runs for the duration of the race, until all boats finish.
     */
    private void raceLoop() {

        long previousFrameTime = System.currentTimeMillis();

        while (race.getRaceStatusEnum() != RaceStatusEnum.FINISHED) {

            //Get the current time.
            long currentTime = System.currentTimeMillis();

            //Execute commands from clients.
            commands.execute();

            //Update race time.
            race.updateRaceTime(currentTime);

            //As long as there is at least one boat racing, we still simulate the race.
            if (race.getNumberOfActiveBoats() != 0) {

                //Get the time period of this frame.
                long framePeriod = currentTime - previousFrameTime;

                //For each boat, we update its position, and generate a BoatLocationMessage.
                for (MockBoat boat : race.getBoats()) {

                    //If it is still racing, update its position.
                    if (boat.getStatus() == BoatStatusEnum.RACING) {
                        race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());

                    }

                }

            } else {
                //Otherwise, the race is over!
                raceFinished.start();
                race.setRaceStatusEnum(RaceStatusEnum.FINISHED);
            }

            if (race.getNumberOfActiveBoats() != 0) {
                // Change wind direction
                race.changeWindDirection();

                //Parse the race snapshot.
                server.parseSnapshot();

                //Update the last frame time.
                previousFrameTime = currentTime;
            }

            waitForFramePeriod(previousFrameTime, currentTime, 50);
            previousFrameTime = currentTime;
        }
    }

    /**
     * Broadcast that the race has finished.
     */
    protected AnimationTimer raceFinished = new AnimationTimer(){
        int iters = 0;
        @Override
        public void handle(long now) {

            server.parseSnapshot();

            if (iters > 500) {
                stop();
            }
            iters++;
        }
    };


    /**
     * Returns the race state that this RaceLogic is simulating.
     * @return Race state this RaceLogic is simulating.
     */
    public MockRace getRace() {
        return race;
    }
}
