package cn.util.file;

import cn.util.core.IsKit;
import cn.util.core.LogKit;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.*;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.io.FileUtils;

import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class TarGipFileUtil {

    static boolean flag = false;
    static File sourceFile = null;
    static FileInputStream fis = null;
    static BufferedInputStream bis = null;
    static FileOutputStream fos = null;
    static ZipOutputStream zos = null;

    /**
     * 将存放在sourceFilePath目录下的所有源文件,打包成fileName名称的ZIP文件,并存放到zipFilePath。
     *
     * @param sourceFilePath 待压缩的文件路径
     * @param zipFilePath    压缩后存放路径
     * @param fileName       压缩后文件的名称
     * @return flag
     */
    public static boolean fileToZip(String sourceFilePath, String zipFilePath, String fileName) {
        sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            LogKit.info("待压缩的文件目录：" + sourceFilePath + " 不存在.");
            return false;
        }
        try {
            File zipFile = new File(zipFilePath + "/" + fileName + ".zip");
            if (zipFile.exists()) {
                LogKit.info(zipFilePath + " 目录下存在名字为：" + fileName + ".zip" + " 打包文件.");
                return false;
            }
            File[] sourceFiles = sourceFile.listFiles();
            if (IsKit.isNull(sourceFiles) || sourceFiles.length < 1) {
                LogKit.info("待压缩的文件目录：" + sourceFilePath + " 里面不存在文件,无需压缩.");
                return false;
            }
            fos = new FileOutputStream(zipFile);
            zos = new ZipOutputStream(new BufferedOutputStream(fos));
            byte[] bufs = new byte[1024 * 10];
            for (int i = 0; i < sourceFiles.length; i++)
            {
                File f = new File(sourceFilePath + "/" + sourceFiles[i].getName());
                if (f.isDirectory()) {
                    File[] files = f.listFiles();
                    for (File file : files) {
                        File tempF = new File(f.getPath() + "/" + file.getName());
                        if (tempF.isDirectory()) {
                            zipfile(sourceFilePath + "/" + sourceFiles[i].getName() + "/" + file.getName(), zipFilePath, sourceFilePath);
                        } else {
                            // 创建ZIP实体,并添加进压缩包
                            ZipEntry zipEntry = new ZipEntry(sourceFiles[i].getName() + "/" + file.getName());
                            zos.putNextEntry(zipEntry);
                            // 读取待压缩的文件并写进压缩包里
                            fis = new FileInputStream(file);
                            bis = new BufferedInputStream(fis, 1024 * 10);
                            int read = 0;
                            while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                                zos.write(bufs, 0, read);
                            }
                        }
                    }
                } else {
                    // 创建ZIP实体,并添加进压缩包
                    ZipEntry zipEntry = new ZipEntry(sourceFiles[i].getName());
                    zos.putNextEntry(zipEntry);
                    // 读取待压缩的文件并写进压缩包里
                    fis = new FileInputStream(sourceFiles[i]);
                    bis = new BufferedInputStream(fis, 1024 * 10);
                    int read = 0;
                    while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                        zos.write(bufs, 0, read);
                    }
                }
            }
            flag = true;

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {//关闭流
            try {
                if (IsKit.isNotNull(bis)) bis.close();
                if (IsKit.isNotNull(zos)) zos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        return flag;
    }

    //功能同上，只打包给定文件sourceFileNames
    public static boolean fileToZip(String sourceFilePath, String zipFilePath, String sourceFileNames, String fileName) {
        sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            LogKit.info("待压缩的文件目录：" + sourceFilePath + " 不存在.");
            return false;
        }
        try {
            File zipFile = new File(zipFilePath + "/" + fileName + ".zip");
            /*if (zipFile.exists()) {
                LogKit.info(zipFilePath + " 目录下存在名字为：" + fileName + ".zip" + " 打包文件.");
                return false;
            }*/
            File[] sourceFiles = sourceFile.listFiles();

            if (IsKit.isNull(sourceFiles) || sourceFiles.length < 1) {
                LogKit.info("待压缩的文件目录：" + sourceFilePath + " 里面不存在文件,无需压缩.");
                return false;
            }
            fos = new FileOutputStream(zipFile);
            zos = new ZipOutputStream(new BufferedOutputStream(fos));
            byte[] bufs = new byte[1024 * 10];
            for (int i = 0; i < sourceFiles.length; i++) {
                File f = new File(sourceFilePath + "/" + sourceFiles[i].getName());
                String fileNameTemp = f.getName();
                if (IsKit.isNullOrEmpty(fileNameTemp) || !sourceFileNames.contains(fileNameTemp)) {
                    continue;
                }
                if (f.isDirectory()) {
                    File[] files = f.listFiles();
                    for (File file : files) {
                        File tempF = new File(f.getPath() + "/" + file.getName());
                        if (tempF.isDirectory()) {
                            zipfile(sourceFilePath + "/" + sourceFiles[i].getName() + "/" + file.getName(), zipFilePath, sourceFilePath);
                        } else {
                            // 创建ZIP实体,并添加进压缩包
                            ZipEntry zipEntry = new ZipEntry(sourceFiles[i].getName() + "/" + file.getName());
                            zos.putNextEntry(zipEntry);
                            // 读取待压缩的文件并写进压缩包里
                            fis = new FileInputStream(file);
                            bis = new BufferedInputStream(fis, 1024 * 10);
                            int read = 0;
                            while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                                zos.write(bufs, 0, read);
                            }
                        }
                    }
                } else {
                    // 创建ZIP实体,并添加进压缩包
                    ZipEntry zipEntry = new ZipEntry(sourceFiles[i].getName());
                    zos.putNextEntry(zipEntry);
                    // 读取待压缩的文件并写进压缩包里
                    fis = new FileInputStream(sourceFiles[i]);
                    bis = new BufferedInputStream(fis, 1024 * 10);
                    int read = 0;
                    while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                        zos.write(bufs, 0, read);
                    }
                }
            }
            flag = true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {//关闭流
            try {
                if (IsKit.isNotNull(bis)) bis.close();
                if (IsKit.isNotNull(zos)) zos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return flag;
    }

    public static void zipfile(String path, String zipFilePath, String sourceFilePath) throws Exception {
        File[] files = new File(path).listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                zipfile(path + "/" + file.getName(), zipFilePath, sourceFilePath);
            } else {

                byte[] bufs = new byte[1024 * 10];
                // 创建ZIP实体,并添加进压缩包
                String tempPath = path.substring(sourceFilePath.length());
                ZipEntry zipEntry = new ZipEntry(tempPath + "/" + file.getName());
                zos.putNextEntry(zipEntry);
                // 读取待压缩的文件并写进压缩包里
                fis = new FileInputStream(file);
                BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);
                int read = 0;
                while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                    zos.write(bufs, 0, read);
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        String sourceFilePath = "C:/bjtu/template";
        String zipFilePath = "C:/bjtu/template";
        String sourceFileNames = "t_result.docx";
        String fileName = "qqq";
        //boolean flag = TarGipFileUtil.fileToZip(sourceFilePath, zipFilePath, fileName);
        flag = TarGipFileUtil.fileToZip(sourceFilePath, zipFilePath, sourceFileNames, fileName);
        if (flag) {
            System.out.println(">>>>>> 文件打包成功." + fileName + " <<<<<<");
        } else {
            System.out.println(">>>>>> 文件打包失败. <<<<<<");
        }


 /*       String sourceFilePath = "C:/bjtu/template";
        String tarFilePath = "C:/bjtu/template";
        String tarFileName = "test";
        createTarFile(sourceFilePath, tarFilePath, "", tarFileName);

  */
    }

    /**
     * 把一个目录下的所有文件和文件夹打成.tar.gz包（从当前的目录开始）
     * <p>Method ：createTarFile
     * <p>Description : 打包文件 .tar.gz
     *
     * @param sourceFolder  需要打成.tar.gz包的目录（包含目录和目录下的所有文件和文件夹）例：E:/aarm/test
     * @param tarGzPath     打成的tar包生成的目标目录 例： D:/tmp  最终打包会在 D:/tmp目录下生成 test.tar.gz包
     * @param ignoreDir     需要忽略的目录
     * @param tarGzFileName 打tar.gz包的名，例如：ide-sdk.tar.gz
     */
    public static void createTarFile(String sourceFolder, String tarGzPath, String ignoreDir, String tarGzFileName) {
        TarArchiveOutputStream tarOs = null;
        try {
            File tarGzFile = new File(tarGzPath);
            File sourceFile = new File(sourceFolder);
            if (!tarGzFile.exists()) {
                tarGzFile.mkdirs();
            }
            if (!sourceFile.exists()) {
                throw new FileNotFoundException("压缩的目录不存在。。。");
            }
            // 创建一个 FileOutputStream 到输出文件（.tar.gz）
            File tarFile = new File(tarGzFile + "/" + tarGzFileName);
            FileOutputStream fos = new FileOutputStream(tarFile);
//			// 创建一个 GZIPOutputStream，用来包装 FileOutputStream 对象
            GZIPOutputStream gos = new GZIPOutputStream(new BufferedOutputStream(fos));
            // 创建一个 TarArchiveOutputStream，用来包装 GZIPOutputStream 对象
            tarOs = new TarArchiveOutputStream(gos);
            // 若不设置此模式，当文件名超过 100 个字节时会抛出异常，异常大致如下：
            // is too long ( > 100 bytes)
            // 具体可参考官方文档：http://commons.apache.org/proper/commons-compress/tar.html#Long_File_Names
            tarOs.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            addFilesToTarGZ(sourceFile, "", tarOs, ignoreDir);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                tarOs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 把文件复制到.tar.gz包中
     *
     * @param file       需要复制的文件
     * @param parent     父目录
     * @param tarArchive tar包流
     * @param ignoreDir  忽略的目录
     * @throws IOException 异常
     */
    public static void addFilesToTarGZ(File file, String parent, TarArchiveOutputStream tarArchive, String ignoreDir)
            throws IOException {
        //不要再包一层最上面的目录
        if (parent.startsWith(ignoreDir + File.separator)) {
            parent = parent.replace(ignoreDir + File.separator, File.separator);
        }
        String entryName = parent + file.getName();
        if (!parent.equals("")) {
            // 添加 tar ArchiveEntry
            tarArchive.putArchiveEntry(new TarArchiveEntry(file, entryName));
        }
        if (file.isFile()) {
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);
            // 写入文件
            IOUtils.copy(bis, tarArchive);
            tarArchive.closeArchiveEntry();
            bis.close();
        } else if (file.isDirectory()) {
            // 因为是个文件夹，无需写入内容，关闭即可
            if (!parent.equals("")) {
                tarArchive.closeArchiveEntry();
            }
            File[] files = file.listFiles();
            if (files != null) {
                // 读取文件夹下所有文件
                for (File f : files) {
                    // 递归
                    addFilesToTarGZ(new File(f.getAbsolutePath()), entryName + File.separator, tarArchive, ignoreDir);
                }
            }
        }
    }

    /**
     * 递归删除目录下的所有文件及子目录下所有文件
     *
     * @param dir 将要删除的文件目录
     * @return boolean Returns "true" if all deletions were successful.
     * If a deletion fails, the method stops attempting to
     * delete and returns "false".
     */
    public static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //递归删除目录中的子目录下
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空，可以删除
        return dir.delete();
    }

    /**
     * <p>Method ：copyDirectory
     * <p>Description :  复制文件夹
     *
     * @param sourcePathString
     * @param targetPathString
     */
    public static void copyDirectory(String sourcePathString, String targetPathString) {
        if (!new File(sourcePathString).canRead()) {
            System.out.println("源文件夹" + sourcePathString + "不可读，无法复制！");
        } else {
            (new File(targetPathString)).mkdirs();
            System.out.println("开始复制文件夹" + sourcePathString + "到" + targetPathString);
            File[] files = new File(sourcePathString).listFiles();
            for (File file : files) {
                if (file.isFile()) {
                    copyFile(new File(sourcePathString + File.separator + file.getName()), new File(targetPathString + File.separator + file.getName()));
                } else if (file.isDirectory()) {
                    copyDirectory(sourcePathString + File.separator + file.getName(), targetPathString + File.separator + file.getName());
                }
            }
            System.out.println("复制文件夹" + sourcePathString + "到" + targetPathString + "结束");
        }
    }

    /**
     * <p>Method ：copyFile
     * <p>Description : 复制文件
     *
     * @param sourceFile
     * @param targetFile
     */
    public static void copyFile(File sourceFile, File targetFile) {
        if (!sourceFile.canRead()) {
            System.out.println("源文件" + sourceFile.getAbsolutePath() + "不可读，无法复制！");
        } else {
            System.out.println("开始复制文件" + sourceFile.getAbsolutePath() + "到" + targetFile.getAbsolutePath());
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            FileOutputStream fos = null;
            BufferedOutputStream bos = null;
            try {
                fis = new FileInputStream(sourceFile);
                bis = new BufferedInputStream(fis);
                fos = new FileOutputStream(targetFile);
                bos = new BufferedOutputStream(fos);
                int len = 0;
                while ((len = bis.read()) != -1) {
                    bos.write(len);
                }
                bos.flush();

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                    if (bis != null) {
                        bis.close();
                    }
                    if (fos != null) {
                        fos.close();
                    }
                    if (bos != null) {
                        bos.close();
                    }
                    System.out.println("文件" + sourceFile.getAbsolutePath() + "复制到" + targetFile.getAbsolutePath() + "完成");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
