package com.yanzuoguang.cloud.file;

import com.yanzuoguang.cloud.CloudConfig;
import com.yanzuoguang.cloud.helper.HttpFileHelper;
import com.yanzuoguang.cloud.vo.YzgFileUploadReqVo;
import com.yanzuoguang.util.MediaHelper;
import com.yanzuoguang.util.MediaParameter;
import com.yanzuoguang.util.YzgError;
import com.yanzuoguang.util.cache.MemoryCache;
import com.yanzuoguang.util.exception.CodeException;
import com.yanzuoguang.util.helper.FileHelper;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.vo.file.*;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.*;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static com.yanzuoguang.util.helper.FileHelper.checkFolder;

/**
 * 文件上传服务实现
 *
 * @author 颜佐光
 */
@Component
public class YzgFileServiceImpl implements YzgFileService, ApplicationContextAware {

    private CloudConfig cloudConfig;

    private YzgFileConfig fileConfig;

    private YzgFileProcedure procedure;

    private final MemoryCache<Boolean> cacheRemoveTempFolder = new MemoryCache<>(10 * 60);

    /**
     * Set the ApplicationContext that this object runs in.
     * Normally this call will be used to initialize the object.
     * <p>Invoked after population of normal bean properties but before an init callback such
     * as {@link InitializingBean#afterPropertiesSet()}
     * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
     * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
     * {@link MessageSourceAware}, if applicable.
     *
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws ApplicationContextException in case of context initialization errors
     * @throws BeansException              if thrown by application context methods
     * @see BeanInitializationException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        cloudConfig = applicationContext.getBean(CloudConfig.class);
        fileConfig = applicationContext.getBean(YzgFileConfig.class);
        procedure = applicationContext.getBean(YzgFileProcedure.class);
    }

    /**
     * 压缩文件
     *
     * @param req 文件上传请求参数
     * @return 上传文件对象
     */
    @Override
    public YzgFileUploadResVo upload(YzgFileUploadReqVo req) {
        YzgFileUploadResVo res = new YzgFileUploadResVo();
        if (req.getFile() == null || req.getFile().length == 0) {
            throw YzgError.getRuntimeException("062");
        }
        String folder = checkFolder(req.getFolder());

        for (MultipartFile uploadFile : req.getFile()) {
            // 获取上传源文件名和扩展名
            String originalFilename = uploadFile.getOriginalFilename();
            if (StringHelper.isEmpty(originalFilename)) {
                throw YzgError.getRuntimeException("062");
            }
            String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
            // 保存的文件名
            String saveFileName = StringHelper.getNewID() + ext;

            // 获取临时文件路径
            String tempFolder = cloudConfig.getTempFolder(folder, new Date());
            String serverFolder = String.format("%s/%s", cloudConfig.getServerUrl(), tempFolder);
            // 创建服务器路径
            FileHelper.createDirectory(serverFolder);
            // 删除目录
            procedure.removeTempFolder(serverFolder);

            // 设置保存文件的路径
            String serverFile = String.format("%s/%s", serverFolder, saveFileName);
            File file = new File(serverFile);
            try {
                uploadFile.transferTo(file);
            } catch (Exception ex) {
                throw YzgError.getRuntimeException(ex, "063", file.getName(), serverFile);
            }

            // 视频上传结果
            YzgFileUploadItemResVo item = new YzgFileUploadItemResVo();
            item.setLocalFile(originalFilename);
            fileConfig.init(item, serverFile);
            res.getList().add(item);

            if (item.getType() == FileHelper.FILE_TYPE_VIDEO) {
                // 转换后的文件路径
                String zipTo = serverFile + ".jpg";
                // 生成转换参数
                YzgFileConvertVideoFirstReqVo firstReq = new YzgFileConvertVideoFirstReqVo();
                firstReq.setFrom(serverFile);
                firstReq.setTo(zipTo);
                // 转换视频首帧
                this.convertVideoFirst(firstReq);
                // 转换后的结果
                YzgFileBaseVo firstRes = fileConfig.init(zipTo);
                item.setFirst(firstRes);
            }

        }

        return res;
    }

    /**
     * 立即删除临时路径
     *
     * @param tempFolder 删除的临时目录
     */
    @Override
    public void removeTempFolder(String tempFolder) {
        tempFolder = checkFolder(tempFolder);
        String toFolder = fileConfig.getServerFullPath(tempFolder);
        // 设置标记
        synchronized (cacheRemoveTempFolder) {
            if (StringHelper.toBoolean(cacheRemoveTempFolder.get(toFolder))) {
                return;
            }
            cacheRemoveTempFolder.put(toFolder, true);
        }
        try {
            // 删除当前临时目录+子文件
            FileHelper.deleteFolder(toFolder);
            // 父文件夹不存在子文件时则删除
            File file = new File(toFolder);
            File parentFile = file.getParentFile();
            if (parentFile.listFiles() == null || Objects.requireNonNull(parentFile.listFiles()).length == 0) {
                removeTempFolder(parentFile.getAbsolutePath());
            }
        } finally {
            // 设置已经删除完成
            cacheRemoveTempFolder.remove(toFolder);
        }
    }


    /**
     * 下载文件
     *
     * @param fromUrl  下载文件路径
     * @param response 输出流
     */
    @Override
    public void down(String fromUrl, HttpServletResponse response) throws IOException {
        checkFolder(fromUrl);
        String fromFullUrl = fileConfig.getServerFullPath(fromUrl);
        // 父文件夹不存在子文件时则删除
        File file = new File(fromFullUrl);
        if (!file.exists()) {
            throw YzgError.getRuntimeException("064", file.getName());
        }
        if (file.isDirectory()) {
            throw YzgError.getRuntimeException("065", file.getName());
        }
        HttpFileHelper.localToDown(fromFullUrl, StringHelper.EMPTY, response);
    }

    /**
     * 移动文件或文件夹
     *
     * @param req 请求参数
     */
    @Override
    public void moveFile(YzgFileMoveReqVo req) {
        if (req.getList() == null || req.getList().isEmpty()) {
            throw YzgError.getException("066");
        }

        // 先检查一遍参数
        for (YzgFileMoveItemReqVo item : req.getList()) {
            checkMove(item);
        }

        // 然后再移动
        for (YzgFileMoveItemReqVo item : req.getList()) {
            move(item);
        }
    }

    private void checkMove(YzgFileMoveItemReqVo item) {
        if (StringHelper.isEmpty(item.getFrom())) {
            throw YzgError.getRuntimeException("067");
        }
        if (StringHelper.isEmpty(item.getTo())) {
            throw YzgError.getRuntimeException("068");
        }

        checkFolder(item.getFrom());
        checkFolder(item.getTo());

        String fullFrom = fileConfig.getServerFullPath(item.getFrom());
        String fullTo = fileConfig.getServerFullPath(item.getTo());
        File fileFrom = new File(fullFrom);
        File fileTo = new File(fullTo);

        if (fileFrom.exists() && fileTo.exists()) {
            if (fileFrom.isFile() && fileTo.isDirectory()) {
                throw YzgError.getRuntimeException("069", item.getFrom(), item.getTo());
            }
        }
    }

    private void move(YzgFileMoveItemReqVo item) {
        checkFolder(item.getFrom());
        checkFolder(item.getTo());

        String fullFrom = fileConfig.getServerFullPath(item.getFrom());
        String fullTo = fileConfig.getServerFullPath(item.getTo());
        // 路径相等,不移动
        if (StringHelper.compare(fullFrom, fullTo)) {
            return;
        }
        File fileFrom = new File(fullFrom);
        File fileTo = new File(fullTo);

        // 防止另外一个线程移动中
        if (fileFrom.exists() && fileTo.exists()) {
            if (fileFrom.isFile() && fileTo.isDirectory()) {
                throw YzgError.getRuntimeException("069", item.getFrom(), item.getTo());
            }
        } else if (!fileFrom.exists()) {
            return;
        }

        // 创建父文件夹
        File parentFile = fileTo.getParentFile();
        FileHelper.createDirectory(parentFile);

        try {
            Files.move(Paths.get(fullFrom), Paths.get(fullTo), StandardCopyOption.REPLACE_EXISTING);
        } catch (Exception ex) {
            throw YzgError.getRuntimeException("070", item.getFrom(), item.getTo(), ex.getMessage());
        }
    }

    /**
     * 获取文件信息
     *
     * @param fromUrl 文件路径
     * @return 文件信息
     */
    @Override
    public YzgFileVideoImageInfoVo getInfo(String fromUrl) {
        checkFolder(fromUrl);

        YzgFileVideoImageInfoVo info = new YzgFileVideoImageInfoVo();
        fileConfig.init(info, fromUrl);

        String serverFullPath = fileConfig.getServerFullPath(fromUrl);
        try {
            switch (info.getType()) {
                case FileHelper.FILE_TYPE_AUDIO: {
                    MediaParameter parameter = MediaHelper.getGrabberParameter(serverFullPath);
                    info.setBitrate(parameter.getAudioBitRate());
                    break;
                }
                case FileHelper.FILE_TYPE_VIDEO: {
                    MediaParameter parameter = MediaHelper.getGrabberParameter(serverFullPath);
                    info.setBitrate(parameter.getVideoBitRate());
                    info.setWidth(parameter.getVideoWidth());
                    info.setHeight(parameter.getVideoHeight());
                    break;
                }
                case FileHelper.FILE_TYPE_IMAGE: {
                    BufferedImage sourceImg = ImageIO.read(Files.newInputStream(Paths.get(serverFullPath)));
                    info.setWidth(sourceImg.getWidth());
                    info.setHeight(sourceImg.getHeight());
                    break;
                }
                case FileHelper.FILE_TYPE_NONE:
                default: {
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return info;
    }

    /**
     * 移动文件或文件夹
     *
     * @param req
     */
    @Override
    public void convertImage(YzgFileVideoImageReqVo req) {
        checkFolder(req.getFrom());
        checkFolder(req.getTo());
        this.convert(req, FileHelper.FILE_TYPE_IMAGE, "图片", (item, from, toFileTemp) -> {
            // 压缩视频到临时文件
            MediaHelper.compressPic(from, toFileTemp, item.getQuote(), item.getSize());
        });
    }

    /**
     * 转换视频第一帧
     *
     * @param req 请求参数
     */
    @Override
    public void convertVideoFirst(YzgFileConvertVideoFirstReqVo req) {
        checkFolder(req.getFrom());
        checkFolder(req.getTo());

        String fullFrom = fileConfig.getServerFullPath(req.getFrom());
        YzgFileBaseVo fromInfo = fileConfig.init(fullFrom);
        if (StringHelper.isEmpty(fromInfo.getMime())) {
            throw YzgError.getRuntimeException("071");
        }
        if (fromInfo.getType() != FileHelper.FILE_TYPE_VIDEO) {
            throw YzgError.getRuntimeException("072");
        }

        try {
            String fullTo = fileConfig.getServerFullPath(req.getTo());
            File fileTo = new File(fullTo);
            if (fileTo.exists()) {
                fileTo.delete();
            }
            // 获取第一帧做为缩略图
            MediaHelper.getVideoFirstImage(fullFrom, fullTo);
        } catch (CodeException e) {
            throw YzgError.getRuntimeException(e, "073");
        }
    }

    /**
     * 移动文件或文件夹
     *
     * @param req 转换
     */
    @Override
    public void convertVideo(YzgFileVideoImageReqVo req) {
        checkFolder(req.getFrom());
        checkFolder(req.getTo());

        this.convert(req, FileHelper.FILE_TYPE_VIDEO, "视频", (item, from, toFileTemp) -> {
            // 压缩视频的参数
            MediaParameter parameter = new MediaParameter(item.getSize(), item.getQuote());
            // 设置最小宽度,指的是宽度和高度中的大值,宽度>高度
            parameter.setMinVideoWidth(StringHelper.getFirst(item.getWidth(), parameter.getMinVideoWidth()));
            // 设置最小高度,指的是宽度和高度中的小值,宽度>高度
            parameter.setMinVideoHeight(StringHelper.getFirst(item.getHeight(), parameter.getMinVideoHeight()));
            // 设置视频最小比特率
            parameter.setMinVideoBitRate(StringHelper.getFirst(item.getBitrate(), parameter.getMinVideoBitRate()));
            // 压缩视频到临时文件
            MediaHelper.zipVideoMp4(from, toFileTemp, parameter);
        });
    }

    /**
     * 转换文件函数
     *
     * @param req  请求参数
     * @param type 类型
     * @param tag  标记
     * @param run  执行数据
     */
    private void convert(YzgFileVideoImageReqVo req, int type, String tag, YzgConvert run) {
        checkFolder(req.getFrom());
        checkFolder(req.getTo());

        List<YzgFileVideoImageItemReqVo> toFiles = req.getList();
        if (toFiles == null || toFiles.isEmpty()) {
            return;
        }

        // 转换文件基础处理
        convertBase(req);

        // 来源文件路径
        String from = fileConfig.getServerFullPath(req.getFrom());
        YzgFileBaseVo fromInfo = fileConfig.init(from);
        if (fromInfo.getType() != type) {
            throw YzgError.getRuntimeException("074", tag);
        }

        // 循环转换所有文件
        for (YzgFileVideoImageItemReqVo item : req.getList()) {
            String to = fileConfig.getServerFullPath(item.getTo());
            File file = new File(to);

            String ext = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("."));
            // 先压缩到临时文件
            String toFileTemp = to + StringHelper.getNewID() + ".tmp" + ext;
            // 将临时文件改成具体的文件
            File fileTemp = new File(toFileTemp);
            try {
                if (fileTemp.exists()) {
                    fileTemp.delete();
                }
                // 转换文件
                run.run(item, from, toFileTemp);
                // 判断文件是否存在,不存在则更改临时文件名
                if (file.exists()) {
                    file.delete();
                }
                // 判断文件是否存在,文件不存在则压缩,文件存在则不压缩
                fileTemp.renameTo(file);
            } catch (IOException ex) {
                throw new CodeException(ex);
            } finally {
                // 删除临时文件
                if (fileTemp.exists()) {
                    fileTemp.delete();
                }
            }
        }
    }

    private void convertBase(YzgFileVideoImageReqVo req) {
        checkFolder(req.getFrom());
        checkFolder(req.getTo());
        // 先循环检测参数
        checkFolder(req.getFrom());
        for (YzgFileVideoImageItemReqVo item : req.getList()) {
            checkFolder(item.getTo());
        }

        // 移动文件,并且确定只移动一次
        if (!StringHelper.isEmpty(req.getTo())) {
            checkMove(req);
            move(req);
            req.setFrom(req.getTo());
            req.setTo(StringHelper.EMPTY);
        }
    }
}