package mock.app;

import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
import mock.model.*;
import mock.model.commandFactory.CompositeCommand;
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 shared.model.Bearing;
import shared.model.Constants;

import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * A Race Event, this holds all of the race's information as well as handling the connection to its clients.
 */
public class Event {

    /**
     * Contents of the various xml files.
     */
    private String raceXML;
    private String regattaXML;
    private String boatXML;

    private XMLFileType xmlFileType;

    private Polars boatPolars;

    /**
     * Data sources containing data from the xml files.
     */
    RaceDataSource raceDataSource;
    BoatDataSource boatDataSource;
    RegattaDataSource regattaDataSource;


    private ConnectionAcceptor connectionAcceptor;
    private LatestMessages latestMessages;

    private CompositeCommand compositeCommand;

    /**
     * This is used to allocate source IDs.
     */
    private SourceIdAllocator sourceIdAllocator;





    /**
     * Constructs an event, using various XML files.
     * @param singlePlayer Whether or not to create a single player event.
     * @throws EventConstructionException Thrown if we cannot create an Event for any reason.
     */
    public Event(boolean singlePlayer) throws EventConstructionException {

        String raceXMLFile = "mock/mockXML/raceTest.xml";
        String boatsXMLFile = "mock/mockXML/boatTest.xml";
        String regattaXMLFile = "mock/mockXML/regattaTest.xml";

        if (singlePlayer) {
            raceXMLFile = "mock/mockXML/raceSinglePlayer.xml";
            boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
        }

        //Read XML files.
        try {
            this.raceXML = getRaceXMLAtCurrentTime(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
            this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
            this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);

        } catch (XMLReaderException e) {
            throw new EventConstructionException("Could not read XML files.", e);
        }

        this.xmlFileType = XMLFileType.Contents;

        this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");


        //Parse the XML files into data sources.
        try {
            this.raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType);
            this.boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType);
            this.regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType);


        } catch (XMLReaderException | InvalidRaceDataException | InvalidRegattaDataException | InvalidBoatDataException e) {
            throw new EventConstructionException("Could not parse XML files.", e);

        }

        this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants());
        this.compositeCommand = new CompositeCommand();
        this.latestMessages = new LatestMessages();

        //Create and start race.
        WindGenerator windGenerator = new RandomWindGenerator(
                Bearing.fromDegrees(225),
                Bearing.fromDegrees(215),
                Bearing.fromDegrees(235),
                12d,
                8d,
                16d );
        RaceLogic newRace = new RaceLogic(
                new MockRace(
                        boatDataSource,
                        raceDataSource,
                        regattaDataSource,
                        this.boatPolars,
                        Constants.RaceTimeScale,
                        windGenerator   ),
                this.latestMessages,
                this.compositeCommand);

        new Thread(newRace, "Event.Start()->RaceLogic thread").start();


        //Create connection acceptor.

        try {
            this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace);

        } catch (IOException e) {
            throw new EventConstructionException("Could not create ConnectionAcceptor.", e);
        }


        new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start();

        sendXMLs();


    }



    /**
     * Sends the initial race data and then begins race simulation.
     */
    public void start() {


    }

    /**
     * Sends out each xml string, via the mock output
     */
    private void sendXMLs() {

        connectionAcceptor.setRegattaXml(regattaXML);

        connectionAcceptor.setRaceXml(raceXML);

        connectionAcceptor.setBoatsXml(boatXML);
    }

    /**
     * Sets the xml description of the race to show the race was created now, and starts in 4 minutes
     * @param raceXML The race.xml contents.
     * @return String containing edited xml
     */
    private String getRaceXMLAtCurrentTime(String raceXML) {

        //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
        long millisecondsToAdd = Constants.RacePreStartTime + Duration.ofMinutes(1).toMillis();
        long secondsToAdd = millisecondsToAdd / 1000;
        //Scale the time using our time scalar.
        secondsToAdd = secondsToAdd / Constants.RaceTimeScale;

        DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
        ZonedDateTime creationTime = ZonedDateTime.now();
        raceXML = raceXML.replace("CREATION_TIME", dateFormat.format(creationTime));

        raceXML = raceXML.replace("START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd)));

        return raceXML;

    }
}
