package controllers;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
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 objects.LaneWinObject;
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 Label horseDriverSameOrderValue;
	@FXML Label horseDriverDistanceSameOrderValue;
	@FXML Label AllFourBasicSameOrderValue;
	@FXML Label AllSameOrderValue;

	@FXML TextField combineHorseWeight;
	@FXML TextField combineDriverWeight;

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

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

	final HashMap<ArrayList<String>, List<LaneWinObject>> autostartWinPercentages = Maps.newHashMap();
	final HashMap<ArrayList<String>, List<LaneWinObject>> voltstartWinPercentages = Maps.newHashMap();
	private int correctSameHorseDriverTotalRaces;
	private int correctSameHorseDriverByDistanceTotalRaces;
	private int correctSameHorseDriver;

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

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

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

		dbValue = database.getSetting(divisorValueDriver.getId());
		divisorValueDriver.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueDriver.getId() + "Value");
		driverAvgTimePercentValue.setText(Strings.isNullOrEmpty(dbValue)?"0%":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(Strings.isNullOrEmpty(dbValue)?"0%":dbValue + "%");

		dbValue = database.getSetting(divisorValueCombined.getId());
		divisorValueCombined.setText(Strings.isNullOrEmpty(dbValue)?"1":dbValue);
		dbValue = database.getSetting(divisorValueCombined.getId() + "Value");
		combinedPercentageValue.setText(Strings.isNullOrEmpty(dbValue)?"0%":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.withQuote('"').withDelimiter(delimiter).withHeader().parse(br);

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

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

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

			for (final CSVRecord record : parser) {
				final TestDataRow row = new TestDataRow(record.toMap());
				if (row.getRaceNumber() > 2 && row.getRaceNumber() < 13) {

					if (row.getRaceNumber() == 3) {
						dbDumpTestData.add(row);
					}
				}
			}
		} catch (final IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	// Redo to save entire laneWin table in variable for testing.
	private float getWinPercentage(ArrayList<String> distances, boolean autostart, int lane, int distance) {
		float returnValue;
		try {
			if (autostart) {
				if (autostartWinPercentages.containsKey(distances)) {
					final Optional<LaneWinObject> entry = autostartWinPercentages.get(distances).stream().filter(p -> p.getLane() == lane && p.getDistance() == distance).findFirst();

					if (entry.isPresent()) {
						returnValue = entry.get().getPercent();
					} else {
						returnValue = 0f;
					}
				} else {


					final List<LaneWinObject> autostartWinPercents = DatabaseController.getInstance().getAutostartWinPercents(distances);
					autostartWinPercentages.put(distances, autostartWinPercents);
					final Optional<LaneWinObject> lw = autostartWinPercentages.get(distances).stream().filter(p -> p.getLane() == lane && p.getDistance() == distance).findFirst();
					if (lw.isPresent()) {
						returnValue = lw.get().getPercent();
					} else {
						System.out.println("Auto No percentage found for lane " + lane + " and distance " + distance);
						returnValue = 0;
					}
				}
			} else {
				if (voltstartWinPercentages.containsKey(distances)) {
					final Optional<LaneWinObject> entry = voltstartWinPercentages.get(distances).stream().filter(p -> p.getDistance() == distance && p.getLane() == lane).findFirst();
					if (entry.isPresent()) {
						returnValue = entry.get().getPercent();
					} else {
						returnValue = 0f;
					}
				} else {

					final List<LaneWinObject> percentages = DatabaseController.getInstance().getVoltstartWinPercents(distances);
					voltstartWinPercentages.put(distances, percentages);

					final Optional<LaneWinObject> lw = voltstartWinPercentages.get(distances).stream().filter(p -> p.getDistance() == distance && p.getLane() == lane).findFirst();
					if (lw.isPresent()) {
						returnValue = lw.get().getPercent();
					} else {
						System.out.println("Volt No percentage found for lane " + lane + " and distance " + distance);
						returnValue = 0f;
					}
				}
			}

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

	public void getPercentagesOrderedBy(int topCount) {
		// sort by RaceDate, TrackId, RaceNumber
		Collections.sort(dbDumpTestData, 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;

		correctSameHorseDriver = 0;
		final int correctSameHorseDriverByDistance = 0;
		final int correctSameAllFour = 0;
		final int correctSameAll = 0;

		correctSameHorseDriverTotalRaces = 0;
		correctSameHorseDriverByDistanceTotalRaces = 0;
		final int correctSameAllFourTotalRaces = 0;
		final int correctSameAllTotalRaces = 0;


		for (int i = 0; i < dbDumpTestData.size(); i++) {
			final TestDataRow firstRow = dbDumpTestData.get(i);
			final List<TestDataRow> currentRaces = dbDumpTestData.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;
			}
			final boolean autostart = firstRow.getTimeModifier().contains("a");
			currentRaces.forEach(cr -> cr.setLaneWinPercent(getWinPercentage(distances, autostart, cr.getLane(), cr.getDistance())));

			final List<TestDataRow> horseRaces = new ArrayList<TestDataRow>();
			final List<TestDataRow> horseDistanceRaces = new ArrayList<TestDataRow>();
			final List<TestDataRow> driverRaces = new ArrayList<TestDataRow>();
			final List<TestDataRow> driverDistanceRaces = new ArrayList<TestDataRow>();
			final List<TestDataRow> customRaces = new ArrayList<TestDataRow>();
			final List<TestDataRow> combinedRaces = new ArrayList<TestDataRow>();

			for (final TestDataRow cr : currentRaces) {
				horseRaces.add(cr);
				horseDistanceRaces.add(cr);
				driverRaces.add(cr);
				driverDistanceRaces.add(cr);
				customRaces.add(cr);
				combinedRaces.add(cr);
			}


			racesCount++;

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

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

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

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

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

			// Correct combination of sorting same order X first
			checkCombinedHorseDriverSortingCorrect(combinedRaces);

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

		System.out.println("TESTING hr dr combined bet on " + correctSameHorseDriverTotalRaces + " of " +
				racesCount + " correct " + correctSameHorseDriver +  " " + correctSameHorseDriver / (correctSameHorseDriverTotalRaces * 1f));
		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 checkCombinedHorseDriverSortingCorrect(List<TestDataRow> currentRaces) {

		currentRaces.forEach(hr -> hr.setLaneWinDivisor(Float.valueOf(divisorValueHorse.getText())));
		currentRaces.sort(new TestDataRow.horseAvgTimeSorter());
		List<TestDataRow> horseSorted = Lists.newArrayList(currentRaces);



		currentRaces.forEach(hr -> hr.setLaneWinDivisor(Float.valueOf(divisorValueDriver.getText())));
		currentRaces.sort(new TestDataRow.driverAvgTimeSorter());
		final List<TestDataRow> driverSorted = Lists.newArrayList(currentRaces);

		final int maxSelect = horseSorted.size() - 1 >= Integer.parseInt(topCountTextField.getText())?Integer.parseInt(topCountTextField.getText()):horseSorted.size() - 1;
		horseSorted = horseSorted.subList(0, maxSelect);
		final List<TestDataRow> driverSortedFiltered = driverSorted.subList(0, maxSelect);

		// disjoint Returns true if no matches
		if (!Collections.disjoint(horseSorted, driverSorted)) {
			//			final List<TestDataRow> same = horseSorted.stream().filter(hs -> driverSortedFiltered.contains(hs)).collect(Collectors.toList());
			final List<TestDataRow> same = Lists.newArrayList();
			final Integer topCount = maxSelect;
			for (int i = 0; i < topCount; i++) {
				if (horseSorted.get(i).equals(driverSorted.get(i))) {
					same.add(horseSorted.get(i));
				}
			}
			//			if (same.size() == topCount) {
			for (final TestDataRow testDataRow : same) {
				if (testDataRow.getResult() == 1) {
					correctSameHorseDriver++;
				}
				correctSameHorseDriverTotalRaces++;
			}
			//			}
		} else {
			System.out.println("No bet, horse and driver lists not the same");

		}
	}

	private void updateTextFieldSetting(TextField settingTextField, float newValue) {

		final DatabaseController database = DatabaseController.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();
	}
}
