package be.iminds.ilabt.jfed.experimenter_gui.reservations;

import be.iminds.ilabt.jfed.experimenter_gui.ui.LocalTimeTextField;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * User: twalcari
 * Date: 1/23/2015
 * Time: 11:53
 */
public class StartEndDurationPane extends GridPane {
    private static final Logger LOG = LoggerFactory.getLogger(StartEndDurationPane.class);
    private static final Duration DEFAULT_DURATION = Duration.ofHours(2);
    private static final int MAX_DAYS = 365;

    @FXML
    private ComboBox<Integer> daysComboBox;
    @FXML
    private ComboBox<Integer> hoursComboBox;
    @FXML
    private ComboBox<Integer> minutesComboBox;
    @FXML
    private DatePicker startDatePicker;
    @FXML
    private DatePicker endDatePicker;
    @FXML
    private LocalTimeTextField startTimeTextField;
    @FXML
    private LocalTimeTextField endTimeTextField;

    private final ObjectProperty<LocalDateTime> startDateTime;
    private final ObjectProperty<LocalDateTime> endDateTime;

    private boolean updatingEndTime = false;
    private boolean updatingDuration = false;

    public StartEndDurationPane() {
        startDateTime = new SimpleObjectProperty<>(LocalDateTime.now());
        endDateTime = new SimpleObjectProperty<>(startDateTime.get().plus(DEFAULT_DURATION));

        FXMLUtil.injectFXML(this);
    }


    @FXML
    private void initialize() {
        //initialize fields
        daysComboBox.setItems(FXCollections.observableList(IntStream.range(0, MAX_DAYS + 1/*because exclusive!*/).boxed().collect(Collectors.toList())));
        hoursComboBox.setItems(FXCollections.observableList(IntStream.range(0, 24).boxed().collect(Collectors.toList())));
        minutesComboBox.setItems(FXCollections.observableList(IntStream.range(0, 60).boxed().collect(Collectors.toList())));

        startTimeTextField.setShowSeconds(false);
        endTimeTextField.setShowSeconds(false);

        //set values in fields before adding listeners
        startDateTimeModelToView();
        endDateTimeModelToView();
        durationModelToView();

        Duration duration = getDuration();
        long durationTime = duration.toMinutes();

        minutesComboBox.setValue((int) (durationTime % 60));

        durationTime /= 60;
        hoursComboBox.setValue((int) (durationTime % 24));

        durationTime /= 24;
        daysComboBox.setValue((int) durationTime);

        //add listeners to dynamically update reservationStartTime and reservationEndTime
        daysComboBox.valueProperty().addListener(event -> durationViewToModel());
        hoursComboBox.valueProperty().addListener(event -> durationViewToModel());
        minutesComboBox.valueProperty().addListener(event -> durationViewToModel());

        startDatePicker.setOnAction(event -> startDateTimeViewToModel());
        startTimeTextField.timeProperty().addListener(event -> startDateTimeViewToModel());

        endDatePicker.setOnAction(event -> endDateTimeViewToModel());
        endTimeTextField.timeProperty().addListener(event -> endDateTimeViewToModel());
    }

    private void startDateTimeViewToModel() {
        Duration oldDuration = getDuration();

        LocalDateTime newStartTime =
                LocalDateTime.of(startDatePicker.getValue(), startTimeTextField.getTime());

        startDateTime.set(newStartTime);

        LocalDateTime newEndTime = newStartTime.plus(oldDuration);
        endDateTime.set(newEndTime);

        endDateTimeModelToView();
    }

    private void startDateTimeModelToView() {
        startDatePicker.setValue(startDateTime.get().toLocalDate());
        startTimeTextField.setTime(startDateTime.get().toLocalTime());
    }

    private void endDateTimeModelToView() {
        endDatePicker.setValue(endDateTime.get().toLocalDate());
        endTimeTextField.setTime(endDateTime.get().toLocalTime());
    }

    private void endDateTimeViewToModel() {
        if (updatingDuration)
            return;

        updatingEndTime = true;

        boolean changed = false;


        if (Period.between(startDatePicker.getValue(), endDatePicker.getValue()).getDays() > MAX_DAYS) {
            endDatePicker.setValue(startDatePicker.getValue().plusDays(MAX_DAYS));
            changed = true;
        }

        LocalDateTime newEndTime =
                LocalDateTime.of(endDatePicker.getValue(), endTimeTextField.getTime());
//        if (newEndTime.isBefore(startDateTime.get())) {
//            newEndTime = startDateTime.get();
//            changed = true;
//        }

        endDateTime.set(newEndTime);
        durationModelToView();

        if (changed)
            endDateTimeModelToView();

        updatingEndTime = false;
    }

    private void durationModelToView() {
        Duration duration = getDuration();
        if (duration.isNegative()) {
            daysComboBox.getStyleClass().addAll("invalid-duration");
            hoursComboBox.getStyleClass().addAll("invalid-duration");
            minutesComboBox.getStyleClass().addAll("invalid-duration");
        } else {
            daysComboBox.getStyleClass().addAll("valid-duration");
            hoursComboBox.getStyleClass().addAll("valid-duration");
            minutesComboBox.getStyleClass().addAll("valid-duration");
        }


        long durationTime = duration.toMinutes();

        int minutes = (int) durationTime % 60;
        durationTime /= 60;

        int hours = (int) durationTime % 24;
        durationTime /= 24;

        int days = (int) durationTime;

        //make sure that only days go negative!
        if (minutes < 0) {
            hours--;
            minutes += 60;
        }
        while (hours < 0) {
            days--;
            hours += 24;
        }


        minutesComboBox.setValue(minutes);
        hoursComboBox.setValue(hours);
        daysComboBox.setValue(days);
    }

    private void durationViewToModel() {
        if (updatingEndTime)
            return;

        updatingDuration = true;

        endDateTime.setValue(startDateTime.get()
                .plus(Duration.ofDays(daysComboBox.getValue())
                        .plusHours(hoursComboBox.getValue())
                        .plusMinutes(minutesComboBox.getValue())));

        endDateTimeModelToView();

        updatingDuration = false;

    }

    private Duration getDuration() {
        return Duration.between(startDateTime.get(), endDateTime.get());
    }

    public LocalDateTime getEndDateTime() {
        return endDateTime.get();
    }


    public LocalDateTime getStartDateTime() {
        return startDateTime.get();
    }

    public DatePicker getEndDatePicker() {
        return endDatePicker;
    }

    public LocalTimeTextField getEndTimeTextField() {
        return endTimeTextField;
    }

    public DatePicker getStartDatePicker() {
        return startDatePicker;
    }

    public LocalTimeTextField getStartTimeTextField() {
        return startTimeTextField;
    }
}
