package com.pcloud.common.utils.zip;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.junrar.Archive;
import com.github.junrar.rarfile.FileHeader;
import com.google.common.collect.Lists;
import com.pcloud.common.constant.OSConstant;
import com.pcloud.common.dto.CompressDTO;
import com.pcloud.common.dto.CompressDTO.CompressFileDTO;
import com.pcloud.common.entity.UploadResultInfo;
import com.pcloud.common.exceptions.BizException;
import com.pcloud.common.exceptions.FileException;
import com.pcloud.common.utils.FileUtils;
import com.pcloud.common.utils.ListUtils;
import com.pcloud.common.utils.LocalDateUtils;
import com.pcloud.common.utils.UUIDUitl;
import com.pcloud.common.utils.aliyun.OssUtils;
import com.pcloud.common.utils.string.StringUtil;

/**
 * @描述：文件压缩和解压缩
 * @作者：songx
 * @创建时间：2016年7月13日,上午11:36:37 @版本：1.0
 */
public class CompressUtils {

    /**
     *
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(CompressUtils.class);

    /**
     * 压缩文件临时基础路径
     */
    private static String FILE_LOCAL_PATH = OSConstant.USERDIR + "/download/";

    /**
     * 压缩文件临时基础路径
     */
    private static String ZIP_FILE_PATH = OSConstant.USERDIR + "/zip/";

    /**
     * 分拆大小值
     */
    private static long CUT_SIZE = 1 * 1024 * 1024 * 1024;

    /**
     * 分批次打包，压缩文件集超过指定大小后，分多个包压缩,目前限定1个压缩包1G。
     *
     * @param compressDTO
     * @return
     * @throws BizException
     */
    public static List<UploadResultInfo> zips(CompressDTO compressDTO) throws BizException {
        LOGGER.info("【压缩】分批次打包文件.<START>.[compressDTO]=" + compressDTO);
        if (compressDTO == null) {
            return null;
        }
        List<CompressFileDTO> compressFileDTOs = compressDTO.getCompressFileDTOs();
        if (ListUtils.isEmpty(compressFileDTOs)) {
            return null;
        }
        List<UploadResultInfo> uploadResultInfos = Lists.newArrayList();
        List<String[]> fileUrlList = Lists.newArrayList();
        long totalFileSize = 0;
        int size = compressFileDTOs.size();
        int index = 1;
        for (int i = 0; i < size; i++) {
            CompressFileDTO compressFileDTO = compressFileDTOs.get(i);
            totalFileSize += compressFileDTO.getFileSize();
            fileUrlList.add(new String[]{compressFileDTO.getFileName(), compressFileDTO.getFileUrl()});
            if (totalFileSize > CUT_SIZE) {
                String zipName = compressDTO.getCompressName() + "_" + index;
                UploadResultInfo uploadResultInfo = zip(fileUrlList, zipName);
                uploadResultInfo.setUrl(OssUtils.urlAddKeyLong2Short(uploadResultInfo.getUrl()));
                uploadResultInfo.setFileItemCount(fileUrlList.size());
                uploadResultInfos.add(uploadResultInfo);
                // 零时变量复位
                totalFileSize = 0;
                fileUrlList = Lists.newArrayList();
                index++;
            }
        }
        // 打包剩余总大小不足1G的文件
        if (fileUrlList.size() > 0) {
            UploadResultInfo uploadResultInfo = zip(fileUrlList, compressDTO.getCompressName() + "_" + index);
            uploadResultInfo.setUrl(OssUtils.urlAddKeyLong2Short(uploadResultInfo.getUrl()));
            uploadResultInfo.setFileItemCount(fileUrlList.size());
            uploadResultInfos.add(uploadResultInfo);
        }
        LOGGER.info("【压缩】分批次打包文件.<END>");
        return uploadResultInfos;
    }

    /**
     * 压缩文件
     *
     * @param fileUrlList String[0]=文件名称, String[1]=文件地址
     * @param zipName     压缩包的名称
     * @return
     * @throws BizException
     */
    public static UploadResultInfo zip(List<String[]> fileUrlList, String zipName) throws BizException {
        LOGGER.info("【压缩】压缩文件.<START>");
        if (ListUtils.isEmpty(fileUrlList)) {
            return null;
        }
        zipName = FileUtils.formatName(zipName);
        String tempZipName = zipName + "_" + LocalDateUtils.getYmdhmss();
        String zipFilePath = ZIP_FILE_PATH + tempZipName + ".zip";
        // 检查临时文件夹是否存在，不存在就创建
        String fileFolderPath = FILE_LOCAL_PATH + tempZipName;
        FileUtils.isDir(fileFolderPath);
        UploadResultInfo uploadResultInfo = null;
        try {
            // 下载文件到文件夹中
            int idx = 1;
            for (String[] files : fileUrlList) {
                String fileType = FileUtils.getFileType(files[1]);
                String fileName = FileUtils.formatName(files[0]);
                String downloadLocalPath = fileFolderPath + "/" + fileName + "_" + idx + "." + fileType;
                FileUtils.downloadFileFromUrl(files[1], downloadLocalPath);
                idx++;
            }
            // 检查压缩包临时文件夹是否存在，不存在就创建
            FileUtils.isDir(ZIP_FILE_PATH);
            CompressUtils.zip(fileFolderPath, zipFilePath);
            // 上传文件到服务器中
            uploadResultInfo = OssUtils.uploadLocalFile4CustomName(zipFilePath, zipName);
            uploadResultInfo.setFileName(zipName);
        } catch (Exception e) {
            LOGGER.error("【压缩】压缩失败,<ERROR>:" + e.getMessage(), e);
            throw new FileException(FileException.ZIP_ERROR, "压缩失败！");
        } finally {
            // 删除产生的文件
            FileUtils.deleteDirectory(fileFolderPath);
            FileUtils.deleteFile(zipFilePath);
        }
        LOGGER.info("【压缩】压缩文件.<END>");
        return uploadResultInfo;
    }

    /**
     * 压缩文件(带目录)
     *
     * @param catalogFiles key : 目录名 String[0]=文件名称, String[1]=文件地址
     * @param zipName
     * @return
     * @throws BizException
     */
    public static UploadResultInfo zipByCatalog(Map<String, List<String[]>> catalogFiles, String zipName)
            throws BizException {
        LOGGER.info("【压缩】压缩文件.<START>");
        if (MapUtils.isEmpty(catalogFiles)) {
            return null;
        }
        zipName = FileUtils.formatName(zipName);
        String tempZipName = zipName + "_" + UUIDUitl.generateString(12);
        String parentPath = FILE_LOCAL_PATH + tempZipName;
        FileUtils.isDir(parentPath);
        for (String catalog : catalogFiles.keySet()) {
            String downloadPath;
            // 检查临时文件夹是否存在，不存在就创建
            if (!StringUtil.isEmpty(catalog)) {
                String catalogFolderPath = parentPath + "/" + catalog;
                FileUtils.isDir(catalogFolderPath);
                downloadPath = catalogFolderPath;
            } else {
                downloadPath = parentPath;
            }
            List<String[]> fileUrlList = catalogFiles.get(catalog);
            // 下载文件到文件夹中
            int idx = 1;
            for (String[] files : fileUrlList) {
                String fileType = FileUtils.getFileType(files[1]);
                String fileName = FileUtils.formatName(files[0]);
                String downloadLocalPath = downloadPath + "/" + fileName + "_" + idx + "." + fileType;
                FileUtils.downloadFileFromUrl(files[1], downloadLocalPath);
                idx++;
            }
        }
        // 检查压缩包临时文件夹是否存在，不存在就创建
        FileUtils.isDir(ZIP_FILE_PATH);
        String zipFilePath = ZIP_FILE_PATH + tempZipName + ".zip";
        try {
            CompressUtils.zip(parentPath, zipFilePath);
        } catch (Exception e) {
            LOGGER.error("【压缩】压缩失败,<ERROR>:" + e.getMessage(), e);
            throw new FileException(FileException.ZIP_ERROR, "压缩失败！");
        }
        // 上传文件到服务器中
        UploadResultInfo uploadResultInfo = OssUtils.uploadLocalFile4CustomName(zipFilePath, zipName);
        // 删除产生的文件
        FileUtils.deleteDirectory(parentPath);
        FileUtils.deleteFile(zipFilePath);
        LOGGER.info("【压缩】压缩文件.<END>");
        return uploadResultInfo;
    }

    /**
     * @param zipPath ZIP文件目录
     * @param destDir 解压目录
     * @throws Exception
     * @Desc ZIP文件解压
     */
    private static void unzip(String zipPath, String destDir) throws Exception {
        try {
            Project p = new Project();
            Expand e = new Expand();
            e.setProject(p);
            e.setSrc(new File(zipPath));
            e.setOverwrite(false);
            e.setDest(new File(destDir));
            e.setEncoding("gbk");
            e.execute();
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * @param rarPath rar路径
     * @param destDir 解压到的文件夹
     * @throws Exception
     * @Desc rar文件解压
     */
    public static void unrar(String rarPath, String destDir) throws Exception {
        if (!rarPath.toLowerCase().endsWith(".rar")) {
            throw new Exception("非rar文件！");
        }
        File dstDiretory = new File(destDir);
        if (!dstDiretory.exists()) {
            dstDiretory.mkdirs();
        }
        Archive a = new Archive(new File(rarPath));
        if (a != null) {
            FileHeader fh = a.nextFileHeader();
            while (fh != null) {
                String fileName = fh.getFileNameW().isEmpty() ? fh.getFileNameString() : fh.getFileNameW();
                if (fh.isDirectory()) {
                    File fol = new File(destDir + File.separator + fileName);
                    fol.mkdirs();
                } else {
                    File out = new File(destDir + File.separator + fileName.trim());
                    if (!out.exists()) {
                        if (!out.getParentFile().exists()) {
                            out.getParentFile().mkdirs();
                        }
                        out.createNewFile();
                    }
                    FileOutputStream os = new FileOutputStream(out);
                    a.extractFile(fh, os);
                    os.close();
                }
                fh = a.nextFileHeader();
            }
            a.close();
        }
    }

    /**
     * @param sourceFile zip或者rar文件路径
     * @param destDir    解压目录
     * @throws Exception
     * @Desc 解压缩文件
     */
    public static void deCompress(String sourceFile, String destDir) throws Exception {
        // 保证文件夹路径最后是"/"或者"\"
        char lastChar = destDir.charAt(destDir.length() - 1);
        if (lastChar != '/' && lastChar != '\\') {
            destDir += File.separator;
        }
        // 根据类型，进行相应的解压缩
        String type = sourceFile.substring(sourceFile.lastIndexOf(".") + 1).toLowerCase();
        if (type.equals("zip")) {
            unzip(sourceFile, destDir);
        } else if (type.equals("rar")) {
            unrar(sourceFile, destDir);
        } else {
            throw new Exception("只支持zip和rar格式的压缩包！");
        }
    }

    /**
     * @Desc 压缩zip文件 @param inputFilename 待压缩的文件名称或文件夹路径名称 @param zipFilename
     * 压缩后的文件完整的路径名称 @throws
     */
    public static void zip(String inputFilename, String zipFilename) throws IOException {
        File inputFile = new File(inputFilename);
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFilename));
        try {
            compress(inputFile, out);
            // zip(inputFile, out, "");
        } catch (Exception e) {
            throw e;
        } finally {
            out.close();
        }
    }

    /**
     * 压缩方法
     *
     * @param inputFile
     * @param out
     * @param base
     * @throws IOException
     */
    @SuppressWarnings("unused")
    private static void zip(File inputFile, ZipOutputStream out, String base) throws IOException {
        if (inputFile.isDirectory()) {
            File[] inputFiles = inputFile.listFiles();
            if (!base.equals(""))
                out.putNextEntry(new ZipEntry(base + "/"));
            base = base.length() == 0 ? "" : base + "/";
            for (int i = 0; i < inputFiles.length; i++) {
                zip(inputFiles[i], out, base + inputFiles[i].getName());
            }
        } else {
            if (base.length() > 0) {
                out.putNextEntry(new ZipEntry(base));
            } else {
                out.putNextEntry(new ZipEntry(inputFile.getName()));
            }
            FileInputStream in = new FileInputStream(inputFile);
            try {
                int c;
                byte[] by = new byte[1024];
                while ((c = in.read(by)) != -1) {
                    out.write(by, 0, c);
                }
            } catch (IOException e) {
                throw e;
            } finally {
                in.close();
            }
        }
    }

    /**
     * 按照原路径的类型就行压缩。文件路径直接把文件压缩，
     *
     * @param src
     * @param zos
     */
    private static void compress(File src, ZipOutputStream zos) {
        compressbyType(src, zos, "", true);
    }

    /**
     * 按照原路径的类型就行压缩。文件路径直接把文件压缩，
     *
     * @param src
     * @param zos
     * @param baseDir
     */
    private static void compressbyType(File src, ZipOutputStream zos, String baseDir, boolean isFirst) {
        if (!src.exists())
            return;
        // 判断文件是否是文件，如果是文件调用compressFile方法,如果是路径，则调用compressDir方法；
        if (src.isFile()) {
            // src是文件，调用此方法
            compressFile(src, zos, baseDir);
        } else if (src.isDirectory()) {
            // src是文件夹，调用此方法
            compressDir(src, zos, baseDir, isFirst);

        }
    }

    /**
     * 压缩文件
     */
    private static void compressFile(File file, ZipOutputStream zos, String baseDir) {
        if (!file.exists())
            return;
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            ZipEntry entry = new ZipEntry(baseDir + file.getName());
            zos.putNextEntry(entry);
            int count;
            byte[] buf = new byte[1024];
            while ((count = bis.read(buf)) != -1) {
                zos.write(buf, 0, count);
            }
            bis.close();
        } catch (Exception e) {
        }
    }

    /**
     * 压缩文件夹
     */
    private static void compressDir(File dir, ZipOutputStream zos, String baseDir, boolean isFirst) {
        if (!dir.exists())
            return;
        File[] files = dir.listFiles();
        if (files.length == 0 && !StringUtil.isEmpty(baseDir)) {
            try {
                zos.putNextEntry(new ZipEntry(baseDir + dir.getName() + File.separator));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        for (File file : files) {
            if (isFirst) {
                compressbyType(file, zos, baseDir, false);
            } else {
                compressbyType(file, zos, baseDir + dir.getName() + File.separator, false);
            }
        }
    }

    /**
     * @Desc 压缩zip文件 @param inputFilename 待压缩的文件名称或文件夹路径名称 @param zipFilename
     * 压缩后的文件完整的路径名称 @throws
     */
    public static void zip(String inputFilename, ZipOutputStream out) throws IOException {
        File inputFile = new File(inputFilename);
        try {
            compress(inputFile, out);
            // zip(inputFile, out, "");
        } catch (Exception e) {
            throw e;
        } finally {
            out.close();
        }
    }
}
