package controllers;

import java.net.URL;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.MapValueFactory;
import objects.HorseRaceData;
import objects.LaneWinObject;

@SuppressWarnings("rawtypes")
public class AtgController implements Initializable {

	@FXML TableColumn<Map, String> trackColumn = new TableColumn<>("Track");
	@FXML TableColumn<Map, Integer> raceColumn = new TableColumn<>("Race");
	@FXML TableColumn<Map, String> horseNameColumn = new TableColumn<>("Horse name");
	@FXML TableColumn<Map, Integer> laneColumn = new TableColumn<>("Lane");
	@FXML TableColumn<Map, Integer> distanceColumn = new TableColumn<>("Distance");
	@FXML TableColumn<Map, String> timeModifierColumn = new TableColumn<>("Time modifier");
	@FXML TableColumn<Map, String> driverNameColumn = new TableColumn<>("Driver name");
	@FXML TableColumn<Map, String> trainerNameColumn = new TableColumn<>("Trainer name");
	@FXML TableColumn<Map, Integer> resultColumn = new TableColumn<>("Result");
	@FXML TableColumn<Map, String> horseAvgTimeColumn = new TableColumn<>("Horse avg time");
	@FXML TableColumn<Map, String> horseAvgTimeDistanceColumn = new TableColumn<>("Horse avg time distance");
	@FXML TableColumn<Map, String> horseGallopRateColumn = new TableColumn<>("Horse gallop rate");
	@FXML TableColumn<Map, String> driverAvgTimeColumn = new TableColumn<>("Driver avg time");
	@FXML TableColumn<Map, String> driverAvgTimeDistanceColumn = new TableColumn<>("Driver Avg time distance");
	@FXML TableColumn<Map, String> driverGallopRateColumn = new TableColumn<>("Driver gallop rate");
	@FXML TableColumn<Map, Float> avgTimeColumn = new TableColumn<>("Avg time");
	@FXML TableColumn<Map, Float> calculationColumn = new TableColumn<>("Calculated Value");
	@FXML TableColumn<Map, String> laneWinColumn = new TableColumn<>("Lane win %");

	List<LaneWinObject> autostartPercentages;
	List<LaneWinObject> voltstartPercentages;

	HashMap<ArrayList<String>, List<LaneWinObject>> astartPercent = Maps.newHashMap();
	HashMap<ArrayList<String>, List<LaneWinObject>> vstartPercent = Maps.newHashMap();


	@FXML
	protected TableView<Map<String, Object>> racesTable;

	protected List<HorseRaceData> todaysRaces = new ArrayList<>();

	protected ObservableList<Map<String, Object>> tableRaces;
	private final float horseDistanceAndTypeMultiplyer = 3f; // orig 3f
	private final float horseDistanceMultiplier = 2f;// orig 2f
	private final float driverDistanceAndTypeMultiplyer = 3f;// orig 3f
	private final float driverDistanceMultiplier = 2f;// orig 2f

	@Override
	public void initialize(URL arg0, ResourceBundle arg1) {
		trackColumn.setCellValueFactory(new MapValueFactory<>("track"));
		raceColumn.setCellValueFactory(new MapValueFactory<>("race"));
		horseNameColumn.setCellValueFactory(new MapValueFactory<>("horseName"));
		laneColumn.setCellValueFactory(new MapValueFactory<>("lane"));
		distanceColumn.setCellValueFactory(new MapValueFactory<>("distance"));
		timeModifierColumn.setCellValueFactory(new MapValueFactory<>("timeModifier"));
		driverNameColumn.setCellValueFactory(new MapValueFactory<>("driverName"));
		trainerNameColumn.setCellValueFactory(new MapValueFactory<>("trainerName"));
		resultColumn.setCellValueFactory(new MapValueFactory<>("result"));
		horseAvgTimeColumn.setCellValueFactory(new MapValueFactory<>("horseAvgTime"));
		horseAvgTimeDistanceColumn.setCellValueFactory(new MapValueFactory<>("horseAvgTimeDistance"));
		driverAvgTimeColumn.setCellValueFactory(new MapValueFactory<>("driverAvgTime"));
		driverAvgTimeDistanceColumn.setCellValueFactory(new MapValueFactory<>("driverAvgTimeDistance"));
		avgTimeColumn.setCellValueFactory(new MapValueFactory<>("avgTime"));
		calculationColumn.setCellValueFactory(new MapValueFactory<>("calculatedValue"));
		laneWinColumn.setCellValueFactory(new MapValueFactory<>("laneWin"));
		horseGallopRateColumn.setCellValueFactory(new MapValueFactory<>("horseGallopRate"));
		driverGallopRateColumn.setCellValueFactory(new MapValueFactory<>("driverGallopRate"));

	}

	public void updateRacesTable() {
		racesTable.getItems().clear();
		tableRaces = FXCollections.<Map<String, Object>>observableArrayList();
		for (final HorseRaceData horseRaceData : todaysRaces) {
			final Map<String, Object> row = Maps.newHashMap();
			row.put("track", horseRaceData.getTrackName());
			row.put("race", horseRaceData.getRaceNumber());
			row.put("horseName", horseRaceData.getHorseName());
			row.put("lane", horseRaceData.getLane());
			row.put("distance", horseRaceData.getDistance());
			row.put("timeModifier", horseRaceData.getTimeModifier());
			row.put("shoes", horseRaceData.getShoes());
			row.put("driverName", horseRaceData.getDriverName());
			row.put("trainerName", horseRaceData.getTrainerName());
			row.put("result", horseRaceData.getResult());
			row.put("horseAvgTime", horseRaceData.getAvgHorseTime() + "(" + horseRaceData.getAvgHorseTimeCount() + ")");
			row.put("driverAvgTime", horseRaceData.getAvgDriverTime() + "(" + horseRaceData.getAvgDriverTimeCount() + ")");

			row.put("horseAvgTimeDistance", horseRaceData.getAvgHorseTimeByDistance() + "(" + horseRaceData.getAvgHorseTimeByDistanceCount() + ")");
			row.put("driverAvgTimeDistance", horseRaceData.getAvgDriverTimeByDistance() + "(" + horseRaceData.getAvgDriverTimeByDistanceCount() + ")");


			row.put("avgTime", (horseRaceData.getAvgHorseTime() + horseRaceData.getAvgDriverTime()) / 2f);

			final ArrayList<String> distances = Lists.newArrayList(todaysRaces.stream()
					.filter(hr -> hr.getRaceNumber() == horseRaceData.getRaceNumber() && hr.getTrackName().equals(horseRaceData.getTrackName()))
					.map(hr -> String.valueOf(hr.getDistance())).distinct().collect(Collectors.toList()));


			final float laneWinPercent = getLaneWinPercent(horseRaceData, distances, horseRaceData.getDistance(), horseRaceData.getLane());
			try {
				if (horseRaceData.getLane() > 0 && horseRaceData.getTimeModifier().contains("a")) {
					final Optional<LaneWinObject> lw = autostartPercentages.stream().filter(p -> p.getDistance() == horseRaceData.getDistance() && p.getLane() == horseRaceData.getLane()).findFirst();
					if (lw.isPresent()) {
						row.put("laneWin", lw.get().getPercent());
					} else {
						System.out.println("Failed to get lanw win from distance " + horseRaceData.getDistance() + " and lane " + horseRaceData.getLane());
						row.put("laneWin", 0);
					}
				} else {
					final Optional<LaneWinObject> lw = voltstartPercentages.stream().filter(p -> p.getDistance() == horseRaceData.getDistance() && p.getLane() == horseRaceData.getLane()).findFirst();
					row.put("laneWin", lw.isPresent()?lw.get().getPercent():0);
				}
			} catch (final IndexOutOfBoundsException e) {
				row.put("laneWin", 0f);
			}

			final SimpleEntry<Integer,Integer> numHorseGallop = DatabaseController.getInstance().getNumHorseGallop(horseRaceData.getHorseId());
			final SimpleEntry<Integer,Integer> numDriverGallop = DatabaseController.getInstance().getNumDriverGallop(horseRaceData.getDriverId());
			row.put("horseGallopRate", numHorseGallop.getValue() + " ("+Math.round((numHorseGallop.getValue()/Float.valueOf(numHorseGallop.getKey()))*10000)/100+"%) ");
			row.put("driverGallopRate", numDriverGallop.getValue() + " (" + Math.round((numDriverGallop.getValue()/Float.valueOf(numDriverGallop.getKey()))*10000)/100 + "%) ");


			float calcValue = prevCalculation(horseRaceData, laneWinPercent);
			row.put("horseAvgDistanceModified", calcValue);

			calcValue = calculatedValue(horseRaceData, calcValue);
			row.put("calculatedValue", calcValue);

			tableRaces.add(row);
		}

		racesTable.getItems().addAll(tableRaces);
	}

	protected float getLaneWinPercent(HorseRaceData horseRaceData, ArrayList<String> distances, int distance, int lane) {
		float laneWinPercent = -1;
		if (horseRaceData.getLane() > 0 && horseRaceData.getTimeModifier().contains("a")) {
			//			if (autostartPercentages == null) { // || prevRaceDistance != horseRaceData.getDistance()) {
			if (astartPercent.containsKey(distances)) {
				final Optional<LaneWinObject> lw = astartPercent.get(distances).stream().filter(p -> p.getDistance() == distance && p.getLane() == lane).findFirst();
				if (lw.isPresent()) {
					laneWinPercent = lw.get().getPercent();
				} else {
					laneWinPercent = 0f;
				}
			} else {
				astartPercent.put(distances, DatabaseController.getInstance().getAutostartWinPercents(distances));
			}
			autostartPercentages = DatabaseController.getInstance().getAutostartWinPercents(distances);
			//			}
			//			if (horseRaceData.getLane() < autostartPercentages.size()) {
			final Optional<LaneWinObject> lw = autostartPercentages.stream().filter(p -> p.getDistance() == horseRaceData.getDistance() && p.getLane() == horseRaceData.getLane()).findFirst();
			if (lw.isPresent()) {
				laneWinPercent = lw.get().getPercent();
			} else {
				System.out.println("Auto Failed to get lane win distance " + horseRaceData.getDistance() + " and lane " + horseRaceData.getLane());
				laneWinPercent = 0;
			}
			//			}
		} else if (horseRaceData.getLane() > 0 && !horseRaceData.getTimeModifier().contains("a")) {
			//			if (voltstartPercentages == null) { // || prevRaceDistance != horseRaceData.getDistance()) {
			voltstartPercentages = DatabaseController.getInstance().getVoltstartWinPercents(distances);
			//			}
			//			if (horseRaceData.getLane() < voltstartPercentages.size()) {
			final Optional<LaneWinObject> lw= voltstartPercentages.stream().filter(p -> p.getDistance() == horseRaceData.getDistance() && p.getLane() == horseRaceData.getLane()).findFirst();
			if (lw.isPresent()) {
				laneWinPercent = lw.get().getPercent();
			} else {
				System.out.println("Volt Failed to get lane win distance " + horseRaceData.getDistance() + " and lane " + horseRaceData.getLane());
				laneWinPercent = 0;
			}
			//			}
		}
		return laneWinPercent;
	}

	protected float calculatedValue(final HorseRaceData horseRaceData, float calcValue) {
		float returnValue = calcValue;
		returnValue += calculateCombinedDriverValue(horseRaceData)/2;
		return returnValue;

	}

	protected float prevCalculation(final HorseRaceData horseRaceData, float laneWinPercent) {
		// Calculate Comparison
		float calcValue = calculateCombinedHorseValue(horseRaceData);

		if (laneWinPercent > -1) {
			calcValue -= laneWinPercent / 8;
		}
		return calcValue;
	}

	protected float calculateCombinedHorseValue(HorseRaceData horseRaceData) {
		float returnValue = 0f;
		boolean distanceAndType = false;
		boolean distance = false;
		returnValue += horseRaceData.getAvgHorseTime();
		float divideBy = 1f;
		if (horseRaceData.getAvgHorseTimeByDistanceAndTypeCount() > 0) {
			distanceAndType = true;
			divideBy += horseDistanceAndTypeMultiplyer ;
		}
		if (horseRaceData.getAvgHorseTimeByDistanceCount() > 0) {
			distance = true;
			divideBy += horseDistanceMultiplier;
		}

		if (distanceAndType) {
			returnValue += horseRaceData.getAvgHorseTimeByDistanceAndType() * horseRaceData.getAvgHorseTimeByDistanceAndTypeCount();//* horseDistanceAndTypeMultiplyer;//
			divideBy += horseRaceData.getAvgHorseTimeByDistanceAndTypeCount();
		}
		if (distance) {
			returnValue += horseRaceData.getAvgHorseTimeByDistance() * horseRaceData.getAvgHorseTimeByDistanceCount(); // * horseDistanceMultiplier;//
			divideBy += horseRaceData.getAvgHorseTimeByDistanceCount();
		}

		return returnValue / divideBy;
	}

	protected float calculateCombinedDriverValue(HorseRaceData horseRaceData) {
		float returnValue = 0f;
		boolean distanceAndType = false;
		boolean distance = false;
		returnValue += horseRaceData.getAvgDriverTime();
		float divideBy = 1f;
		if (horseRaceData.getAvgDriverTimeByDistanceAndTypeCount() > 0) {
			distanceAndType = true;
			divideBy += driverDistanceAndTypeMultiplyer;
		}
		if (horseRaceData.getAvgDriverTimeByDistanceCount() > 0) {
			distance = true;
			divideBy += driverDistanceMultiplier;
		}

		if (distanceAndType) {
			returnValue += horseRaceData.getAvgDriverTimeByDistanceAndType() * horseRaceData.getAvgDriverTimeByDistanceAndTypeCount();// * driverDistanceAndTypeMultiplyer;//
			divideBy += horseRaceData.getAvgDriverTimeByDistanceAndTypeCount();
		}
		if (distance) {
			returnValue += horseRaceData.getAvgDriverTimeByDistance() * horseRaceData.getAvgDriverTimeByDistanceCount();// * driverDistanceMultiplier;//
			divideBy += horseRaceData.getAvgDriverTimeByDistanceCount();
		}

		return returnValue / divideBy;
	}

	protected void resetLaneWinPercentages() {
		autostartPercentages = null;
		voltstartPercentages = null;
	}
}
