package com.sz.admin.monitor.utils;

import com.sz.admin.monitor.pojo.vo.offset.OffsetVO;
import com.sz.core.util.JsonUtils;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.FloatPointer;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_features2d.BFMatcher;
import org.bytedeco.opencv.opencv_features2d.SIFT;

import java.util.*;

import static org.bytedeco.opencv.global.opencv_calib3d.RANSAC;
import static org.bytedeco.opencv.global.opencv_calib3d.findHomography;
import static org.bytedeco.opencv.global.opencv_core.CV_32FC2;
import static org.bytedeco.opencv.global.opencv_core.NORM_L2;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgproc.COLOR_BGR2GRAY;
import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor;

// 图片向量偏移对比工具类型
public class OffsetCalculator {

    public static Map<String, Object> calculateOffset(String img1Path, String img2Path) {
        Mat srcPts = null;
        Mat dstPts = null;
        Mat H = null;
        Mat mask = null;
        Mat img1 = null;
        Mat img2 = null;
        Mat gray1 = null;
        Mat gray2 = null;
        Mat des1 = null;
        Mat des2 = null;
        try {
            // 加载图片
            img1 = imread(img1Path);
            img2 = imread(img2Path);

            if (img1.empty() || img2.empty()) {
                System.err.println("错误：无法加载图片");
                return null;
            }

            // 转灰度图
            gray1 = new Mat();
            gray2 = new Mat();
            cvtColor(img1, gray1, COLOR_BGR2GRAY);
            cvtColor(img2, gray2, COLOR_BGR2GRAY);

            //  SIFT 特征检测
            SIFT sift = SIFT.create();
            // 记录图片1的每个特征点的坐标
            KeyPointVector kp1 = new KeyPointVector();
            // 记录图片2的每个特征点的坐标
            KeyPointVector kp2 = new KeyPointVector();
            // 存放图片1的描述特征点的容量
            des1 = new Mat();
            // 存放图片2的描述特征点的容量
            des2 = new Mat();
            // 查找灰度图1的特征点，并且找到每个特征点计算出描述保存到des容器中
            sift.detectAndCompute(gray1, new Mat(), kp1, des1);
            // 查找灰度图2的特征点，并且找到每个特征点计算出描述保存到des容器中
            sift.detectAndCompute(gray2, new Mat(), kp2, des2);

            // 特征匹配 (KNN)
            // 暴力匹配器，拿着图1中的特征点去图2中的每个特征点进行比较
            BFMatcher matcher = new BFMatcher(NORM_L2, false);
            DMatchVectorVector matches = new DMatchVectorVector();
            // 对于图1的特征点，去图2中找1到2个最相似的特征点
            matcher.knnMatch(des1, des2, matches, 2);

            // 筛选 Good Matches
            // 先把好的匹配暂存到一个 Java List 里，避免操作复杂的 Vector
            List<DMatch> goodMatchList = new ArrayList<>();
            for (long i = 0; i < matches.size(); i++) {
                DMatchVector currentMatch = matches.get(i);
                if (currentMatch.size() < 2) continue;
                // 最像的特征点
                DMatch m = currentMatch.get(0);
                // 第2像的特征点
                DMatch n = currentMatch.get(1);
                // 最像的特征点的距离，必须小于第2像的特征点距离的 75%
                if (m.distance() < 0.75 * n.distance()) {
                    goodMatchList.add(m);
                }
            }

            // 检查数量
            int count = goodMatchList.size();
            if (count < 4) {
                System.out.println("匹配点不足 (" + count + ")，无法计算偏移");
                return null;
            }

            // 准备纯 Java 数组 (大小 = 点数 * 2，因为有 x 和 y)
            float[] srcData = new float[count * 2];
            float[] dstData = new float[count * 2];

            // 在 Java 内存中填好数据
            for (int i = 0; i < count; i++) {
                DMatch match = goodMatchList.get(i);
                // 获取匹配对在图1中的坐标
                Point2f p1 = kp1.get(match.queryIdx()).pt();
                // 获取匹配对在图2中的坐标
                Point2f p2 = kp2.get(match.trainIdx()).pt();
                // 将二维数组转一维数组
                // 保存x坐标
                srcData[2 * i] = p1.x();
                // 保存y坐标
                srcData[2 * i + 1] = p1.y();
                // 保存x坐标
                dstData[2 * i] = p2.x();
                // 保存y坐标
                dstData[2 * i + 1] = p2.y();
            }

            // 创建一个矩阵对象，固定为1列，count行，每个点位存放32位的浮点数，每个点位存放两个数字
            srcPts = new Mat(count, 1, CV_32FC2);
            dstPts = new Mat(count, 1, CV_32FC2);

            // 将Java内存中的数据保存到C++申请到的内存中
            new FloatPointer(srcPts.data()).put(srcData);
            new FloatPointer(dstPts.data()).put(dstData);

            // 计算单应性矩阵 H
            mask = new Mat();
            H = findHomography(srcPts, dstPts, RANSAC, 5.0, mask, 2000, 0.995);

            if (H.empty()) {
                System.out.println("无法计算单应性矩阵");
                return null;
            }
            // 计算偏移的像素
            double[] hData = new double[9];
            // 把 C++中h矩阵的数据复制回 Java 数组
            new DoublePointer(H.data()).get(hData);
            //水平方向的平移量
            double dx = hData[2]; // 第0行第2列
            // 垂直方向的平移量
            double dy = hData[5]; // 第1行第2列
            String dxStr = String.format("%.2f", dx);
            String dyStr = String.format("%.2f", dy);
            // 如果水平偏移量和垂直偏移量都小于10，那么就是没有没有问题 value=0
            // 如果其中有一个大于10，那么就是有问题 value=1
            int value = (Math.abs(dx) < 10 && Math.abs(dy) < 10) ? 0 : 1;

            Map<String, Object> resultItem = new HashMap<>();
            resultItem.put("type", "tx_yzwpy");
            resultItem.put("value", value);
            resultItem.put("code", "2000");
            resultItem.put("location", new ArrayList<>());
            resultItem.put("confidence", 1);
            OffsetVO offsetVO = new OffsetVO();
            offsetVO.setX(dxStr);
            offsetVO.setY(dyStr);
            offsetVO.setUnit("px");
            resultItem.put("description", JsonUtils.toJsonString(offsetVO));
            return resultItem;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 显式释放 Native 内存
            srcPts.close();
            dstPts.close();
            H.close();
            mask.close();
            img1.close();
            img2.close();
            gray1.close();
            gray2.close();
            des1.close();
            des2.close();
        }
    }
}