package com.example.jmacro.wjdr;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import com.example.jmacro.wjdr.base.ScreenPoint;
import com.example.jmacro.wjdr.base.ScreenRect;
import com.example.jmacro.wjdr.util.ColorUtil;
import com.example.jmacro.wjdr.util.Imager;
import com.example.jmacro.wjdr.util.Logger;
import com.example.jmacro.wjdr.util.TaskUtil;

import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.concurrent.TimeUnit;

/**
 * Java脚本精灵
 *
 * @author wangbing
 * @version 0.0.1
 * @since 1.8
 */
public class JMacro {

    /**
     * 机器人操作实例
     */
    private final Robot robot;

    /**
     * 图例目录
     */
    private File legend;

    public JMacro() throws AWTException {
        // 机器人初始化
        this.robot = new Robot();
        this.robot.setAutoDelay(100);
    }

    public JMacro(File legend) throws AWTException {
        this();
        this.legend = legend;
    }

    /**
     * 键盘按键组
     */
    public void keyInput(int... keycodes) {
        for (int keycode : keycodes) {
            keyPress(keycode);
        }
    }

    /**
     * 键盘按键
     * keycode Key to press (e.g. <code>KeyEvent.VK_A)
     */
    public void keyPress(int keycode) {
        this.robot.keyPress(keycode);
    }

    /**
     * 鼠标移动
     *
     * @param point 坐标点
     */
    public void mouseMove(ScreenPoint point) {
        mouseMove(point, false);
    }

    /**
     * 鼠标移动
     *
     * @param point  坐标点
     * @param smooth 平滑移动
     */
    public void mouseMove(ScreenPoint point, boolean smooth) {
        if (smooth) {
            // 获取当前鼠标位置
            Point mousePoint = MouseInfo.getPointerInfo().getLocation();
            int startX = mousePoint.x;
            int startY = mousePoint.y;

            // 求两点距离
            double absX = Math.abs(startX - point.getX());
            double absY = Math.abs(startY - point.getY());
            double absZ = Math.sqrt(Math.pow(absX, 2) + Math.pow(absY, 2));
            int times = (int) (absZ / 30 + (absZ % 30 > 0 ? 1 : 0));
            int interval = Math.min(500 / times, 10);

            times = Math.min(times, 10);
            // 分times次移动到指定点
            for (int i = 1; i <= times; i++) {
                float d = i * 1.0f / times;
                int dx = (int) (startX + (point.getX() - startX) * d);
                int dy = (int) (startY + (point.getY() - startY) * d);
                robot.mouseMove(dx, dy);
                robot.delay(RandomUtil.randomInt(interval - 10, interval + 10));
            }
        } else {
            robot.mouseMove(point.getX(), point.getY());
        }
    }

    /**
     * 鼠标左键单击
     *
     * @param rect 点
     */
    public void mouseLeftClick(ScreenPoint rect) {
        mouseMove(rect);
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        delayTap();
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    }

    /**
     * 鼠标左键单击
     *
     * @param rect 矩形区域
     */
    public void mouseLeftClick(ScreenRect rect) {
        mouseLeftClick(new ScreenPoint(rect.getCenter()[0], rect.getCenter()[1]));
    }

    /**
     * 鼠标左键拖拽
     *
     * @param start 开始位置
     * @param end   结束位置
     */
    public void mouseLeftDrag(ScreenPoint start, ScreenPoint end) {
        mouseLeftDrag(start, end, false);
    }

    /**
     * 鼠标左键拖拽
     *
     * @param start  开始位置
     * @param end    结束位置
     * @param smooth 平滑移动
     */
    public void mouseLeftDrag(ScreenPoint start, ScreenPoint end, boolean smooth) {
        mouseMove(start, smooth);
        delayTap();
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        delayTap();
        mouseMove(end, smooth);
        delayTap();
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    }

    /**
     * 鼠标右键拖拽
     *
     * @param start 开始位置
     * @param end   结束位置
     */
    public void mouseRightDrag(ScreenPoint start, ScreenPoint end) {
        mouseRightDrag(start, end, false);
    }

    /**
     * 鼠标右键拖拽
     *
     * @param start  开始位置
     * @param end    结束位置
     * @param smooth 平滑移动
     */
    public void mouseRightDrag(ScreenPoint start, ScreenPoint end, boolean smooth) {
        mouseMove(start, smooth);
        delayTap();
        robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
        delayTap();
        mouseMove(end, smooth);
        delayTap();
        robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
    }

    /**
     * 鼠标滚轮单击
     *
     * @param rect 矩形区域
     */
    public void mouseWheelClick(ScreenRect rect) {
        delayTap();
        robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]);
        delayTap();
        robot.mousePress(InputEvent.BUTTON2_DOWN_MASK);
        delayTap();
        robot.mouseRelease(InputEvent.BUTTON2_DOWN_MASK);
    }

    /**
     * 鼠标右键单击
     *
     * @param rect 矩形区域
     */
    public void mouseRightClick(ScreenRect rect) {
        delayTap();
        robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]);
        delayTap();
        robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
        delayTap();
        robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
    }

    /**
     * 鼠标左键双击
     *
     * @param rect 矩形区域
     */
    public void mouseLeftDoubleClick(ScreenRect rect) {
        delayTap();
        robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]);
        delayTap();
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        robot.delay(100);
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
        robot.delay(100);
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        robot.delay(100);
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    }

    /**
     * 捕获指定区域屏幕
     */
    public BufferedImage capture(Robot robot, ScreenRect screenRect) {
        return robot.createScreenCapture(new Rectangle(screenRect.getLeft(), screenRect.getTop(), screenRect.getWidth(), screenRect.getHeight()));
    }

    /**
     * 获取屏幕范围
     */
    public ScreenRect getScreenRect() {
        Toolkit tk = Toolkit.getDefaultToolkit();
        return new ScreenRect(0, 0, tk.getScreenSize().width, tk.getScreenSize().height);
    }

    /**
     * 全屏查找图片
     *
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return findPic(new File(legend, legendName), minSimilar);
    }

    /**
     * 全屏查找图片
     *
     * @param pic        图片
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(File pic, double minSimilar) {
        return findPic(getScreenRect(), Imager.load(pic), minSimilar);
    }

    /**
     * 全屏查找图片
     *
     * @param pic        图片
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(BufferedImage pic, double minSimilar) {
        return findPic(getScreenRect(), pic, minSimilar);
    }

    /**
     * 应用视口查找图例
     *
     * @param screenRect 应用视口
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(ScreenRect screenRect, String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return findPic(screenRect, new File(legend, legendName), minSimilar);
    }


    /**
     * 应用视口查找图例
     *
     * @param screenRect 应用视口
     * @param legend     图例文件
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(ScreenRect screenRect, File legend, double minSimilar) {
        return findPic(screenRect, Imager.load(legend), minSimilar);
    }

    /**
     * 获取定位
     *
     * @param pic        参考图
     * @param screenRect 查找范围
     * @param minSimilar 相似度
     * @return 匹配图片区域
     */
    public ScreenRect findPic(ScreenRect screenRect, BufferedImage pic, double minSimilar) {
        // 当查找区域比图片还小时，直接返回失败
        if (screenRect.getWidth() < pic.getWidth() || screenRect.getHeight() < pic.getHeight()) {
            Logger.error("视口尺寸小于图片");
            return null;
        }

        // 获取实时屏幕
        BufferedImage screen = capture(robot, screenRect);
        int[][] screenData = Imager.getImageRGB(screen);
        int[][] picData = Imager.getImageRGB(pic);

        // 得到图片左上角范围
        int xMin = screenRect.getLeft();
        // 因为坐标取像素点是从0开始，因此需要-1
        int xMax = screenRect.getRight() - pic.getWidth();
        int yMin = screenRect.getTop();
        // 因为坐标取像素点是从0开始，因此需要-1
        int yMax = screenRect.getBottom() - pic.getHeight();

        for (int y = yMin; y <= yMax; y++) {
            for (int x = xMin; x <= xMax; x++) {
                // 对关键点进行先期匹配，降低运算复杂度。如果关键点本身就不匹配，就没必要再去匹配小图的每一个像素点
                // 左上角
                boolean lt = ColorUtil.isSimilar(screenData[x - xMin][y - yMin], picData[0][0]);
                // 右上角
                boolean rt = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() - 1][y - yMin], picData[pic.getWidth() - 1][0]);
                // 左下角
                boolean lb = ColorUtil.isSimilar(screenData[x - xMin][y - yMin + pic.getHeight() - 1], picData[0][pic.getHeight() - 1]);
                // 右下角
                boolean rb = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() - 1][y - yMin + pic.getHeight() - 1], picData[pic.getWidth() - 1][pic.getHeight() - 1]);
                //中心点
                boolean cc = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() / 2][y - yMin + pic.getHeight() / 2], picData[pic.getWidth() / 2][pic.getHeight() / 2]);
                if (lt && rt && lb && rb && cc) {
                    // 进行全像素匹配
                    double similar = Imager.calcSimilar(x - xMin, y - yMin, pic.getHeight(), pic.getWidth(), screenData, picData);
                    if (similar >= minSimilar) {
                        return new ScreenRect(x, y, x + pic.getWidth(), y + pic.getHeight(), similar);
                    }
                }
            }
        }
//        try {
//            ImgUtil.write(screen,"png", new FileOutputStream("D://1.png"));
//        } catch (FileNotFoundException e) {
//
//        }
        return null;
    }

    public void delay() {
        delayNormal();
    }


    public void delayTap() {
        int i = RandomUtil.randomInt(100, 500);
        robot.delay(i);
    }

    public void delayNormal() {
        int i = RandomUtil.randomInt(500, 1500);
        robot.delay(i);
    }

    public void delayLong() {
        int i = RandomUtil.randomInt(1500, 3000);
        robot.delay(i);
    }

    /**
     * 等待并查找图片
     *
     * @param file       图例
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect waitAndFindPic(File file, double minSimilar) {
        return waitAndFindPic(getScreenRect(), file, minSimilar, 10, TimeUnit.SECONDS);
    }

    /**
     * 等待并查找图片
     *
     * @param rect       查找区域
     * @param file       图例
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect waitAndFindPic(ScreenRect rect, File file, double minSimilar) {
        return waitAndFindPic(rect, file, minSimilar, 10, TimeUnit.SECONDS);
    }

    /**
     * 等待并查找图片
     *
     * @param rect       查找区域
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect waitAndFindPic(ScreenRect rect, String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return waitAndFindPic(rect, new File(legend, legendName), minSimilar, 10, TimeUnit.SECONDS);
    }

    /**
     * 等待并查找图片
     *
     * @param file       图例
     * @param minSimilar 最低相似度
     * @param time       最长等待时间
     * @param unit       最长等待时间单位
     * @return 匹配图片区域
     */
    public ScreenRect waitAndFindPic(File file, double minSimilar, long time, TimeUnit unit) {
        return waitAndFindPic(getScreenRect(), file, minSimilar, time, unit);
    }

    /**
     * 等待并查找图片
     *
     * @param rect       查找区域
     * @param file       图例
     * @param minSimilar 最低相似度
     * @param time       最长等待时间
     * @param unit       最长等待时间单位
     * @return 匹配图片区域
     */
    public ScreenRect waitAndFindPic(ScreenRect rect, File file, double minSimilar, long time, TimeUnit unit) {
        return TaskUtil.timeTask(() -> {
            BufferedImage image = Imager.load(file);
            if (rect.getWidth() < image.getWidth()) {
                Logger.error("查找图片区域宽度{}小于图片宽度{}", rect.getWidth(), image.getWidth());
                return null;
            }
            if (rect.getHeight() < image.getHeight()) {
                Logger.error("查找图片区域宽度{}小于图片宽度{}", rect.getHeight(), image.getHeight());
                return null;
            }

            while (true) {
                delayTap();
                ScreenRect pic = findPic(rect, image, minSimilar);
                if (pic != null) {
                    return pic;
                }
            }
        }, time, unit);
    }

    /**
     * 等待并匹配图例
     *
     * @param rect       参照区域
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect waitAndMatchPic(ScreenRect rect, String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return waitAndMatchPic(rect, new File(legend, legendName), minSimilar, 10, TimeUnit.SECONDS);
    }

    /**
     * 等待并匹配图例
     *
     * @param rect       参照区域
     * @param file       图例
     * @param minSimilar 最低相似度
     * @return 匹配图片区域
     */
    public ScreenRect waitAndMatchPic(ScreenRect rect, File file, double minSimilar) {
        return waitAndMatchPic(rect, file, minSimilar, 10, TimeUnit.SECONDS);
    }

    /**
     * 等待并匹配图例
     *
     * @param rect       参照区域
     * @param file       图例
     * @param minSimilar 最低相似度
     * @param time       最长等待时间
     * @param unit       最长等待时间单位
     * @return 匹配图片区域
     */
    public ScreenRect waitAndMatchPic(ScreenRect rect, File file, double minSimilar, long time, TimeUnit unit) {
        return TaskUtil.timeTask(() -> {
            while (true) {
                delayTap();
                ScreenRect matchPic = matchPic(rect, file, minSimilar);
                if (matchPic != null) {
                    return matchPic;
                }
            }
        }, time, unit);
    }

    /**
     * 匹配图片
     *
     * @param rect       程序窗口（参照系）
     * @param file       图例
     * @param minSimilar 最低相似度
     * @return 匹配图片
     */
    public ScreenRect matchPic(ScreenRect rect, File file, double minSimilar) {
        if (!file.exists()) {
            Logger.error("file [{}] not exist", file.getAbsolutePath());
            return null;
        }
        String name = file.getName();
        int offsetX = 0;
        int offsetY = 0;
        if (name.matches("[\\S\\s]+_L[0-9]+,[0-9]+\\.png")) {
            offsetX = Convert.toInt(ReUtil.get("[\\S\\s]+_L([0-9]+),[0-9]+\\.png", name, 1), 0);
            offsetY = Convert.toInt(ReUtil.get("[\\S\\s]+_L[0-9]+,([0-9]+)\\.png", name, 1), 0);
        }
        ScreenRect screenRect = new ScreenRect();
        screenRect.setLeft(rect.getLeft() + offsetX);
        screenRect.setTop(rect.getTop() + offsetY);
        BufferedImage image = Imager.load(file);
        screenRect.setRight(screenRect.getLeft() + image.getWidth());
        screenRect.setBottom(screenRect.getTop() + image.getHeight());
        return findPic(screenRect, image, minSimilar);
    }

    /**
     * 匹配图片
     *
     * @param rect       程序窗口（参照系）
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片
     */
    public ScreenRect matchPic(ScreenRect rect, String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return matchPic(rect, new File(legend, legendName), minSimilar);
    }

    /**
     * 是否匹配图片
     *
     * @param rect       程序窗口（参照系）
     * @param file       图例
     * @param minSimilar 最低相似度
     * @return 匹配图片
     */
    public boolean isMatchPic(ScreenRect rect, File file, double minSimilar) {
        return matchPic(rect, file, minSimilar) != null;
    }

    /**
     * 是否匹配图片
     *
     * @param rect       程序窗口（参照系）
     * @param legendName 图例名称
     * @param minSimilar 最低相似度
     * @return 匹配图片
     */
    public boolean isMatchPic(ScreenRect rect, String legendName, double minSimilar) {
        if (!legendName.endsWith(".png")) {
            legendName = legendName + ".png";
        }
        return matchPic(rect, new File(legend, legendName), minSimilar) != null;
    }
}
