package controllers;

import java.net.URL;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.MapValueFactory;
import javafx.scene.layout.Pane;
import parsers.TravsportParser;

@SuppressWarnings("rawtypes")
public class TestTabController extends AtgController {

    @FXML Pane DDButtonPane;
    @FXML Pane DDResultPane;
    @FXML DatePicker datePicker;
    @FXML Button getRacesButton;
    @FXML Button getRacesForDateButton;
    @FXML Button getRaceCalendarButton;
    @FXML ChoiceBox<String> raceTypeSelector;

    @FXML TableColumn<Map, Float> horseAvarageWithModifiersColumn = new TableColumn<>("horseAvgModified");
    @FXML TableColumn<Map, Float> driverAvarageWithModifiersColumn = new TableColumn<>("driverAvgModified");
    @FXML TableColumn<Map, Float> horseAvarageDistanceModifiedColumn = new TableColumn<>("horseAvgDistanceModified");
    @FXML TableColumn<Map, Float> driverAvarageDistanceModifiedColumn = new TableColumn<>("driverAvgDistanceModified");

    @FXML TableColumn<Map, Float> horseDaysLastRaceColumn = new TableColumn<>("horseDaysLastRace");
    @FXML TableColumn<Map, Float> driverDaysLastRaceColumn = new TableColumn<>("driverDaysLastRace");
    @FXML TableColumn<Map, Float> horseDriverDaysLastRaceColumn = new TableColumn<>("bothDaysLastRace");

    @FXML TableColumn<Map, Float> combinedColumn = new TableColumn<>("Custom");
    @FXML TableColumn<Map, Float> customColumn = new TableColumn<>("Combined");

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

    ObservableList<Map<String, Object>> tableRaces;
    ObservableList<String> raceTypes;
    private Float driverDivisor;
    private Float horseDivisor;
    private Float driverDistanceDivisor;
    private Float horseDistanceDivisor;
    private Float customDivisor;
    private Float combinedDivisor;
    private float horseWeight;
    private float driverWeight;
    @FXML CheckBox getFullStatsCheckBox;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        super.initialize(arg0, arg1);

        horseAvarageWithModifiersColumn.setCellValueFactory(new MapValueFactory<>("horseAvgModified"));
        driverAvarageWithModifiersColumn.setCellValueFactory(new MapValueFactory<>("driverAvgModified"));
        customColumn.setCellValueFactory(new MapValueFactory<>("custom"));
        combinedColumn.setCellValueFactory(new MapValueFactory<Float>("combined"));

        driverAvarageDistanceModifiedColumn.setCellValueFactory(new MapValueFactory<>("driverAvgDistanceModified"));
        horseAvarageDistanceModifiedColumn.setCellValueFactory(new MapValueFactory<>("horseAvgDistanceModified"));

        horseDaysLastRaceColumn.setCellValueFactory(new MapValueFactory<>("horseDaysLastRace"));
        driverDaysLastRaceColumn.setCellValueFactory(new MapValueFactory<>("driverDaysLastRace"));
        horseDriverDaysLastRaceColumn.setCellValueFactory(new MapValueFactory<>("bothDaysLastRace"));

        raceTypes = FXCollections.observableArrayList();
        raceTypes.addAll(Lists.newArrayList("V75", "dd", "ld"));
        raceTypeSelector.setItems(raceTypes);

        datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null) {
                getRacesButton.setDisable(true);
                getRacesForDateButton.setDisable(true);
                getRaceCalendarButton.setDisable(true);
            } else if (newValue.isBefore(LocalDate.now())) {
                getRacesButton.setDisable(false);
                getRacesForDateButton.setDisable(false);
                getRaceCalendarButton.setDisable(false);
            } else if (newValue.isEqual(LocalDate.now())) {
                getRacesButton.setDisable(false);
                getRacesForDateButton.setDisable(false);
                getRaceCalendarButton.setDisable(false);
            } else {
                // getRacesButton.setDisable(true);
                // getRacesForDateButton.setDisable(true);
            }
        });

        getSettings();

    }

    private void getSettings() {
        final DatabaseController db = DatabaseController.getInstance();

        driverDivisor = Float.parseFloat(db.getSetting("divisorValueDriver"));
        horseDivisor = Float.parseFloat(db.getSetting("divisorValueHorse"));
        driverDistanceDivisor = Float.parseFloat(db.getSetting("divisorValueDriverDistance"));
        horseDistanceDivisor = Float.parseFloat(db.getSetting("divisorValueHorseDistance"));
        customDivisor = Float.parseFloat(db.getSetting("divisorValueCustom"));
        combinedDivisor = Float.parseFloat(db.getSetting("divisorValueCombined"));

        horseWeight = Float.parseFloat(db.getSetting("combineHorseWeight"));
        driverWeight = Float.parseFloat(db.getSetting("combineDriverWeight"));
    }

    @Override
    public void updateRacesTable() {
        final Stopwatch stopWatch = Stopwatch.createStarted();
        super.updateRacesTable();
        System.out.println("Super updateTabel took: " + stopWatch.elapsed(TimeUnit.SECONDS) + " seconds");

        todaysRaces.forEach(tr -> {
            final List<String> distances = todaysRaces.stream()
                    .filter(f -> tr.getRaceNumber() == f.getRaceNumber() && tr.getTrackName().equals(f.getTrackName()))
                    .map(m -> String.valueOf(m.getDistance())).distinct().collect(Collectors.toList());
            final int i = todaysRaces.indexOf(tr);
            final Map<String, Object> currentRow = racesTable.getItems().get(i);

            final float laneWinPercent = getLaneWinPercent(tr, Lists.newArrayList(distances), tr.getDistance(), tr.getLane());
            currentRow.put("horseAvgModified", tr.getAvgHorseTime() - (laneWinPercent / horseDivisor));
            currentRow.put("driverAvgModified", tr.getAvgDriverTime() - (laneWinPercent / driverDivisor));
            currentRow.put("horseAvgDistanceModified", tr.getAvgHorseTime() - (laneWinPercent / horseDistanceDivisor));
            currentRow.put("driverAvgDistanceModified", tr.getAvgDriverTime() - (laneWinPercent / driverDistanceDivisor));
            currentRow.put("custom", "Not Done"); // tr.getAvgHorseTime() - (laneWinPercent / customDivisor));
            currentRow.put("combined",
                    ((tr.getAvgHorseTime() - (laneWinPercent / horseDivisor) * horseWeight)
                            + (tr.getAvgDriverTime() - (laneWinPercent / driverDivisor) * driverWeight))
                            - (laneWinPercent / combinedDivisor));

            currentRow.put("horseDaysLastRace",
                    DatabaseController.getInstance().getLastRaceDaysHorse(tr.getHorseId(), tr.getRaceDate(), tr.getRaceNumber()));
            final int daysSinceLastRaceDriver = DatabaseController.getInstance()
                    .getLastRaceDaysDriver(tr.getDriverId(), tr.getRaceDate(), tr.getRaceNumber());
            currentRow.put("driverDaysLastRace", daysSinceLastRaceDriver);
            currentRow.put("bothDaysLastRace",
                    DatabaseController.getInstance()
                            .getLastRaceDaysBoth(tr.getHorseId(), tr.getDriverId(), tr.getRaceDate(), tr.getRaceNumber()));

        });

        System.out.println("Other update took " + stopWatch.elapsed(TimeUnit.SECONDS) + " seconds");

        stopWatch.reset().start();
        normalizeTabel();
        System.out.println("normalize took " + stopWatch.elapsed(TimeUnit.SECONDS) + " seconds");
        stopWatch.stop();

    }

    private void normalizeTabel() {
        final ObservableList<Map<String, Object>> items = racesTable.getItems();

        int i = 0;
        while (i < items.size()) {
            final Map<String, Object> firstRow = items.get(i);
            final List<Map<String, Object>> race = items.stream()
                    .filter(p -> p.get("track").equals(firstRow.get("track")) && p.get("race").equals(firstRow.get("race")))
                    .collect(Collectors.toList());
            normalizeCombined(race);
            normalizeHorse(race);
            normalizeHorseDistance(race);
            normalizeDriver(race);
            normalizeDriverDistance(race);
            i += race.size();
        }
    }

    private void normalizeCombined(List<Map<String, Object>> race) {
        try {
            final List<Float> combinedValues
                    = race.stream().map(m -> ((Float) m.get("combined")).floatValue()).collect(Collectors.toList());

            final double total = combinedValues.stream().mapToDouble(a -> a).sum();

            race.stream().forEach(r -> {
                final double newValue = (((Float) r.get("combined")).floatValue() / total) * 100;
                r.put("combined", newValue);
            });
        } catch (final ClassCastException e) {
            System.out.println("WHAT");
        }
    }

    private void normalizeHorse(List<Map<String, Object>> race) {
        try {
            final List<Float> combinedValues
                    = race.stream().map(m -> ((Float) m.get("horseAvgModified")).floatValue()).collect(Collectors.toList());

            final double total = combinedValues.stream().mapToDouble(a -> a).sum();

            race.stream().forEach(r -> {
                final double newValue = (((Float) r.get("horseAvgModified")).floatValue() / total) * 100;
                r.put("horseAvgModified", String.valueOf(newValue));
            });
        } catch (final ClassCastException e) {
            System.out.println("WHAT");
        }
    }

    private void normalizeHorseDistance(List<Map<String, Object>> race) {
        try {
            final List<Float> combinedValues = race.stream().map(m -> ((Float) m.get("horseAvgDistanceModified")).floatValue())
                    .collect(Collectors.toList());

            final double total = combinedValues.stream().mapToDouble(a -> a).sum();

            race.stream().forEach(r -> {
                final double newValue = (((Float) r.get("horseAvgDistanceModified")).floatValue() / total) * 100;
                r.put("horseAvgDistanceModified", String.valueOf(newValue));
            });
        } catch (final ClassCastException e) {
            System.out.println("WHAT");
        }
    }

    private void normalizeDriver(List<Map<String, Object>> race) {
        try {
            final List<Float> combinedValues
                    = race.stream().map(m -> ((Float) m.get("driverAvgModified")).floatValue()).collect(Collectors.toList());

            final double total = combinedValues.stream().mapToDouble(a -> a).sum();

            race.stream().forEach(r -> {
                final double newValue = (((Float) r.get("driverAvgModified")).floatValue() / total) * 100;
                r.put("driverAvgModified", String.valueOf(newValue));
            });
        } catch (final ClassCastException e) {
            System.out.println("WHAT");
        }
    }

    private void normalizeDriverDistance(List<Map<String, Object>> race) {
        try {
            final List<Float> combinedValues = race.stream().map(m -> ((Float) m.get("driverAvgDistanceModified")).floatValue())
                    .collect(Collectors.toList());

            final double total = combinedValues.stream().mapToDouble(a -> a).sum();

            race.stream().forEach(r -> {
                final double newValue = (((Float) r.get("driverAvgDistanceModified")).floatValue() / total) * 100;
                r.put("driverAvgDistanceModified", String.valueOf(newValue));
            });
        } catch (final ClassCastException e) {
            System.out.println("WHAT");
        }
    }

    @FXML
    public void GetRacesAction() {
        final Stopwatch createStarted = Stopwatch.createStarted();
        todaysRaces = DatabaseController.getInstance().getTodaysRaces("", datePicker.getValue().toString(), true);
        System.out.println("Getting from database took: " + createStarted.elapsed(TimeUnit.SECONDS) + " seconds");
        updateRacesTable();
        createStarted.stop();
        System.out.println("Updateing table took " + createStarted.elapsed(TimeUnit.SECONDS) + " seconds");
    }

    @FXML
    public void GetRacesForDate() {
        // System.out.println("Getting races for " +
        // raceTypeSelector.getSelectionModel().getSelectedItem() + " at date " +
        // datePicker.getValue().toString());
        // resetLaneWinPercentages();
        // AtgParser.getInstance().getStartsForDay(datePicker.getValue().toString(),
        // raceTypeSelector.getSelectionModel().getSelectedItem());
        // GetRacesAction();
        getRaceCalendarButton.setDisable(true);
        getRacesForDateButton.setDisable(true);
        Platform.runLater(() -> {
            TravsportParser.getInstance().getRaceCalendar(datePicker.getValue().toString(), getFullStatsCheckBox.isSelected());
            System.out.println("DONE");
            getRaceCalendarButton.setDisable(false);
            getRacesForDateButton.setDisable(false);
        });
    }

    public void GetRaceCalendar() {
        getRaceCalendarButton.setDisable(true);
        getRacesForDateButton.setDisable(true);
        Platform.runLater(() -> {
            TravsportParser.getInstance().getRaceCalendar(null, false);
            // AtgParser.getInstance().getResultsForDate(raceTypeSelector.getSelectionModel().getSelectedItem(),
            // datePicker.getValue().toString());
            // GetRacesAction();
            getRaceCalendarButton.setDisable(false);
            getRacesForDateButton.setDisable(false);
        });
    }
}
