package com.imyeyu.fx.ui.components;

import com.imyeyu.fx.ui.TimiFXUI;
import com.imyeyu.io.IO;
import com.imyeyu.java.bean.CallbackArg;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;

import javax.swing.SwingUtilities;
import java.awt.Image;
import java.awt.Point;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * JavaFX 系统托盘（单例），需要在 FX 线程运行后调用
 * <pre>
 *     TrayFX trayFX = TrayFX.getInstance();
 *     trayFX.getMenu().getItems().addAll(new MenuItem("menu"));
 *     trayFX.show("icon.png");
 * </pre>
 *
 * @author 夜雨
 * @since 2021-10-30 17:27
 */
public final class TrayFX implements TimiFXUI {

	private static final String SORT_KEY = "TIMI_FX_TRAY_SORT_KEY";
	private static final String STYLE_CLASS = "tray-menu";

	private static TrayFX trayFX;

	private final Stage owner;

	/** 菜单寄主窗体 */
	private final Stage stage;

	/** 根节点，修改这个节点的内容可以完全自定义右键菜单内容 */
	@Getter
	private final StackPane root;

	/** 菜单进行修改 */
	@Getter
	private final ContextMenu menu;

	/** 托盘对象 */
	@Getter
	private final SystemTray tray;

	/** 文本提示 */
	private final StringProperty toolTip;

	/** 显示监听 */
	private final BooleanProperty showing;

	/** 图标监听 */
	private final ObjectProperty<Image> icon;

	/** 托盘图标 */
	@Getter
	private TrayIcon trayIcon;

	private final List<CallbackArg<Stage>> showMenuListeners;
	private final List<CallbackArg<MouseEvent>> clickListeners;

	private TrayFX() {
		tray = SystemTray.getSystemTray();

		clickListeners = new ArrayList<>();
		showMenuListeners = new ArrayList<>();
		toolTip = new SimpleStringProperty();
		icon = new SimpleObjectProperty<>();
		showing = new SimpleBooleanProperty(false);

		// 嵌套舞台去除边框的同时不显示在任务栏
		Rectangle2D screen = Screen.getPrimary().getBounds();
		owner = new Stage();
		owner.initStyle(StageStyle.UTILITY);
		owner.setOpacity(0);
		owner.setX(screen.getMaxX() + 10);
		owner.setY(screen.getMaxY() + 10);

		menu = new ContextMenu();
		menu.getStyleClass().add(STYLE_CLASS);

		root = new StackPane();

		Scene scene = new Scene(root);
		TimiFXUI.applyStyle(scene, TrayFX.class.getResource(RESOURCE + "style/components/tray-menu.css").toExternalForm());
		scene.setFill(null);
		stage = new Stage();
		stage.setWidth(1);
		stage.setHeight(1);
		stage.setScene(scene);
		stage.initOwner(owner);
		stage.setResizable(false);
		stage.setAlwaysOnTop(true);
		stage.initStyle(StageStyle.TRANSPARENT);

		// 图标
		icon.addListener((obs, o, img) -> trayIcon.setImage(img));

		// 失焦隐藏
		stage.focusedProperty().addListener((obs, o, isFocused) -> {
			if (!isFocused) {
				stage.hide();
				owner.hide();
			}
		});

		// 提示文本
		toolTip.addListener((obs, o, text) -> {
			if (trayIcon != null) {
				if (text != null && !text.trim().equals("")) {
					trayIcon.setToolTip(text);
				} else {
					trayIcon.setToolTip("");
				}
			}
		});

		// 排序
		menu.setOnShown(e -> menu.getItems().sort((o1, o2) -> {
			Object o1v = o1.getProperties().get(SORT_KEY);
			Object o2v = o2.getProperties().get(SORT_KEY);
			int o1i = o1v == null ? 0 : (int) o1v;
			int o2i = o2v == null ? 0 : (int) o2v;
			return Integer.compare(o1i, o2i);
		}));
	}

	/**
	 * 添加菜单
	 *
	 * @param menu 菜单
	 */
	public void addMenu(MenuItem... menu) {
		this.menu.getItems().addAll(menu);
	}

	/**
	 * 添加菜单
	 *
	 * @param sort 排序位置
	 * @param menu 菜单
	 */
	public void addMenu(int sort, MenuItem... menu) {
		for (MenuItem menuItem : menu) {
			menuItem.getProperties().put(SORT_KEY, sort);
		}
		this.menu.getItems().addAll(menu);
	}

	/**
	 * 显示图标到托盘
	 *
	 * @param path 图标位置
	 */
	public void show(String path) {
		try {
			show(Toolkit.getDefaultToolkit().createImage(IO.resourceToBytes(getClass(), path)));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 显示图标到托盘
	 *
	 * @param icon 图标
	 */
	public void show(Image icon) {
		showing.set(true);
		try {
			trayIcon = new TrayIcon(icon);
			// 点击事件
			trayIcon.addMouseListener(new MouseAdapter() {

				@Override
				public void mouseReleased(MouseEvent e) {
					Platform.runLater(() -> {
						for (CallbackArg<MouseEvent> clickListener : clickListeners) {
							clickListener.handler(e);
						}
						if (SwingUtilities.isRightMouseButton(e)) {
							Point p = e.getLocationOnScreen();
							owner.show();
							stage.setX(p.getX());
							stage.setY(p.getY());
							stage.show();
							stage.setAlwaysOnTop(true);
							stage.requestFocus();
							menu.setX(p.getX());
							menu.setY(p.getY());
							menu.show(stage);
							stage.sizeToScene();
							for (CallbackArg<Stage> showMenuListener : showMenuListeners) {
								showMenuListener.handler(stage);
							}
						}
					});
				}
			});
			tray.add(trayIcon);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 发送系统通知
	 *
	 * @param title   标题
	 * @param content 内容
	 * @param type    类型
	 */
	public void sendMessage(String title, String content, TrayIcon.MessageType type) {
		trayIcon.displayMessage(title, content, type);
	}

	/** 从托盘移除图标（应主动调用，操作系统不会监听程序是否还在运行） */
	public void remove() {
		if (trayIcon != null) {
			tray.remove(trayIcon);
			trayIcon = null;
		}
		showing.set(false);
	}

	/**
	 * 获取单例对象
	 *
	 * @return 单例对象
	 */
	public static synchronized TrayFX getInstance() {
		if (!SystemTray.isSupported()) {
			throw new UnsupportedOperationException("The OS is unsupported tray icon.");
		}
		if (trayFX == null) {
			trayFX = new TrayFX();
		}
		return trayFX;
	}

	/**
	 * 获取提示文本
	 *
	 * @return 提示文本
	 */
	public String getToolTip() {
		return toolTip.get();
	}

	/**
	 * 设置提示文本，需在 {@link #show(Image)} 或 {@link #show(String)} 之后调用才有效
	 *
	 * @param text 文本
	 */
	public void setToolTip(String text) {
		toolTip.set(text);
	}

	/**
	 * 获取提示文本监听
	 *
	 * @return 提示文本监听
	 */
	public StringProperty toolTipProperty() {
		return toolTip;
	}

	/**
	 * 设置图标
	 *
	 * @param path 图标位置
	 */
	public void setIcon(String path) {
		try {
			setIcon(Toolkit.getDefaultToolkit().createImage(IO.resourceToBytes(getClass(), path)));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 设置图标
	 *
	 * @param image AWT 图片
	 */
	public void setIcon(Image image) {
		icon.set(image);
	}

	/**
	 * 获取当前图标
	 *
	 * @return 图标
	 */
	public Image getIcon() {
		return icon.get();
	}

	/**
	 * 获取图标监听
	 *
	 * @return 图标监听
	 */
	public ObjectProperty<Image> iconProperty() {
		return icon;
	}

	/**
	 * 获取是否正在显示托盘图标
	 *
	 * @return true 为正在显示托盘图标
	 */
	public boolean isShowing() {
		return showing.get();
	}

	/**
	 * 获取正在显示托盘图标监听
	 *
	 * @return 正在显示托盘图标监听
	 */
	public ReadOnlyBooleanProperty showingProperty() {
		return showing;
	}

	/**
	 * 添加点击回调
	 *
	 * @param listener 点击监听
	 */
	public void addClickListener(CallbackArg<MouseEvent> listener) {
		clickListeners.add(listener);
	}

	/**
	 * 添加显示菜单回调
	 *
	 * @param listener 点击监听
	 */
	public void addShowMenuListener(CallbackArg<Stage> listener) {
		showMenuListeners.add(listener);
	}
}
