package seng302.DataInput;

import javafx.scene.paint.Color;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import seng302.Model.*;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by fwy13 on 26/03/2017.
 */
public class RaceXMLReader extends XMLReader implements RaceDataSource {
    private static double COORDINATEPADDING = 0.0005;
    private int raceID;
    private List<Boat> boats = new ArrayList<>();
    private Color[] colors = {Color.BLUEVIOLET, Color.BLACK, Color.RED, Color.ORANGE, Color.DARKOLIVEGREEN, Color.LIMEGREEN};//TODO make this established in xml or come up with a better system.
    private List<Leg> legs = new ArrayList<>();
    private GPSCoordinate mark, startPt1, startPt2, finishPt1, finishPt2, leewardPt1, leewardPt2, windwardPt1, windwardPt2;
    private GPSCoordinate mapTopLeft, mapBottomRight;
    private List<GPSCoordinate> boundary = new ArrayList<>();
    private List<Marker> markers = new ArrayList<>();

    ///Filename to read data from.
    private String filePath;
    ///The polar table object to assign to each boat.
    private Polars boatPolars;



    /**
     * Constractor for Race XML
     *
     * @param filePath The path to the file to read data from.
     * @param boatPolars The polar table to assign to each boat.
     * @throws IOException                  error
     * @throws SAXException                 error
     * @throws ParserConfigurationException error
     */
    public RaceXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException {

        super(filePath);

        this.filePath = filePath;
        this.boatPolars = boatPolars;


        read();
    }



    /**
     * Read the files
     */
    private void read() {

        readCourse();
        readID();
        readLegs();
        readMarkers();
        readBoats();
    }

    /**
     * Read in race ID from XML object.
     */
    public void readID() {
        NodeList race = doc.getElementsByTagName("race");
        raceID = Integer.parseInt(getTextValueOfNode((Element) race.item(0), "raceId"));
    }

    /**
     * Read all the boats in the XML file
     */
    public void readBoats() {
        //get all boats
        NodeList nBoats = doc.getElementsByTagName("boat");

        for (int i = 0; i < nBoats.getLength(); i++) {
            String name = getTextValueOfNode((Element) nBoats.item(i), "name");
            String abbrev = getTextValueOfNode((Element) nBoats.item(i), "abbr");
            double velo = Double.parseDouble(getTextValueOfNode((Element) nBoats.item(i), "speed"));
            int sourceID = Integer.parseInt(getTextValueOfNode((Element) nBoats.item(i), "sourceID"));

            Boat boat = new Boat(name, velo, abbrev, sourceID, this.boatPolars);

            boat.setCurrentPosition(startPt1);
            if (legs.size() > 0) {
                boat.setCurrentLeg(legs.get(0));
            }
            boats.add(boat);
        }
    }


    /**
     * Read all the boats in the XML file
     */
    public void readMarkers() {
        //get all boats
        NodeList nMarkers = doc.getElementsByTagName("marker");

        for (int i = 0; i < nMarkers.getLength(); i++) {
            Marker marker = getMarker((Element) nMarkers.item(i));
            if (marker.getName() != null) markers.add(marker);
        }
    }

    /**
     * Read all the legs in the XML file
     */
    public void readLegs() {
        //get all legs
        NodeList nLegs = doc.getElementsByTagName("leg");

        for (int i = 0; i < nLegs.getLength(); i++) {
            String label = getTextValueOfNode((Element) nLegs.item(i), "name");
            NodeList start = ((Element) nLegs.item(i)).getElementsByTagName("start");
            Marker startMarker = getMarker(start);
            NodeList finish = ((Element) nLegs.item(i)).getElementsByTagName("finish");
            Marker finishMarker = getMarker(finish);
            legs.add(new Leg(label, startMarker, finishMarker, i));
        }
    }

    /**
     * Read courses in XML file
     */
    public void readCourse() {
        NodeList nCourse = doc.getElementsByTagName("course");

        NodeList nBounds = ((Element) nCourse.item(0)).getElementsByTagName("boundaries");
        nBounds = ((Element) nBounds.item(0)).getElementsByTagName("coordinate");
        int maxLatitudeIndex = 0;
        double maxLatitude = -Double.MIN_VALUE;
        int maxLongitudeIndex = 0;
        double maxLongitude = -180;
        int minLatitudeIndex = 0;
        double minLatitude = Double.MAX_VALUE;
        int minLongitudeIndex = 0;
        double minLongitude = Double.MAX_VALUE;
        for (int i = 0; i < nBounds.getLength(); i++) {
            boundary.add(getCoordinates((Element) nBounds.item(i)));
            if (boundary.get(i).getLatitude() > maxLatitude) {
                maxLatitudeIndex = i;
                maxLatitude = boundary.get(i).getLatitude();
            }
            if (boundary.get(i).getLatitude() < minLatitude) {
                minLatitudeIndex = i;
                minLatitude = boundary.get(i).getLatitude();
            }
            if (boundary.get(i).getLongitude() > maxLongitude) {
                maxLongitudeIndex = i;
                maxLongitude = boundary.get(i).getLongitude();
            }
            if (boundary.get(i).getLongitude() < minLongitude) {
                minLongitudeIndex = i;
                minLongitude = boundary.get(i).getLongitude();
            }
        }

        double difference = 0;//this will hold the largest difference so we can make the map square.
        double latitudeDiff = Math.abs(Math.abs(boundary.get(maxLatitudeIndex).getLatitude()) - Math.abs(boundary.get(minLatitudeIndex).getLatitude()));
        double longitudeDiff = Math.abs(Math.abs(boundary.get(maxLongitudeIndex).getLongitude()) - Math.abs(boundary.get(minLongitudeIndex).getLongitude()));
        if (latitudeDiff >= longitudeDiff) {
            difference = latitudeDiff - longitudeDiff;
            maxLongitude += difference / 2;
            minLongitude -= difference / 2;
        } else {
            difference = longitudeDiff - latitudeDiff;
            maxLatitude += difference / 2;
            minLatitude -= difference / 2;
        }
        maxLatitude += COORDINATEPADDING;
        minLatitude -= COORDINATEPADDING;
        maxLongitude += COORDINATEPADDING;
        minLongitude -= COORDINATEPADDING;
        //now create map boundaries
        //top left canvas point is min logitude, max latitude
        //bottom right of canvas point is min longitude, max latitude.
        mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
        mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);

        NodeList nMarks = ((Element) nCourse.item(0)).getElementsByTagName("marker");
        startPt1 = getCoordinates(nMarks, 0);
        startPt2 = getCoordinates(nMarks, 0, 1);
        mark = getCoordinates(nMarks, 1);
        windwardPt1 = getCoordinates(nMarks, 2);
        windwardPt2 = getCoordinates(nMarks, 2, 1);
        leewardPt1 = getCoordinates(nMarks, 3);
        leewardPt2 = getCoordinates(nMarks, 3, 1);
        finishPt1 = getCoordinates(nMarks, 4);
        finishPt2 = getCoordinates(nMarks, 4, 1);
    }

    /**
     * gets a marker from the XML file
     *
     * @param start base nodelist this should be the tag that contains <coordinate></coordinate>
     * @return
     */
    private Marker getMarker(NodeList start) {
        return getMarker(start, 0);
    }

    /**
     * gets a marker from the XML file
     *
     * @param start      base nodelist this should be the tag that contains <coordinate></coordinate>
     * @param startIndex index in the node that has the coordinate tag
     * @return
     */
    private Marker getMarker(NodeList start, int startIndex) {
        return getMarker(start, startIndex, 0);
    }

    /**
     * gets a marker from the XML file
     *
     * @param start      base nodelist this should be the tag that contains <coordinate></coordinate>
     * @param startIndex index in the node that has the coordinate tag
     * @param nodeIndex  coordinate index
     * @return
     */
    private Marker getMarker(NodeList start, int startIndex, int nodeIndex) {
        NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("marker");
        Element marker = (Element) nodeList.item(nodeIndex);
        return getMarker(marker);
    }

    /**
     * gets a changes a marker to GPS coordinates into a marker
     *
     * @param markerNode marker to turn into coordinates
     * @return
     */
    private Marker getMarker(Element markerNode) {

        NodeList nCoordinates = markerNode.getElementsByTagName("coordinate");

        GPSCoordinate side1 = getCoordinates((Element) nCoordinates.item(0));
        GPSCoordinate side2;
        if (nCoordinates.getLength() > 1) {
            side2 = getCoordinates((Element) nCoordinates.item(1));
        } else {
            side2 = side1;
        }
        NodeList name = markerNode.getElementsByTagName("name");
        return name.getLength() == 1 ? new Marker(getTextValueOfNode((Element) markerNode, "name"), side1, side2) : new Marker(side1, side2);
    }

    /**
     * gets a coordinates from the XML file
     *
     * @param start base nodelist this should be the tag that contains <coordinate></coordinate>
     * @return
     */
    private GPSCoordinate getCoordinates(NodeList start) {
        return getCoordinates(start, 0);
    }

    /**
     * gets a coordinates from the XML file
     *
     * @param start      base nodelist this should be the tag that contains <coordinate></coordinate>
     * @param startIndex the index the tag containing the coordinate should be in
     * @return
     */
    private GPSCoordinate getCoordinates(NodeList start, int startIndex) {
        return getCoordinates(start, startIndex, 0);
    }

    /**
     * gets a coordinates from the XML file
     *
     * @param start      base nodelist this should be the tag that contains <coordinate></coordinate>
     * @param startIndex the index the tag containing the coordinate should be in
     * @param nodeIndex  The coordinate index
     * @return
     */
    private GPSCoordinate getCoordinates(NodeList start, int startIndex, int nodeIndex) {
        NodeList nodeList = ((Element) start.item(startIndex)).getElementsByTagName("coordinate");
        Element coord = (Element) nodeList.item(nodeIndex);
        return getCoordinates(coord);
    }

    /**
     * Returns the coordinate TODO raise exception that runs when the XML is formatted wrongly.
     *
     * @param coordNode
     * @return
     */
    private GPSCoordinate getCoordinates(Element coordNode) {

        double startLat = Double.parseDouble(getTextValueOfNode(coordNode, "latitude"));
        double startLong = Double.parseDouble(getTextValueOfNode(coordNode, "longitude"));
        return new GPSCoordinate(startLat, startLong);
    }

    public List<Boat> getBoats() {
        return boats;
    }

    public List<Leg> getLegs() {
        return legs;
    }

    public GPSCoordinate getMark() {
        return mark;
    }

    public GPSCoordinate getStartPt1() {
        return startPt1;
    }

    public GPSCoordinate getStartPt2() {
        return startPt2;
    }

    public GPSCoordinate getFinishPt1() {
        return finishPt1;
    }

    public GPSCoordinate getFinishPt2() {
        return finishPt2;
    }

    public GPSCoordinate getLeewardPt1() {
        return leewardPt1;
    }

    public GPSCoordinate getLeewardPt2() {
        return leewardPt2;
    }

    public GPSCoordinate getWindwardPt1() {
        return windwardPt1;
    }

    public GPSCoordinate getWindwardPt2() {
        return windwardPt2;
    }

    public List<GPSCoordinate> getBoundary() {
        return boundary;
    }

    public GPSCoordinate getMapTopLeft() {
        return mapTopLeft;
    }

    public GPSCoordinate getMapBottomRight() {
        return mapBottomRight;
    }

    public int getRaceId() {
        return raceID;
    }

    public List<Marker> getMarkers() {
        return markers;
    }

    public String getRaceType() {
        return "FLEET";
    }
}
