package com.railway.common.utils;

import com.railway.common.utils.file.FileUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

/**
 * @author ZhaoMn
 */
@SuppressWarnings("unchecked")
@Slf4j
public class ZipUtil {

  final static String ZIP_FILE_SUFFIX = ".zip";

  /**
   * 文件打包
   *
   * @param filesPath 导出源文件目录
   * @param zipPath zip文件保存目录
   */
  public static String zipExportFiles(String filesPath, String zipPath) throws ZipException {
    File srcFilesPath = new File(filesPath);
    return zipExportFiles(srcFilesPath, zipPath, srcFilesPath.getName());
  }

  public static String zipExportFiles(String filesPath, String zipPath, String zipFileName)
      throws ZipException {
    File srcFilesPath = new File(filesPath);
    return zipExportFiles(srcFilesPath, zipPath, zipFileName);
  }

  public static String zipExportFiles(File srcFilesPath, String zipPath, String zipFileName)
      throws ZipException {
    String fileName = zipFileName + ZIP_FILE_SUFFIX;
    ZipUtil.zip(srcFilesPath.getAbsolutePath(), zipPath, fileName);
    return fileName;
  }


  /**
   * 递归压缩文件夹
   *
   * @param srcRootDir 压缩文件夹根目录的子路径
   * @param file 当前递归压缩的文件或目录对象
   * @param zos 压缩文件存储对象
   */
  private static void zip(String srcRootDir, File file, ZipOutputStream zos) throws Exception {
    if (file == null) {
      return;
    }
    //如果是文件，则直接压缩该文件
    if (file.isFile()) {
      int count, bufferLen = 1024;
      byte[] data = new byte[bufferLen];

      //获取文件相对于压缩文件夹根目录的子路径
      String subPath = file.getAbsolutePath();
      int index = subPath.indexOf(srcRootDir);
      if (index != -1) {
        subPath = subPath.substring(srcRootDir.length() + File.separator.length());
      }
      ZipEntry entry = new ZipEntry(subPath);
      zos.putNextEntry(entry);
      try (InputStream is = new FileInputStream(file);
          BufferedInputStream bis = new BufferedInputStream(is);) {
        while ((count = bis.read(data, 0, bufferLen)) != -1) {
          zos.write(data, 0, count);
        }
      }
      zos.closeEntry();
    }
    //如果是目录，则压缩整个目录
    else {
      //压缩目录中的文件或子目录
      File[] childFileList = file.listFiles();
      assert childFileList != null;
      for (File file1 : childFileList) {
        file1.getAbsolutePath().indexOf(file.getAbsolutePath());
        zip(srcRootDir, file1, zos);
      }
    }
  }

  /**
   * 对文件或文件目录进行压缩
   *
   * @param srcPath 要压缩的源文件路径。如果压缩一个文件，则为该文件的全路径；如果压缩一个目录，则为该目录的顶层目录路径
   * @param zipPath 压缩文件保存的路径。注意：zipPath不能是srcPath路径下的子文件夹
   * @param zipFileName 压缩文件名
   */
  public static void zip(String srcPath, String zipPath, String zipFileName) throws ZipException {
    log.info("begin to zip file {} ... to a {}/{} zip File...", srcPath, zipPath, zipFileName);
    if (StringUtils.isEmpty(srcPath) || StringUtils.isEmpty(zipPath) || StringUtils
        .isEmpty(zipFileName)) {
      log.error("parameter is null >>> srcPath : {},zipPath : {}, zipFileName : {}", srcPath,
          zipPath, zipFileName);
      throw new ZipException("压缩文件异常，参数为空！");
    }

    File srcFile = new File(srcPath);
    //判断压缩文件保存的路径是否为源文件路径的子文件夹，如果是，则抛出异常（防止无限递归压缩的发生）
    if (srcFile.isDirectory() && zipPath.contains(srcPath)) {
      throw new ZipException("压缩文件保存的路径不能为源文件路径的子文件夹！");
    }

    //判断压缩文件保存的路径是否存在，如果不存在，则创建目录
    File zipDir = new File(zipPath);
    if (!zipDir.exists() || !zipDir.isDirectory()) {
      zipDir.mkdirs();
    }

    //创建压缩文件保存的文件对象
    String zipFilePath = zipPath + File.separator + zipFileName;
    File zipFile = new File(zipFilePath);
    // 删除已有文件
    FileUtils.deleteFile(zipFilePath);

    //如果只是压缩一个文件，则需要截取该文件的父目录
    String srcRootDir = srcPath;
    if (srcFile.isFile()) {
      int index = srcPath.lastIndexOf(File.separator);
      if (index != -1) {
        srcRootDir = srcPath.substring(0, index);
      }
    }

    try (OutputStream os = new FileOutputStream(zipFile);
        CheckedOutputStream cos = new CheckedOutputStream(os, new CRC32());
        ZipOutputStream zos = new ZipOutputStream(cos);) {
      //调用递归压缩方法进行目录或文件压缩
      zip(srcRootDir, srcFile, zos);
      zos.flush();
      log.info("zip file " + zipFile.getAbsolutePath() + " has finished");
    } catch (Exception e) {
      if (zipFile.exists()) {
        zipFile.delete();
      }
    }
    FileUtils.deleteDirectory(srcPath);
  }

  /**
   * 解压缩zip包
   *
   * @param zipFilePath zip文件的全路径
   * @param unzipFilePath 解压后的文件保存的路径
   * @param includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含；false-不包含
   */
  public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName,
      Charset charset) throws Exception {
    if (StringUtils.isEmpty(zipFilePath) || StringUtils.isEmpty(unzipFilePath)) {
      log.error("parameter is null >>> zipFilePath : {},unzipFilePath : {}", zipFilePath,
          unzipFilePath);
      throw new Exception("PARAMETER IS NULL!!!");
    }
    File zipFile = new File(zipFilePath);
    //如果解压后的文件保存路径包含压缩文件的文件名，则追加该文件名到解压路径
    if (includeZipFileName) {
      String fileName = zipFile.getName();
      if (!StringUtils.isEmpty(fileName)) {
        fileName = fileName.substring(0, fileName.lastIndexOf("."));
      }
      unzipFilePath = unzipFilePath + File.separator + fileName;
    }
    //创建解压缩文件保存的路径
    File unzipFileDir = new File(unzipFilePath);
    if (!unzipFileDir.exists() || !unzipFileDir.isDirectory()) {
      unzipFileDir.mkdirs();
    }

    //开始解压
    ZipEntry entry;
    String entryFilePath, entryDirPath;
    File entryFile, entryDir;
    int index, count, bufferSize = 1024;
    byte[] buffer = new byte[bufferSize];

    ZipFile zip;
    try {
      zip = new ZipFile(zipFile, charset);
    } catch (IOException e) {
      throw new Exception("PARAMETER IS NULL!!!");
    }
    Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zip.entries();
    //循环对压缩包里的每一个文件进行解压
    while (entries.hasMoreElements()) {
      entry = entries.nextElement();
      String entryName = entry.getName().replace("/", File.separator).replace("\\", File.separator);
      //构建压缩包中一个文件解压后保存的文件全路径
      entryFilePath = unzipFilePath + File.separator + entryName;
      //构建解压后保存的文件夹路径
      index = entryFilePath.lastIndexOf(File.separator);
      if (index != -1) {
        entryDirPath = entryFilePath.substring(0, index);
      } else {
        entryDirPath = "";
      }
      entryDir = new File(entryDirPath);
      //如果文件夹路径不存在，则创建文件夹
      if (!entryDir.exists() || !entryDir.isDirectory()) {
        entryDir.mkdirs();
      }

      //创建解压文件
      entryFile = new File(entryFilePath);
      // 删除已有文件
      FileUtils.deleteFile(entryFilePath);

      //写入文件
      try (OutputStream os = new FileOutputStream(entryFile);
          BufferedOutputStream bos = new BufferedOutputStream(os);
          InputStream is = zip.getInputStream(entry);
          BufferedInputStream bis = new BufferedInputStream(is)) {
        while ((count = bis.read(buffer, 0, bufferSize)) != -1) {
          bos.write(buffer, 0, count);
        }
        bos.flush();
      } catch (FileNotFoundException e) {
        throw new Exception("File Not Found Exception!!!");
      } catch (IOException e) {
        throw new Exception("IO Exception!!!");
      }
    }
    zip.close();
  }

  /**
   * 解压缩zip包
   *
   * @param zipFilePath zip文件的全路径
   * @return 文件解压路径
   */
  public static String unzip(String zipFilePath) throws Exception {
    // includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含；false-不包含
    // 此处通过文件名获得解压路径，因此不需要再次包含文件名
    String unzipFilePath = zipFilePath.substring(0, zipFilePath.lastIndexOf("."));
    unzip(zipFilePath, unzipFilePath, false);
    return unzipFilePath;
  }

  /**
   * 解压缩zip包
   *
   * @param zipFilePath zip文件的全路径
   * @param unzipFilePath 解压后的文件保存的路径
   */
  public static void unzip(String zipFilePath, String unzipFilePath) throws Exception {
    // includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含；false-不包含
    boolean includeZipFileName = true;
    Charset charset = Charset.forName("GBK");
    unzip(zipFilePath, unzipFilePath, includeZipFileName, charset);
  }

  /**
   * 解压缩zip包
   *
   * @param zipFilePath zip文件的全路径
   * @param unzipFilePath 解压后的文件保存的路径
   */
  public static void unzip(String zipFilePath, String unzipFilePath, Charset charset)
      throws Exception {
    // includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含；false-不包含
    boolean includeZipFileName = true;
    unzip(zipFilePath, unzipFilePath, includeZipFileName, charset);
  }

  /**
   * 解压缩zip包
   *
   * @param zipFilePath zip文件的全路径
   * @param unzipFilePath 解压后的文件保存的路径
   */
  public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName)
      throws Exception {
    Charset charset = Charset.forName("GBK");
    unzip(zipFilePath, unzipFilePath, includeZipFileName, charset);
  }

}
