package com.imyeyu.fx.ui.components;

import com.imyeyu.fx.icon.TimiFXIcon;
import com.imyeyu.fx.icon.TimiIcon;
import com.imyeyu.fx.ui.TimiFXUI;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 分页组件，支持省略页，滚动页，如 [&lt;][1]..[5][6][7][8][9]..[20][&gt;]
 * <pre>
 *     Pagination pagination = new Pagination();
 *     pagination.setSize(10);    // 单页数据量
 *     pagination.setLength(200); // 总数据量
 *     pagination.indexProperty((obs, o, newIndex) -> {
 *        // 监听激活下标
 *     });
 *     pagination.setIndex(6); // 激活页下标（0 开始，这是第 7 页）
 * </pre>
 *
 * @author 夜雨
 * @since 2021-12-22 15:25
 */
public class Pagination extends HBox implements TimiFXUI {

	private static final String STYLE_CLASS = "x-pagination";

	/** 阻止取消选择 */
	public static final EventHandler<MouseEvent> EVENT_TOGGLE_BUTTON = e -> {
		if (e.getSource() instanceof ToggleButton btn && btn.isSelected()) {
			e.consume();
		}
	};

	private final IconButton prev, next;

	// 步进翻页动态下标
	private int prevI, nextI;

	private final LongProperty lp;    // lengthProperty 总数据量 [0, N]
	private final IntegerProperty ip; // indexProperty  激活下标 [0, chunkProperty.value]
	private final IntegerProperty sp; // sizeProperty   单页数量 [1, N]
	private final IntegerProperty cp; // chunkProperty  页面数量 [1, N]

	/** 默认构造 */
	public Pagination() {
		// 基本参数
		lp = new SimpleLongProperty();
		ip = new SimpleIntegerProperty();
		sp = new SimpleIntegerProperty();
		cp = new SimpleIntegerProperty();

		// 更新页面大小和总数据量时重新计算页面数量
		cp.bind(Bindings.createIntegerBinding(() -> {
			long total = lp.get();
			int page = sp.get();
			return (int) Math.ceil(1D * total / page);
		}, sp, lp));

		// 页面组
		List<PageButton> pageButtons = new ArrayList<>();
		PageButton tb;

		// 上一页
		prev = new IconButton(TimiFXIcon.get(TimiIcon.ARROW_1_W)).withBackground();
		prev.setBorder(Border.EMPTY);
		prev.setMaxHeight(Double.MAX_VALUE);
		prev.disableProperty().bind(ip.isEqualTo(0));
		getChildren().add(prev);

		// 前置页 [1, 6]
		for (int i = 0; i < 6; i++) {
			tb = new PageButton();
			tb.indexProperty.set(i);
			if (0 < i) {

				// 第一页保持显示，非第一页显示条件：
				// 1. 总页数小于 8，页码大于 i
				// 2. 总页数大于等于 8，激活下标小于 4（1 - 4 页）
				tb.visibleProperty().bind(cp.greaterThan(i).and(cp.lessThan(8)).or(cp.greaterThan(7).and(ip.lessThan(4))));
			} else {
				tb.setBorder(Stroke.V);
			}
			pageButtons.add(tb);
			getChildren().add(tb);
		}
		{
			// 左省略，总页数大于 7（有中间页），激活下标大于 3 时显示（第五页）
			Label leftEllipsis = new Label("..");
			leftEllipsis.setAlignment(Pos.CENTER);
			leftEllipsis.setPrefWidth(32);
			leftEllipsis.setBorder(Stroke.RIGHT);
			leftEllipsis.setMaxHeight(Double.MAX_VALUE);
			leftEllipsis.visibleProperty().bind(cp.greaterThan(7).and(ip.greaterThan(3)));
			leftEllipsis.managedProperty().bind(leftEllipsis.visibleProperty());
			getChildren().add(leftEllipsis);

			// 中间页，显示条件：总页数大于 8 ，激活下标大于 3 且小于总页数 - 4（小于 3 时前置页处理，大于总页数 - 4 时后置页处理）
			for (int i = 0; i < 5; i++) {
				tb = new PageButton();
				tb.indexProperty.bind(ip.add(i - 2)); // 动态数值
				tb.visibleProperty().bind(cp.greaterThan(8).and(ip.greaterThan(3).and(ip.lessThan(cp.subtract(4)))));

				pageButtons.add(tb);
				getChildren().add(tb);
			}

			// 右省略，显示条件：总页数大于 7，激活下标小于总页数 - 4（大于总页数 - 4 时后置页处理，不需要省略）
			Label rightEllipsis = new Label("..");
			rightEllipsis.setAlignment(Pos.CENTER);
			rightEllipsis.setPrefWidth(32);
			rightEllipsis.setMaxHeight(Double.MAX_VALUE);
			rightEllipsis.setBorder(Stroke.RIGHT);
			rightEllipsis.visibleProperty().bind(cp.greaterThan(7).and(ip.lessThan(cp.subtract(4))));
			rightEllipsis.managedProperty().bind(rightEllipsis.visibleProperty());
			getChildren().add(rightEllipsis);
		}

		// 后置页
		for (int i = 0; i < 6; i++) {
			tb = new PageButton();
			tb.indexProperty.bind(cp.add(i - 6)); // 动态数值
			if (i == 5) {
				// 页数达到 7 时最后一页始终显示
				tb.visibleProperty().bind(cp.greaterThan(6));
			} else {
				// 其他页显示条件：总页数大于 7，激活下标大于总页数 - 5（倒数第四页）
				tb.visibleProperty().bind(cp.greaterThan(7).and(ip.greaterThan(cp.subtract(5))));
			}
			pageButtons.add(tb);
			getChildren().add(tb);
		}

		// 下一页
		next = new IconButton(TimiFXIcon.get(TimiIcon.ARROW_1_E)).withBackground();
		next.setBorder(Border.EMPTY);
		next.setMaxHeight(Double.MAX_VALUE);
		next.disableProperty().bind(ip.isEqualTo(cp.subtract(1)).or(cp.isEqualTo(0)));

		getStyleClass().add(STYLE_CLASS);
		setBorder(Stroke.DEFAULT);
		setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
		setAlignment(Pos.BOTTOM_CENTER);
		getChildren().add(next);

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

		new ToggleGroup().getToggles().addAll(pageButtons);
		for (PageButton button : pageButtons) {
			// 阻止取消选择
			button.addEventFilter(MouseEvent.MOUSE_PRESSED, EVENT_TOGGLE_BUTTON);
		}

		// 数据变动更新
		ChangeListener<Number> paramsListener = (obs, o, n) -> {
			// 步进翻页
			prevI = ip.get() - 1;
			nextI = ip.get() + 1;
			// 重置激活页
			if (cp.get() - 1 < ip.get()) {
				ip.set(0);
			}
			// 主动选中
			for (PageButton pageButton : pageButtons) {
				// 分页存在预设页码，只作触发事件用（如前置页的第五第六页），需要主动计算激活的按钮
				if (ip.get() == pageButton.indexProperty.get() && pageButton.isVisible()) {
					pageButton.setSelected(true);
					return;
				}
			}
		};
		sp.addListener(paramsListener);
		ip.addListener(paramsListener);
		lp.addListener(paramsListener);

		// 步进翻页
		prev.setOnAction(e -> ip.set(prevI));
		next.setOnAction(e -> ip.set(nextI));
	}

	@Override
	public String getUserAgentStylesheet() {
		return Objects.requireNonNull(Pagination.class.getResource(RESOURCE + "style/components/pagination.css")).toExternalForm();
	}

	/** 选择上一页 */
	public void prev() {
		prev.fire();
	}

	/** 选择下一页 */
	public void next() {
		next.fire();
	}

	/**
	 * 设置激活页下标，取值范围 [0, {@link #getChunk()}]
	 *
	 * @param index 激活页下标
	 */
	public void setIndex(int index) {
		ip.set(index);
	}

	/**
	 * 获取当前激活页下标
	 *
	 * @return 当前激活页下标
	 */
	public int getIndex() {
		return ip.get();
	}

	/**
	 * 获取激活页监听
	 *
	 * @return 激活页监听
	 */
	public IntegerProperty indexProperty() {
		return ip;
	}

	/**
	 * 获取当前页面数量
	 *
	 * @return 当前页面数量
	 */
	public int getChunk() {
		return cp.get();
	}

	/**
	 * 获取页面数量监听
	 *
	 * @return 页面数量监听
	 */
	public ReadOnlyIntegerProperty chunkProperty() {
		return cp;
	}

	/**
	 * 设置单页数量，取值范围 [1, Integer.MAX_VALUE]
	 *
	 * @param size 单页数量
	 */
	public void setSize(int size) {
		if (size < 1) {
			throw new IllegalArgumentException("page size range of [1, Integer.MAX_VALUE]");
		}
		sp.set(size);
	}

	/**
	 * 当前单页数量
	 *
	 * @return 单页数量
	 */
	public int getSize() {
		return sp.get();
	}

	/**
	 * 获取单页数量监听
	 *
	 * @return 单页数量监听
	 */
	public IntegerProperty sizeProperty() {
		return sp;
	}

	/**
	 * 设置总数据量，取值范围 [0, Long.MAX_VALUE]
	 *
	 * @param length 总数据量
	 */
	public void setLength(long length) {
		if (length < 0) {
			throw new IllegalArgumentException("length range of [0, Long.MAX_VALUE]");
		}
		lp.set(length);
	}

	/**
	 * 获取当前总数据量
	 *
	 * @return 当前总数据量
	 */
	public long getLength() {
		return lp.get();
	}

	/**
	 * 获取总数据大小监听
	 *
	 * @return 总数据大小监听
	 */
	public LongProperty lengthProperty() {
		return lp;
	}

	/**
	 * 页面按钮，分页组件内部调度
	 *
	 * @author 夜雨
	 * @since 2022-01-27 01:42
	 */
	private class PageButton extends ToggleButton implements TimiFXUI {

		/** 当前页码 */
		final IntegerProperty indexProperty;

		public PageButton() {
			indexProperty = new SimpleIntegerProperty();

			getStyleClass().add(CSS.BG_BUTTON);
			setBorder(Stroke.RIGHT);
			// 页码即显示内容
			textProperty().bind(indexProperty.add(1).asString());
			managedProperty().bind(visibleProperty());
			// 选中更新激活下标
			selectedProperty().addListener((obs, o, isSelected) -> {
				if (isSelected) {
					Pagination.this.ip.set(indexProperty.get());
				}
			});
		}

		@Override
		public String getUserAgentStylesheet() {
			return Objects.requireNonNull(Pagination.class.getResource(RESOURCE + "style/components/pagination.css")).toExternalForm();
		}
	}
}
