package com.imyeyu.fx.ui.components;

import com.imyeyu.fx.TimiFX;
import com.imyeyu.fx.icon.TimiFXIcon;
import com.imyeyu.fx.icon.TimiIcon;
import com.imyeyu.fx.ui.TimiFXUI;
import com.imyeyu.fx.utils.Nodes;
import com.imyeyu.java.bean.Callback;
import com.imyeyu.utils.Time;
import com.sun.javafx.scene.control.DatePickerContent;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.skin.DatePickerSkin;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.util.StringConverter;
import lombok.Getter;

import java.time.LocalDate;
import java.time.LocalTime;

/**
 * 详细时间选择器
 *
 * @author 夜雨
 * @since 2021-12-27 01:02
 */
public class DateTimePicker extends Region implements TimiFXUI, TimiFXUI.Colorful {

	private static final String STYLE_CLASS = "date-time-picker";
	private static final String STYLE_CLASS_TIME_PANE = "date-time-picker-time-pane";
	private static final String STYLE_CLASS_TIME_BOX = "date-time-picker-time-box";
	private static final String STYLE_CLASS_NOW = "date-time-picker-now";
	private static final String STYLE_CLASS_CLEAR = "date-time-picker-clear";

	/** 日期选择器 */
	@Getter
	private final DatePicker datePicker;

	/** 当前值 */
	private final LongProperty value;

	private final BorderPane timePane;
	private final ListView<String> hour, minute, second;

	/** 默认构造器 */
	public DateTimePicker() {
		getStylesheets().add(DateTimePicker.class.getResource(RESOURCE + "style/components/date-time-picker.css").toExternalForm());

		value = new SimpleLongProperty(-1);

		// 时间选择
		hour = new ListView<>();
		minute = new ListView<>();
		second = new ListView<>();

		hour.setBorder(Stroke.RB);
		minute.setBorder(Stroke.RB);
		second.setBorder(Stroke.BOTTOM);

		GridPane hmsPane = new GridPane();
		hmsPane.getStyleClass().add(STYLE_CLASS_TIME_BOX);
		hmsPane.addRow(0, hour, minute, second);

		Button now = new Button(TimiFXUI.MULTILINGUAL.text("now_tick"));
		now.setBorder(Stroke.LEFT);
		now.getStyleClass().add(STYLE_CLASS_NOW);

		timePane = new BorderPane(hmsPane);
		timePane.getStylesheets().add(DateTimePicker.class.getResource(RESOURCE + "style/components/date-time-picker.css").toExternalForm());
		timePane.getStyleClass().add(STYLE_CLASS_TIME_PANE);
		timePane.setPrefWidth(140);
		timePane.setBottom(now);
		BorderPane.setAlignment(now, Pos.CENTER_RIGHT);

		// 日期组件降权
		datePicker = new DatePicker() {

			@Override
			protected Skin<?> createDefaultSkin() {
				DatePickerSkin skin = new DatePickerSkin(this) {

					private BorderPane root;

					@Override
					public Node getPopupContent() {
						Node popupContent = super.getPopupContent();
						if (popupContent instanceof DatePickerContent content) {
							if (root == null) {
								// 插入时间选择
								timePane.prefHeightProperty().bind(content.heightProperty());

								root = new BorderPane();
								root.setCenter(popupContent);
								root.setRight(timePane);
							}
						}
						return root;
					}

					@Override
					public void hide() {
						// 失焦关闭而非选择后关闭
					}
				};
				IconButton clear = new IconButton(TimiFXIcon.get(TimiIcon.FAIL, GRAY));
				clear.setBorder(Stroke.DEFAULT);
				clear.getStyleClass().add(STYLE_CLASS_CLEAR);
				clear.translateXProperty().bind(widthProperty().subtract(40));
				clear.setTranslateY(15);
				clear.visibleProperty().bind(valueProperty().isNotNull());
				clear.setOnAction(e -> clear());
				skin.getChildren().add(clear);
				return skin;
			}
		};
		datePicker.setEditable(false);
		datePicker.prefWidthProperty().bind(widthProperty());
		datePicker.prefHeightProperty().bind(heightProperty());
		datePicker.getEditor().setCursor(Cursor.DEFAULT);
		datePicker.setConverter(new StringConverter<>() {

			@Override
			public String toString(LocalDate object) {
				Long unixTime = value.getValue();
				if (object == null || unixTime == null) {
					return "";
				}
				return Time.toDateTime(unixTime);
			}

			@Override
			public LocalDate fromString(String string) {
				return null;
			}
		});
		datePicker.setOnShown(e -> {
			hour.scrollTo(hour.getSelectionModel().getSelectedIndex() - 3);
			minute.scrollTo(minute.getSelectionModel().getSelectedIndex() - 3);
			second.scrollTo(second.getSelectionModel().getSelectedIndex() - 3);
		});

		getStyleClass().add(STYLE_CLASS);
		setPrefWidth(200);
		getChildren().add(datePicker);

		// ---------- 事件 ----------

		// 时间选择数据
		for (int i = 0; i < 24; i++) {
			hour.getItems().add(String.format("%02d", i));
		}
		for (int i = 0; i < 60; i++) {
			minute.getItems().add(String.format("%02d", i));
		}
		second.getItems().addAll(minute.getItems());

		// 时间滚动居中
		hour.getSelectionModel().select(0);
		minute.getSelectionModel().select(0);
		second.getSelectionModel().select(0);
		EventHandler<ScrollEvent> middleScroll = e -> {
			if (e.getSource() instanceof ListView<?> list) {
				if (e.getDeltaY() < 0) {
					if (list.getSelectionModel().getSelectedIndex() < list.getItems().size() - 1) {
						list.getSelectionModel().selectNext();
					}
				} else {
					if (0 < list.getSelectionModel().getSelectedIndex()) {
						list.getSelectionModel().selectPrevious();
					}
				}
				list.scrollTo(list.getSelectionModel().getSelectedIndex() - 3);
				e.consume();
			}
		};
		hour.addEventFilter(ScrollEvent.SCROLL, middleScroll);
		minute.addEventFilter(ScrollEvent.SCROLL, middleScroll);
		second.addEventFilter(ScrollEvent.SCROLL, middleScroll);

		Nodes.requestFocusOnHover(hour);
		Nodes.requestFocusOnHover(minute);
		Nodes.requestFocusOnHover(second);

		// 此刻
		now.setOnAction(e -> setValue(Time.now()));

		// ---------- 解析 ----------

		Callback parseDate = () -> {
			LocalDate date = datePicker.getValue();
			if (date == null) {
				return;
			}

			int h = hour.getSelectionModel().getSelectedIndex();
			int m = minute.getSelectionModel().getSelectedIndex();
			int s = second.getSelectionModel().getSelectedIndex();

			value.set(Time.fromLocalDateTime(date.atTime(LocalTime.of(h, m, s))));

			datePicker.getEditor().setText(Time.toDateTime(value.get()));
		};
		datePicker.valueProperty().addListener((obs, o, newDate) -> parseDate.handler());

		Callback parseTime = () -> {
			LocalDate date = datePicker.getValue();
			if (date == null) {
				datePicker.setValue(LocalDate.now());
			}
			parseDate.handler();
		};
		hour.getSelectionModel().selectedIndexProperty().addListener((obs, o, n) -> parseTime.handler());
		minute.getSelectionModel().selectedIndexProperty().addListener((obs, o, n) -> parseTime.handler());
		second.getSelectionModel().selectedIndexProperty().addListener((obs, o, n) -> parseTime.handler());
	}

	/** 清除值 */
	public void clear() {
		hour.getSelectionModel().select(0);
		minute.getSelectionModel().select(0);
		second.getSelectionModel().select(0);
		hour.scrollTo(0);
		minute.scrollTo(0);
		second.scrollTo(0);

		datePicker.setValue(null);
		value.setValue(-1);
	}

	/**
	 * 设置选择时间戳
	 *
	 * @param unixTime 选择时间戳
	 */
	public void setValue(Long unixTime) {
		if (unixTime == null || unixTime < 0) {
			clear();
			return;
		}
		this.value.set(unixTime);

		LocalDate date = Time.toLocalDateTime(unixTime).toLocalDate();
		datePicker.setValue(date);

		int s = (int) ((unixTime - Time.fromLocalDate(date)) / 1000);
		int h = s / 60 / 60;
		hour.getSelectionModel().select(h);
		minute.getSelectionModel().select(s / 60 - h * 60);
		second.getSelectionModel().select(s % 60);

		hour.scrollTo(hour.getSelectionModel().getSelectedIndex() - 3);
		minute.scrollTo(minute.getSelectionModel().getSelectedIndex() - 3);
		second.scrollTo(second.getSelectionModel().getSelectedIndex() - 3);
	}

	/**
	 * 获取选择时间戳
	 *
	 * @return 选择时间戳
	 */
	public Long getValue() {
		return -1 < value.getValue() ? value.getValue() : null;
	}

	/**
	 * 获取选择时间戳监听
	 *
	 * @return 选择时间戳监听
	 */
	public LongProperty valueProperty() {
		return value;
	}
}
