package com.zhangmeng.file.utils;


import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;

import com.zhangmeng.file.dto.ImageInfo;
import com.zhangmeng.file.dto.VideoInfo;
import com.zhangmeng.model.base.baseUtil.BaiduApiUtil;
import com.zhangmeng.model.base.baseUtil.CommonUtil;
import com.zhangmeng.model.bean.SpringBean;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncoderException;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.opencv.core.Point;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Base64Utils;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import com.zhangmeng.model.dto.baidu.LatAndLngToAddress;



/**
 * 视频处理
 */
public class FileUtil {

    private static Logger logger = LoggerFactory.getLogger(FileUtil.class);

    @Value("${api.baidu-api.ak}")
    private final static String ak = null;

    public enum UpLoadType {

        CHATTING_IMAGE("聊天图片上传"),
        CHATTING_AUDIO("聊天音频上传"),
        CHATTING_VIDEO("聊天视频上传");

        private String description;//描述

        UpLoadType(String description) {
            this.description = description;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }
    }

    public String uploadPath(UpLoadType upLoadType) {
        String path = null;
        switch (upLoadType) {
            case CHATTING_IMAGE:
                break;
            case CHATTING_AUDIO:
                break;
            case CHATTING_VIDEO:
                break;
        }
        return path;
    }

    public enum Type {

        Images("图片"),
        Audio("音频"),
        Video("视频"),
        File("文件");

        private String description;//描述

        Type(String description) {
            this.description = description;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }
    }

    private static byte[] frame(File targetFile) {
        byte[] content = null;
        try {
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(targetFile);
            ff.start();
            int ftp = ff.getLengthInFrames();
            int flag = 0;
            Frame frame = null;
            while (flag <= ftp) {
                //获取帧
                frame = ff.grabImage();
                //过滤前3帧，避免出现全黑图片
                if ((flag > 3) && (frame != null)) {
                    break;
                }
                flag++;
            }

            RenderedImage renderedImage = frameToBufferedImage(frame);

            //视频旋转度
            String rotate = ff.getVideoMetadata("rotate");
            if (rotate != null) {
                // 旋转图片
                renderedImage = rotate((BufferedImage) renderedImage, Integer.parseInt(rotate));
            }

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(renderedImage, "jpg", os);

            ff.close();
            ff.stop();

            content = os.toByteArray();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }

    private static RenderedImage frameToBufferedImage(Frame frame) {
        //创建BufferedImage对象
        Java2DFrameConverter converter = new Java2DFrameConverter();
        return converter.getBufferedImage(frame);
    }


    public static BufferedImage rotate(BufferedImage src, int angel) {
        int src_width = src.getWidth(null);
        int src_height = src.getHeight(null);
        int type = src.getColorModel().getTransparency();
        Rectangle rect_des = calcRotatedSize(new Rectangle(new Dimension(src_width, src_height)), angel);
        BufferedImage bi = new BufferedImage(rect_des.width, rect_des.height, type);
        Graphics2D g2 = bi.createGraphics();
        g2.translate((rect_des.width - src_width) / 2, (rect_des.height - src_height) / 2);
        g2.rotate(Math.toRadians(angel), src_width / 2, src_height / 2);
        g2.drawImage(src, 0, 0, null);
        g2.dispose();
        return bi;
    }

    private static Rectangle calcRotatedSize(Rectangle src, int angel) {
        if (angel >= 90) {
            if (angel / 90 % 2 == 1) {
                int temp = src.height;
                src.height = src.width;
                src.width = temp;
            }
            angel = angel % 90;
        }
        double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
        double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
        double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
        double angel_dalta_width = Math.atan((double) src.height / src.width);
        double angel_dalta_height = Math.atan((double) src.width / src.height);
        int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));
        int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));
        int des_width = src.width + len_dalta_width * 2;
        int des_height = src.height + len_dalta_height * 2;
        return new Rectangle(new Dimension(des_width, des_height));
    }

    public static String duration(Long ms) {
        String duration = null;

        int ss = 1000;
        int mi = ss * 60;
        int hh = mi * 60;
        int dd = hh * 24;

        long day = ms / dd;
        long hour = (ms - day * dd) / hh;
        long minute = (ms - day * dd - hour * hh) / mi;
        long second = (ms - day * dd - hour * hh - minute * mi) / ss;

        String strHour = hour < 10 ? "0" + hour : "" + hour;//小时
        String strMinute = minute < 10 ? "0" + minute : "" + minute;//分钟
        String strSecond = second < 10 ? "0" + second : "" + second;//秒

        if (strHour.equals("00")) {
            duration = strMinute + ":" + strSecond;
        } else {
            duration = strHour + ":" + strMinute + ":" + strSecond;
        }

        return duration;
    }


    /**
     * 图片信息获取Metadata元数据信息
     *
     * @param file 需要解析的文件
     * @return ImageInfo
     */
    public static ImageInfo parseImgInfo(MultipartFile file) {
        ImageInfo imgInfoBean = null;
        try {
            Metadata Metadata = ImageMetadataReader.readMetadata(file.getInputStream());
            imgInfoBean = printImageTags(file, Metadata);
        } catch (ImageProcessingException | IOException e) {
            e.printStackTrace();
        }
        return imgInfoBean;
    }

    /**
     * 读取Metadata里面的信息
     *
     * @param sourceFile 源文件
     * @param Metadata   Metadata元数据信息
     * @return ImageInfo
     */
    private static ImageInfo printImageTags(MultipartFile sourceFile, Metadata Metadata) {
        ImageInfo imgInfoBean = new ImageInfo();
        imgInfoBean.setImgSize(sourceFile.getSize());
        imgInfoBean.setImgName(sourceFile.getName());
        for (Directory directory : Metadata.getDirectories()) {
            for (Tag tag : directory.getTags()) {
                String tagName = tag.getTagName();
                String desc = tag.getDescription();
                switch (tagName) {
                    case "Compression Type":
                        break;
                    case "Data Precision":
                        imgInfoBean.setDataPrecision(desc);
                        break;
                    case "Image Height":
                        //图片高度
                        imgInfoBean.setImgHeight(desc);
                        break;
                    case "Image Width":
                        //图片宽度
                        imgInfoBean.setImgWidth(desc);
                        break;
                    case "Date/Time Original":
                        //拍摄时间
                        imgInfoBean.setDateTime(desc);
                        break;
                    case "GPS Altitude":
                        //海拔
                        imgInfoBean.setAltitude(desc);
                        break;
                    case "GPS Latitude":
                        //纬度
                        imgInfoBean.setLatitude(pointToLatlong(desc));
                        break;
                    case "GPS Longitude":
                        //经度
                        imgInfoBean.setLongitude(pointToLatlong(desc));
                        break;
                    case "Detected MIME Type":
                        imgInfoBean.setDetectedMimeType(desc);
                        break;
                    case "Expected File Name Extension":
                        imgInfoBean.setExpectedFileNameExtension(desc);
                        break;
                }
            }
            for (String error : directory.getErrors()) {
                System.err.println("ERROR: " + error);
            }
        }
        if (imgInfoBean.getLatitude()!=null && imgInfoBean.getLongitude() != null) {
            BaiduApiUtil baiduApiUtil = SpringBean.getBean(BaiduApiUtil.class);
            if (baiduApiUtil != null){
                LatAndLngToAddress latAndLngToAddress = baiduApiUtil.lat_lng_to_address(imgInfoBean.getLatitude(), imgInfoBean.getLongitude());
                String formatted_address = latAndLngToAddress.getResult().getFormatted_address();
                imgInfoBean.setAddress(formatted_address);
            }
        }
        return imgInfoBean;
    }

    /**
     * 经纬度转换  度分秒转换
     *
     * @param point 坐标点
     * @return String
     */
    private static String pointToLatlong(String point) {
        double du = Double.parseDouble(point.substring(0, point.indexOf("°")).trim());
        double fen = Double.parseDouble(point.substring(point.indexOf("°") + 1, point.indexOf("'")).trim());
        double miao = Double.parseDouble(point.substring(point.indexOf("'") + 1, point.indexOf("\"")).trim());
        double duStr = du + fen / 60 + miao / 60 / 60;
        return Double.toString(duStr);
    }

    public static String getPrintSize(long size) {
        // 如果字节数少于1024，则直接以B为单位，否则先除于1024，后3位因太少无意义
        if (size < 1024) {
            return String.valueOf(size) + "B";
        } else {
            size = size / 1024;
        }
        // 如果原字节数除于1024之后，少于1024，则可以直接以KB作为单位
        // 因为还没有到达要使用另一个单位的时候
        // 接下去以此类推
        if (size < 1024) {
            return String.valueOf(size) + "KB";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            // 因为如果以MB为单位的话，要保留最后1位小数，
            // 因此，把此数乘以100之后再取余
            size = size * 100;
            return String.valueOf((size / 100)) + "." + String.valueOf((size % 100)) + "MB";
        } else {
            // 否则如果要以GB为单位的，先除于1024再作同样的处理
            size = size * 100 / 1024;
            return String.valueOf((size / 100)) + "." + String.valueOf((size % 100)) + "GB";
        }
    }



    public static String correct(String base64) {
        try {
            Mat src = base642Mat(base64);
            Mat correctMat = correct(src, null);
            return catToBase64(correctMat);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("图片扶正失败", e);
            return base64;
        }
    }

    /**
     * 装换回编码
     *
     * @param correctMat
     * @return
     */
    public static String catToBase64(Mat correctMat) {
        return bufferToBase64(toByteArray(correctMat));
    }

    /**
     * 转换成base64编码
     *
     * @param buffer
     * @return
     */
    public static String bufferToBase64(byte[] buffer) {
        return Base64Utils.encodeToString(buffer);
    }

    /**
     * mat转换成bufferedImage
     *
     * @param matrix
     * @return
     */
    public static byte[] toByteArray(Mat matrix) {
        MatOfByte mob = new MatOfByte();
        Imgcodecs.imencode(".jpg", matrix, mob);
        return mob.toArray();
    }

    /**
     * 作用：保存图像
     *
     * @param src      Mat矩阵图像
     * @param filePath 要保存图像的路径及名字
     * @return
     */
    public static boolean saveImg(Mat src, String filePath) {
        return Imgcodecs.imwrite(filePath, src);
    }

    /**
     * 矫正图像
     *
     * @param src
     * @return
     */
    public static Mat correct(Mat src, String cannyPath) {
        Mat mat = src.clone();
        // Canny 描边
        Imgproc.GaussianBlur(mat, mat, new Size(3, 3), 0);
        // 灰度化处理
        mat = gray(mat);
        // 描边
        Mat cannyMat = canny(mat);
        if (cannyPath != null) {
            saveImg(cannyMat, cannyPath);
        }
        // 获取最大矩形
        RotatedRect rect = findMaxRect(cannyMat);
        paintRect(cannyMat, rect.boundingRect(), new Scalar(255, 0, 0));
        // 旋转矩形
        Mat CorrectImg = rotation(cannyMat, rect);
        Mat NativeCorrectImg = rotation(src, rect);;
        // 裁剪矩形
        Mat dst = cutRect(CorrectImg, NativeCorrectImg);
        return dst;
    }

    /**
     * 画矩形
     *
     * @param src
     * @param r
     * @param scalar
     * @return
     */
    public static Mat paintRect(Mat src, Rect r, Scalar scalar) {
        Point pt1 = new Point(r.x, r.y);
        Point pt2 = new Point(r.x + r.width, r.y);
        Point pt3 = new Point(r.x + r.width, r.y + r.height);
        Point pt4 = new Point(r.x, r.y + r.height);

        Imgproc.line(src, pt1, pt2, scalar, 5);
        Imgproc.line(src, pt2, pt3, scalar, 5);
        Imgproc.line(src, pt3, pt4, scalar, 5);
        Imgproc.line(src, pt4, pt1, scalar, 5);

        return src;
    }

    /**
     * 把矫正后的图像切割出来
     *
     * @param correctMat 图像矫正后的Mat矩阵
     */
    public static Mat cutRect(Mat correctMat, Mat nativeCorrectMat) {
        // 获取最大矩形
        RotatedRect rect = findMaxRect(correctMat);

        Point[] rectPoint = new Point[4];
        rect.points(rectPoint);

        int[] roi = cutRectHelp(rectPoint);
        System.out.println(String.format("%d,%d,%d,%d", roi[0], roi[1], roi[2], roi[3]));

        Mat temp = new Mat(nativeCorrectMat, new Rect(roi[0], roi[1], roi[2], roi[3]));

        Mat t = new Mat();
        temp.copyTo(t);
        return t;
    }

    /**
     * 返回边缘检测之后的最大矩形
     *
     * @param cannyMat Canny之后的mat矩阵
     * @return
     */
    public static RotatedRect findMaxRect(Mat cannyMat) {
        MatOfPoint maxContour = findMaxContour(cannyMat);
        MatOfPoint2f matOfPoint2f = new MatOfPoint2f(maxContour.toArray());
        RotatedRect rect = Imgproc.minAreaRect(matOfPoint2f);
        return rect;
    }

    /**
     * 作用：返回边缘检测之后的最大轮廓
     *
     * @param cannyMat Canny之后的Mat矩阵
     * @return
     */
    public static MatOfPoint findMaxContour(Mat cannyMat) {
        List<MatOfPoint> contours = findContours(cannyMat);
        return contours.get(contours.size() - 1);
    }

    /**
     * 寻找轮廓，并按照递增排序
     *
     * @param cannyMat
     * @return
     */
    public static List<MatOfPoint> findContours(Mat cannyMat) {
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();

        // 寻找轮廓
        Imgproc.findContours(cannyMat, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE,
                new Point(0, 0));

        if (contours.size() <= 0) {
            throw new RuntimeException("未找到图像轮廓");
        } else {
            // 对contours进行了排序，按递增顺序
            contours.sort((o1, o2) -> {
                MatOfPoint2f mat1 = new MatOfPoint2f(o1.toArray());
                RotatedRect rect1 = Imgproc.minAreaRect(mat1);
                Rect r1 = rect1.boundingRect();

                MatOfPoint2f mat2 = new MatOfPoint2f(o2.toArray());
                RotatedRect rect2 = Imgproc.minAreaRect(mat2);
                Rect r2 = rect2.boundingRect();
                return (int) (r1.area() - r2.area());
            });
            return contours;
        }
    }

    /**
     * 把矫正后的图像切割出来--辅助函数(修复)
     *
     * @param rectPoint 矩形的四个点
     * @return int[startLeft , startUp , width , height]
     */
    public static int[] cutRectHelp(Point[] rectPoint) {
        double minX = rectPoint[0].x;
        double maxX = rectPoint[0].x;
        double minY = rectPoint[0].y;
        double maxY = rectPoint[0].y;
        for (int i = 1; i < rectPoint.length; i++) {
            minX = rectPoint[i].x < minX ? rectPoint[i].x : minX;
            maxX = rectPoint[i].x > maxX ? rectPoint[i].x : maxX;
            minY = rectPoint[i].y < minY ? rectPoint[i].y : minY;
            maxY = rectPoint[i].y > maxY ? rectPoint[i].y : maxY;
        }
        int[] roi = {(int) Math.abs(minX), (int) Math.abs(minY), (int) Math.abs(maxX - minX),
                (int) Math.abs(maxY - minY)};
        return roi;
    }

    /**
     * 旋转矩形
     *
     * @param cannyMat mat矩阵
     * @param rect     矩形
     * @return
     */

    public static Mat rotation(Mat cannyMat, RotatedRect rect) {
        // 获取矩形的四个顶点
        Point[] rectPoint = new Point[4];
        rect.points(rectPoint);

//        int a = 0;

//        if (cannyMat.height() > cannyMat.width()) {
//            a = 90;
//        }

        double angle = rect.angle; //+ 90 + a;
        if (angle > -90 && angle < -45) {
            angle = angle + 90;
        } else if (angle > 45 && angle < 90) {
            angle = angle - 90;
        }

        Point center = rect.center;

        Mat CorrectImg = new Mat(cannyMat.size(), cannyMat.type());

        cannyMat.copyTo(CorrectImg);

        // 得到旋转矩阵算子
        Mat matrix = Imgproc.getRotationMatrix2D(center, angle, 1);

        Imgproc.warpAffine(CorrectImg, CorrectImg, matrix, CorrectImg.size(), 1, 0, new Scalar(0, 0, 0));

        return CorrectImg;
    }

    /**
     * canny算法，边缘检测
     *
     * @param src
     * @return
     */
    public static Mat canny(Mat src) {
        Mat mat = src.clone();
        // 高斯滤镜
//		Imgproc.GaussianBlur(src, mat, new Size(3, 3), 0);
        // Canny边缘检测
//		Imgproc.Canny(src, mat, 40, 80);
        // 膨胀，连接边缘
//		Imgproc.dilate(src, mat, new Mat(), new Point(-1,-1), 3, 1, new Scalar(1));
        // Canny边缘检测
        Imgproc.Canny(mat, mat, 50, 50 * 3);

        // 膨胀，连接边缘
        Imgproc.dilate(mat, mat, new Mat(), new Point(-1, -1), 3, 1, new Scalar(1));
//		Imgproc.Canny(src, mat, 60, 200);
        return mat;
    }

    /**
     * 作用：灰度话
     *
     * @param src Mat矩阵图像
     * @return
     */
    public static Mat gray(Mat src) {
        Mat gray = new Mat();
        if (src.channels() == 3) {
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            src = gray;
        } else {
            System.out.println("The Image File Is Not The RGB File!");
        }
        return src;
    }

    /**
     * base64转Mat
     *
     * @param base64
     * @return
     * @throws IOException
     */
    public static Mat base642Mat(String base64) throws IOException {
        return bufImg2Mat(base64ToBufferedImage(base64), BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);
    }

    /**
     * base64 编码转换为 BufferedImage
     *
     * @param base64
     * @return
     */
    public static BufferedImage base64ToBufferedImage(String base64) {
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            byte[] bytes1 = decoder.decodeBuffer(base64);
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);
            return ImageIO.read(bais);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * BufferedImage转换成Mat
     *
     * @param original 要转换的BufferedImage
     * @param imgType  bufferedImage的类型 如 BufferedImage.TYPE_3BYTE_BGR
     * @param matType  转换成mat的type 如 CvType.CV_8UC3
     */
    public static Mat bufImg2Mat(BufferedImage original, int imgType, int matType) {
        if (original == null) {
            throw new IllegalArgumentException("original == null");
        }
        // Don't convert if it already has correct type
        if (original.getType() != imgType) {
            // Create a buffered image
            BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
            // Draw the image onto the new buffer
            Graphics2D g = image.createGraphics();
            try {
                g.setComposite(AlphaComposite.Src);
                g.drawImage(original, 0, 0, null);
                original = image;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                g.dispose();
            }
        }
        byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
        Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
        mat.put(0, 0, pixels);
        return mat;
    }
}
