package controllers;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

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

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import test.Test.CombinedSorter;
import test.Test.customSorter;
import test.TestDataRow;
import test.TestDatabaseRow;

public class TestingTabController implements Initializable {

	@FXML Button calculateButton;
	@FXML Button resetSettings;
	@FXML Label horseAvgTimePercentText;
	@FXML Label horseAvgTimePercentValue;
	@FXML Label horseAvgTimeByDistancePercentText;
	@FXML Label horseAvgTimeByDistancePercentValue;
	@FXML Label driverAvgTimeText;
	@FXML Label driverAvgTimeValue;
	@FXML Label customText;
	@FXML Label combinedText;
	@FXML Label driverAvgTimePercentValue;
	@FXML Label driverAvgTimeByDistancePercentValue;
	@FXML Label customPercentageValue;
	@FXML Label combinedPercentageValue;

	@FXML TextField divisorValueHorse;
	@FXML TextField divisorValueHorseDistance;
	@FXML TextField divisorValueDriver;
	@FXML TextField divisorValueDriverDistance;
	@FXML TextField divisorValueCustom;
	@FXML TextField divisorValueCombined;

	@FXML TextField topCountTextField;

	@FXML TextField horseDriverSameOrderValue;
	@FXML TextField horseDriverDistanceSameOrderValue;
	@FXML TextField AllFourBasicSameOrderValue;
	@FXML TextField AllSameOrderValue;


	@FXML TextField combineHorseWeight;
	@FXML TextField combineDriverWeight;

	String filename = this.getClass().getResource("../test/Atg-Results.csv").getFile();
	String testDataSetFile = this.getClass().getResource("../test/dataset.csv").getFile();
	private CSVParser parser;
	ArrayList<TestDatabaseRow> testData = Lists.newArrayList();

	ArrayList<TestDataRow> threeMonthsData = Lists.newArrayList();

	final HashMap<ArrayList<String>, List<SimpleEntry<Integer, Float>>> autostartWinPercentages = Maps.newHashMap();
	final HashMap<ArrayList<String>, List<SimpleEntry<Integer, Float>>> voltstartWinPercentages = Maps.newHashMap();

	@Override
	public void initialize(URL arg0, ResourceBundle arg1) {
		LoadData();
		setBestSettingFromDB();
	}

	private void setBestSettingFromDB() {
		final DatabaseContoller database = DatabaseContoller.getInstance();
		String dbValue = database.getSetting(divisorValueHorse.getId());
		divisorValueHorse.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueHorse.getId() + "Value");
		horseAvgTimePercentValue.setText(dbValue + "%");

		dbValue = database.getSetting(divisorValueHorseDistance.getId());
		divisorValueHorseDistance.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueHorseDistance.getId() + "Value");
		horseAvgTimeByDistancePercentValue.setText(dbValue + "%");

		dbValue = database.getSetting(divisorValueDriver.getId());
		divisorValueDriver.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueDriver.getId() + "Value");
		driverAvgTimePercentValue.setText(dbValue + "%");

		dbValue = database.getSetting(divisorValueDriverDistance.getId());
		divisorValueDriverDistance.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueDriverDistance.getId() + "Value");
		driverAvgTimeByDistancePercentValue.setText(Strings.isNullOrEmpty(dbValue)?"0%": dbValue + "%");

		dbValue = database.getSetting(divisorValueCustom.getId());
		divisorValueCustom.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueCustom.getId() + "Value");
		customPercentageValue.setText(dbValue + "%");

		dbValue = database.getSetting(divisorValueCombined.getId());
		divisorValueCombined.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueCombined.getId() + "Value");
		combinedPercentageValue.setText(dbValue + "%");

		dbValue = database.getSetting(combineHorseWeight.getId());
		combineHorseWeight.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(combineDriverWeight.getId());
		combineDriverWeight.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);

	}

	public void LoadData() {
		final Character delimiter = ',';
		BufferedReader br;
		try {
			br = new BufferedReader(new FileReader(filename));
			parser = CSVFormat.DEFAULT.withDelimiter(delimiter).withHeader().parse(br);

			for (final CSVRecord record : parser) {
				final TestDatabaseRow row = new TestDatabaseRow(record.toMap());
				testData.add(row);
			}
		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	@FXML
	public void CalculateAction() {
		ReadTestData();
		getPercentagesOrderedBy(Integer.valueOf(topCountTextField.getText()));
	}

	private void ReadTestData() {
		final Character delimiter = ',';
		BufferedReader br;
		threeMonthsData = Lists.newArrayList();
		try {
			br = new BufferedReader(new FileReader(testDataSetFile));
			parser = CSVFormat.DEFAULT.withDelimiter(delimiter).withHeader().parse(br);

			for (final CSVRecord record : parser) {
				final TestDataRow row = new TestDataRow(record.toMap());
				threeMonthsData.add(row);
			}
		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	// Redo to save entire laneWin table in variable for testing.
	private float getWinPercentage(ArrayList<String> distances, boolean autostart, int lane) {
		float returnValue;
		try {
			if (autostart) {
				if (autostartWinPercentages.containsKey(distances)) {
					final SimpleEntry<Integer, Float> entry = autostartWinPercentages.get(distances).get(lane);

					if (entry != null) {
						returnValue = entry.getValue();
					} else {
						returnValue = 0f;
					}
				} else {
					final List<TestDatabaseRow> races = testData.stream()
							.filter(p ->
							distances.contains(String.valueOf(p.getDistance())) &&
							p.getTimeModifier().contains("a")).collect(Collectors.toList());

					final ArrayList<SimpleEntry<Integer, Float>> percentages = Lists.newArrayList();
					for (int i = 1; i <= 13; i++) {
						final int pos = i;
						percentages.add(new SimpleEntry<Integer, Float>(pos, (races.stream().filter(p -> p.getLane() == pos && p.getResult() == 1).count() /
								(float) races.stream().filter(p -> p.getLane() == pos).count()) * 100));
					}
					autostartWinPercentages.put(distances, percentages);
					returnValue = autostartWinPercentages.get(distances).get(lane).getValue();
				}
			} else {
				if (voltstartWinPercentages.containsKey(distances)) {
					final SimpleEntry<Integer, Float> entry = voltstartWinPercentages.get(distances).get(lane);
					if (entry != null) {
						returnValue = entry.getValue();
					} else {
						returnValue = 0f;
					}
				} else {
					final List<TestDatabaseRow> races = testData.stream()
							.filter(p ->
							distances.contains(String.valueOf(p.getDistance())) &&
							!p.getTimeModifier().contains("a")).collect(Collectors.toList());

					final ArrayList<SimpleEntry<Integer, Float>> percentages = Lists.newArrayList();
					for (int i = 1; i <= 12; i++) {
						final int pos = i;
						percentages.add(new SimpleEntry<Integer, Float>(pos, (races.stream().filter(p -> p.getLane() == pos && p.getResult() == 1).count() /
								(float) races.stream().filter(p -> p.getLane() == pos).count()) * 100));
					}
					voltstartWinPercentages.put(distances, percentages);
					returnValue = voltstartWinPercentages.get(distances).get(lane).getValue();
				}
			}

		} catch (final IndexOutOfBoundsException e) {
			returnValue = 0f;
		}
		return returnValue;
	}

	public void getPercentagesOrderedBy(int topCount) {
		// sort by RaceDate, TrackId, RaceNumber

		Collections.sort(threeMonthsData, new TestDataRow.RaceDateSorter()
				.thenComparing(new TestDataRow.TrackIdSorter())
				.thenComparing(new TestDataRow.RaceNumberSorter()));

		int correctHorseByDistance = 0;
		int correctHorse= 0;
		int correctDriver = 0;
		int correctDriverByDistance = 0;
		int correctCustom = 0;
		int correctCombined = 0;
		int racesCount = 0;
		for (int i = 0; i < threeMonthsData.size(); i++) {
			final TestDataRow firstRow = threeMonthsData.get(i);
			final List<TestDataRow> currentRaces = threeMonthsData.stream().filter(d -> firstRow.getRaceDate().equals(d.getRaceDate()) &&
					firstRow.getTrackId() == d.getTrackId() &&
					firstRow.getRaceNumber() == d.getRaceNumber()).collect(Collectors.toList());

			if (currentRaces.size() < 7) { // Not enough info
				continue;
			}

			final ArrayList<String> distances = Lists.newArrayList();

			currentRaces.forEach(cr -> {
				final String distance = String.valueOf(cr.getDistance());
				if(!distances.contains(distance)) {
					distances.add(distance);
				}
			});

			if (firstRow.getRaceNumber() >= 13) {
				i = i + currentRaces.size();
				continue;
			}

			racesCount++;
			final boolean autostart = firstRow.getTimeModifier().contains("a");
			currentRaces.forEach(cr -> cr.setLaneWinPercent(getWinPercentage(distances, autostart, cr.getLane())));

			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueHorse.getText())));
			correctHorse = checkIfCorrect(topCount, correctHorse, currentRaces, new TestDataRow.horseAvgTimeSorter());

			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueHorseDistance.getText())));
			correctHorseByDistance = checkIfCorrect(topCount, correctHorseByDistance, currentRaces, new TestDataRow.horseAvgTimeByDistanceSorter());

			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueDriver.getText())));
			correctDriver = checkIfCorrect(topCount, correctDriver, currentRaces, new TestDataRow.driverAvgTimeSorter());

			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueDriverDistance.getText())));
			correctDriverByDistance = checkIfCorrect(topCount, correctDriverByDistance, currentRaces, new TestDataRow.driverAvgTimeByDistanceSorter());

			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueCustom.getText())));
			correctCustom = checkIfCorrect(topCount, correctCustom, currentRaces, new customSorter());

			final float horseWeight = Float.valueOf(combineHorseWeight.getText());
			final float driverWeight = Float.valueOf(combineDriverWeight.getText());
			currentRaces.forEach(cr -> cr.setLaneWinDivisor(Float.valueOf(divisorValueCombined.getText())));
			correctCombined = checkIfCorrect(topCount, correctCombined, currentRaces, new CombinedSorter(horseWeight, driverWeight,
					Float.valueOf(divisorValueHorse.getText()), Float.valueOf(divisorValueDriver.getText())));
			i = i + currentRaces.size();
		}

		updateTextColor(correctHorse, racesCount, horseAvgTimePercentValue);
		if (horseAvgTimePercentValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueHorse, correctHorse/(racesCount * 1f) * 100);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);

		updateTextColor(correctHorseByDistance, racesCount, horseAvgTimeByDistancePercentValue);
		if (horseAvgTimeByDistancePercentValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueHorseDistance, correctHorseByDistance/(racesCount * 1f) * 100);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);

		updateTextColor(correctDriver, racesCount, driverAvgTimePercentValue);
		if (driverAvgTimePercentValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueDriver, correctDriver/(racesCount * 1f) * 100);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);

		updateTextColor(correctDriverByDistance, racesCount, driverAvgTimeByDistancePercentValue);
		if (driverAvgTimeByDistancePercentValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueDriverDistance, correctDriverByDistance/(racesCount * 1f) * 100);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);

		updateTextColor(correctCustom, racesCount, customPercentageValue);
		if (customPercentageValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueCustom, correctCustom/(racesCount * 1f) * 100);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);

		updateTextColor(correctCombined, racesCount, combinedPercentageValue);
		final float combinedResultValue = correctCombined/(racesCount * 1f) * 100;
		if (combinedPercentageValue.getTextFill().equals(Color.GREEN)) {
			updateTextFieldSetting(divisorValueCombined, combinedResultValue);
			updateTextFieldSetting(combineHorseWeight, combinedResultValue);
			updateTextFieldSetting(combineDriverWeight, combinedResultValue);
		}
		//		updateTextColor(correctHorse, racesCount, horseAvgTimePercentText);


		horseAvgTimePercentValue.setText(String.valueOf(correctHorse/(racesCount * 1f) * 100) + "%");
		horseAvgTimeByDistancePercentValue.setText(String.valueOf(correctHorseByDistance/(racesCount * 1f) * 100) + "%");
		driverAvgTimePercentValue.setText(String.valueOf(correctDriver/(racesCount * 1f) * 100) + "%");
		driverAvgTimeByDistancePercentValue.setText(String.valueOf(correctDriverByDistance/(racesCount * 1f) * 100) + "%");
		customPercentageValue.setText(String.valueOf(correctCustom/(racesCount * 1f) * 100) + "%");
		combinedPercentageValue.setText(String.valueOf(combinedResultValue) + "%");

	}

	private void updateTextFieldSetting(TextField settingTextField, float newValue) {

		final DatabaseContoller database = DatabaseContoller.getInstance();
		final String oldSetting = database.getSetting(settingTextField.getId() + "Value");
		if (!Strings.isNullOrEmpty(oldSetting) && Float.valueOf(oldSetting) < newValue && Integer.valueOf(topCountTextField.getText()) == 2) {
			database.setSetting(settingTextField.getId() + "Value", String.valueOf(newValue));
			database.setSetting(settingTextField.getId(), settingTextField.getText());
		} else if (Strings.isNullOrEmpty(oldSetting) && Integer.valueOf(topCountTextField.getText()) == 2) {
			database.setSetting(settingTextField.getId() + "Value", String.valueOf(newValue));
			database.setSetting(settingTextField.getId(), settingTextField.getText());
		}
	}

	private void updateTextColor(int correct, int totalRaces, Label textLabel) {

		final float prevValue = Float.valueOf(textLabel.getText().substring(0, textLabel.getText().length() - 1));
		final float newValue = correct/(totalRaces * 1f) * 100;

		if (prevValue < newValue) {
			textLabel.setTextFill(Color.GREEN);
		} else if (prevValue == newValue) {
			textLabel.setTextFill(Color.BLACK);
		} else {
			textLabel.setTextFill(Color.RED);
		}
	}

	private int checkIfCorrect(int topCount, int correct, final List<TestDataRow> currentRaces, Comparator<TestDataRow> sorter) {
		currentRaces.sort(sorter);

		for (int p = 0; p < topCount; p++) {
			if (currentRaces.size() > p && currentRaces.get(p).getResult() == 1) {
				correct++;
				break;
			}
		}
		return correct;
	}

	@FXML
	public void resetSettingsAction() {
		setBestSettingFromDB();
	}
}
