package de.tud.kom.p2psim.impl.topology.movement.vehicular.sumo.simulation.controller.xml;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import de.tud.kom.p2psim.impl.topology.movement.vehicular.sumo.simulation.controller.VehicleInformationContainer;
import de.tud.kom.p2psim.impl.topology.movement.vehicular.sumo.simulation.controller.csv.RoadSideUnitInformationHandler;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.api.SimulationSetupExtractor;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.api.VehicleController;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetwork;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkEdge;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkRoute;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.exception.NoAdditionalRouteAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.exception.NoExitAvailableException;

public class XMLSimulationController implements VehicleController, SimulationSetupExtractor, Runnable {
	private String _vehicleDataPath;
	private String _roadSideUnitDataPath;

	private List<String> _vehicles;

	private Map<Double, Map<String, VehicleInformationContainer>> _positonsByTimestamp = new HashMap<>();
	private int _futureInformation = 0;

	private boolean _initalized = false;

	private VehicleDataInformationHandler _vehicleDataInformationHandler = new VehicleDataInformationHandler();
	private RoadSideUnitInformationHandler _roadSideUnitInformationHandler = new RoadSideUnitInformationHandler();

	private double _start = -1;

	private double _step;

	public XMLSimulationController(String pVehicleDataPath, String pRoadSideUnitDataPath) {
		this(pVehicleDataPath);
		_roadSideUnitDataPath = pRoadSideUnitDataPath;
	}

	public XMLSimulationController(String pVehicleDataPath) {
		_vehicleDataPath = pVehicleDataPath;
	}

	@Override
	public void init(long timeBetweenMoveOperations) {

		if (!_initalized) {
			if (_vehicleDataPath != null) {
				Thread thread = new Thread(this);
				thread.start();

				for (int i = 0; i < _futureInformation; i++) {
					nextStep(Time.SECOND);
				}
			}
			if (_roadSideUnitDataPath != null) {
				_roadSideUnitInformationHandler.parseIntersectionsFile(_roadSideUnitDataPath);
			}
			_initalized = true;
		}
	}

	@Override
	public Location getVehiclePosition(String pVehicleID) {
		return getVehiclePosition(_step, pVehicleID);
	}

	public VehicleInformationContainer requestVehicleInformation(String pVehicleID) {
		return _vehicleDataInformationHandler.getVehiclePositions().get(pVehicleID);
	}

	@Override
	public boolean nextStep(long pTimeScale) {
		_vehicleDataInformationHandler.readNext();

		_step = _vehicleDataInformationHandler.getStep() - _futureInformation;
		if (_start == -1) {
			_start = _vehicleDataInformationHandler.getStep();
		}

		double newStep = _step + _futureInformation;
		if (!_positonsByTimestamp.containsKey(newStep)) {
			Map<String, VehicleInformationContainer> vehiclePositions = new HashMap<>();
			_positonsByTimestamp.put(newStep, vehiclePositions);

			List<String> allVehicles = new ArrayList<>(_vehicleDataInformationHandler.getVehiclePositions().keySet());
			for (String vehicle : allVehicles) {
				VehicleInformationContainer vehiclePosition = requestVehicleInformation(vehicle);
				if (vehiclePosition != null) {
					vehiclePositions.put(vehicle, vehiclePosition);
				}
			}
		}

		return !_vehicleDataInformationHandler.isTerminated();
	}

	@Override
	public List<String> getAllVehicles() {
		return getAllVehicles(_step);
	}

	@Override
	public double getStep() {
		return _step;
	}

	@Override
	public double getStart() {
		return _start;
	}

	@Override
	public void run() {
		try {
			SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
			parser.parse(new FileInputStream(_vehicleDataPath), _vehicleDataInformationHandler);
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void setObservedArea(double pStartX, double pStartY, double pEndX, double pEndY) {
		_vehicleDataInformationHandler.setObservedArea(pStartX, pStartY, pEndX, pEndY);
		_roadSideUnitInformationHandler.setObservedArea(pStartX, pStartY, pEndX, pEndY);
	}

	@Override
	public Location getVehiclePosition(double pStep, String pVehicleID) {
		Map<String, VehicleInformationContainer> map = _positonsByTimestamp.get(pStep);

		return map.get(pVehicleID).getPosition();
	}

	@Override
	public List<String> getAllVehicles(double pStep) {
		Map<String, VehicleInformationContainer> map = _positonsByTimestamp.get(pStep);

		return new ArrayList<>(map.keySet());
	}

	@Override
	public double getMaximumAvailablePrediction() {
		double max = Collections.max(_positonsByTimestamp.keySet());

		return max;
	}

	@Override
	public List<Location> getAllIntersections(boolean pCluster) {
		return _roadSideUnitInformationHandler.getIntersections();
	}

	@Override
	public double getScenarioWidth() {
		return -1;
	}

	@Override
	public double getScenarioHeight() {
		return -1;
	}

	@Override
	public double getVehicleLength(String pVehicleID) {
	    throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public RoadNetworkRoute getCurrentRoute(String pVehicleID) {
		throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public RoadNetwork getRoadNetwork() {
		return null;
	}

	@Override
	public void rerouteVehicle(String pVehicle, RoadNetworkRoute pRoute) {
		throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public RoadNetworkRoute findNewRoute(String pVehicle)
			throws NoAdditionalRouteAvailableException {
		throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public RoadNetworkRoute findNewRoute(String pVehicle,
			List<RoadNetworkEdge> pEdgesToAvoid, boolean pKeepDestination)
					throws NoAdditionalRouteAvailableException,
					NoExitAvailableException {
		throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public void stopVehicle(String pVehicle) {
		throw new UnsupportedOperationException("This method is not supported for " + getClass().getSimpleName());
	}

	@Override
	public double getVehicleSpeed(String pVehicleID) {
		return _vehicleDataInformationHandler.getVehiclePositions().get(pVehicleID).getSpeed();
	}

	@Override
	public double getVehicleHeading(String pVehicleID) {
		return _vehicleDataInformationHandler.getVehiclePositions().get(pVehicleID).getHeading();
	}

}
