/*  
 * Copyright 2006-2020 www.anyline.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 * 
 *           
 */ 
 
package org.anyline.util; 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
 
/** 
 * Java utils 实现的Zip工具 不支持RAR格式 
 *  
 * @author  zh
 */ 
public class ZipUtil { 
	static final Logger log = LoggerFactory.getLogger(ZipUtil.class); 
	private static final int BUFF_SIZE = 1024 * 1024; // 1M Byte 


	public static boolean zip(Map<String,File> files, File zip, String root, String comment, boolean append) {
		boolean result = true;
		long fr = System.currentTimeMillis();
		if (ConfigTable.isDebug()) {
			log.warn("[压缩文件][file:{}][size:{}]", zip.getAbsolutePath(), files.size());
		}
		try {

			List<String> keys = BeanUtil.getMapKeys(files);
			File dir = zip.getParentFile();
			if (!dir.exists()) {
				dir.mkdirs();
			}
			ZipOutputStream zipout = null;
			if (append && zip.exists()) {
				//追加文件
				File tempFile = File.createTempFile(zip.getName(), null);
				tempFile.delete();
				boolean renameOk = zip.renameTo(tempFile);
				if (!renameOk) {
					throw new Exception("重命名失败 "
							+ zip.getAbsolutePath() + " > "
							+ tempFile.getAbsolutePath());
				}
				byte[] buf = new byte[1024];
				ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
				zipout = new ZipOutputStream(new FileOutputStream(zip));
				ZipEntry entry = zin.getNextEntry();
				while (entry != null) {
					String name = entry.getName();
					boolean notInFiles = true;
					for(String key:keys){
						if (key.equals(name)) {
							notInFiles = false;
							break;
						}
					}
//					for (File f : files) {
//						if (f.getName().equals(name)) {
//							notInFiles = false;
//							break;
//						}
//					}
					if (notInFiles) {
						zipout.putNextEntry(new ZipEntry(name));
						int len;
						while ((len = zin.read(buf)) > 0) {
							zipout.write(buf, 0, len);
						}
					}
					entry = zin.getNextEntry();
				}
				zin.close();
			}else{//end 追加
				zipout = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip),BUFF_SIZE));
			}
			for(String key:keys){
				File file = files.get(key);
				if (!zip(file, key, zipout, root)) {
					result = false;
				}
			}

			if(null !=comment){
				zipout.setComment(comment);
			}
			zipout.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (ConfigTable.isDebug()) {
			log.warn("[压缩完成][time:{}][size:{}]",(System.currentTimeMillis() - fr), files.size());
		}
		return result;
	}

	/**
	 * 批量压缩文件(夹) 如果zip已存在则会覆盖
	 *
	 * @param files   要压缩的文件(夹)列表
	 * @param zip  生成的压缩文件
	 * @param root 压缩后文件路径,解压到当前目录时,解压完成后的目录名
	 * @param comment   压缩文件的注释
	 * @param append   是否追加
	 * @return return
	 */
	public static boolean zip(Collection<File> files, File zip, String root, String comment, boolean append) {
		Map<String,File> map = new HashMap<String,File>();
		for(File file:files){
			map.put(file.getName(), file);
		}
		return zip(map,zip,root,comment,append);
	}
	public static boolean zip(Collection<File> files, File zip, String root, String comment) {
		return zip(files, zip, root, comment, false);
	}
	public static boolean zip(Map<String,File> files, File zip, String root, String comment) {
		return zip(files, zip, root, comment, false);
	}

	public static boolean append(Collection<File> files, File zip, String root, String comment){
		return zip(files, zip, root, comment, true);
	}
	public static boolean append(Map<String,File> files, File zip, String root, String comment){
		return zip(files, zip, root, comment, true);
	}

	public static boolean zip(File src, File zip, String root, String comment) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return zip(files, zip, root, comment);
	}
	public static boolean append(File src, File zip, String root, String comment) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return append(files, zip, root, comment);
	}

	/**
	 * 批量压缩文件或文件夹
	 * @param srcs  要压缩的文件或文件夹列表
	 * @param root  压缩后文件路径,解压到当前目录时,解压完成后的目录名
	 * @param zip  生成的压缩文件名
	 * @return return
	 */
	public static boolean zip(Collection<File> srcs, File zip, String root) {
		return zip(srcs, zip, root, null);
	}
	public static boolean zip(Map<String,File> srcs, File zip, String root) {
		return zip(srcs, zip, root, null);
	}
	public static boolean append(Collection<File> srcs, File zip, String root) {
		return append(srcs, zip, root, null);
	}
	public static boolean append(Map<String,File> srcs, File zip, String root) {
		return append(srcs, zip, root, null);
	}

	public static boolean zip(File src, File zip, String root) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return zip(files, zip, root);
	}
	public static boolean append(File src, File zip, String root) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return append(files, zip, root);
	}

	public static boolean zip(Collection<File> srcs, File zip) {
		return zip(srcs, zip, "");
	}
	public static boolean zip(Map<String,File> srcs, File zip) {
		return zip(srcs, zip, "");
	}
	public static boolean append(Collection<File> srcs, File zip) {
		return append(srcs, zip, "");
	}

	public static boolean append(Map<String,File> srcs, File zip) {
		return append(srcs, zip, "");
	}

	public static boolean zip(File src, File zip) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return zip(files, zip);
	}
	public static boolean append(File src, File zip) {
		List<File> files = new ArrayList<File>();
		files.add(src);
		return append(files, zip);
	}

	/** 
	 * 压缩文件 
	 *  
	 * @param src  需要压缩的文件或文件夹 
	 * @param zipout 压缩的目的文件 
	 * @param dir   压缩后文件路径,解压到当前目录时,解压完成后的目录名 
	 */ 
	private static boolean zip(File src, String rename, ZipOutputStream zipout, String dir) {
		try { 
			String path = src.getName();
			if(BasicUtil.isNotEmpty(rename)){
				path = rename;
			}
			if (BasicUtil.isNotEmpty(dir)) { 
				path = dir + File.separator + src.getName(); 
			} 
			dir = new String(dir.getBytes("8859_1"), "GB2312"); 
			if (src.isDirectory()) { 
				File[] fileList = src.listFiles(); 
				for (File file : fileList) { 
					zip(file, file.getName(),zipout, path);
				} 
			} else { 
				long fr = System.currentTimeMillis(); 
				byte buffer[] = new byte[BUFF_SIZE]; 
				BufferedInputStream in = new BufferedInputStream( 
						new FileInputStream(src), BUFF_SIZE); 
				zipout.putNextEntry(new ZipEntry(path)); 
				int realLength; 
				while ((realLength = in.read(buffer)) != -1) { 
					zipout.write(buffer, 0, realLength); 
				} 
				in.close(); 
				zipout.flush(); 
				zipout.closeEntry(); 
				if (ConfigTable.isDebug()) { 
					log.warn("[压缩文件][添加文件][耗时:{}][file:{}]",DateUtil.conversion(System.currentTimeMillis()- fr), src.getAbsolutePath()); 
				} 
			} 
			return true; 
		} catch (Exception e) { 
			e.printStackTrace(); 
			return false; 
		} 
	}

	/** 
	 * 解压缩一个文件 
	 *  
	 * @param zip 压缩文件 
	 * @param dir 解压缩的目标目录 
	 * @return return
	 */ 
	public static List<File> unZip(File zip, String dir) { 
		return unZip(zip, new File(dir)); 
	} 
 
	/** 
	 * 解压缩一个文件 
	 *  
	 * @param zip 压缩文件 
	 * @param dir 解压缩的目标目录 
	 * @return return
	 */ 
	public static List<File> unZip(File zip, File dir) { 
		List<File> files = new ArrayList<File>(); 
		long fr = System.currentTimeMillis(); 
		if (ConfigTable.isDebug()) { 
			log.warn("[解压文件][file:{}][dir:{}]", zip.getAbsolutePath(), dir.getAbsolutePath()); 
		} 
		int size = 0; 
		try { 
			if (!dir.exists()) { 
				dir.mkdirs(); 
			} 
			ZipFile zf = new ZipFile(zip, Charset.forName("GBK")); 
			int total = zf.size(); 
			for (Enumeration<?> entries = zf.entries(); entries 
					.hasMoreElements();) { 
				ZipEntry entry = ((ZipEntry) entries.nextElement()); 
				if (entry.isDirectory()) { 
					continue; 
				} 
				size++; 
				InputStream in = zf.getInputStream(entry); 
				File desFile = new File(dir, entry.getName()); 
				if (!desFile.exists()) { 
					File fileParentDir = desFile.getParentFile(); 
					if (!fileParentDir.exists()) { 
						fileParentDir.mkdirs(); 
					} 
					desFile.createNewFile(); 
				} 
				files.add(desFile); 
				OutputStream out = new FileOutputStream(desFile); 
				byte buffer[] = new byte[BUFF_SIZE]; 
				int realLength; 
				while ((realLength = in.read(buffer)) > 0) { 
					out.write(buffer, 0, realLength); 
				} 
				in.close(); 
				out.close(); 
				if (ConfigTable.isDebug()) { 
					log.warn("[解压完成][进度:{}/{}][耗时:{}][file:{}]", size,total,DateUtil.conversion(System.currentTimeMillis()- fr),desFile.getAbsolutePath()); 
				} 
			} 
			zf.close(); 
		} catch (Exception e) { 
			e.printStackTrace(); 
		} 
		if (ConfigTable.isDebug()) { 
			log.warn("[解压完成][共耗时:{}][dir:{}][size:{}]",DateUtil.conversion(System.currentTimeMillis() - fr), dir.getAbsolutePath(), size); 
		} 
		return files; 
	} 
 
	/** 
	 * 解压文件 
	 *  
	 * @param zip  zip
	 * @return return
	 */ 
	public static List<File> unZip(File zip) { 
		if (null == zip) { 
			return new ArrayList<File>(); 
		} 
		return unZip(zip, zip.getParentFile()); 
	} 
 
	/** 
	 * 获得压缩文件内文件列表 
	 *  
	 * @param zip 压缩文件 
	 * @return 压缩文件内文件名称 
	 */ 
	public static ArrayList<String> getEntriesNames(File zip) { 
		ArrayList<String> entryNames = new ArrayList<String>();
		ZipFile zipFile = null;
		try {
			zipFile = new ZipFile(zip);
			Enumeration<?> entries = zipFile.entries();
			while (entries.hasMoreElements()) { 
				ZipEntry entry = ((ZipEntry) entries.nextElement()); 
				entryNames.add(new String(getEntryName(entry) 
						.getBytes("GB2312"), "8859_1")); 
			} 
		} catch (Exception e) { 
			e.printStackTrace(); 
		} finally {
			if(null != zipFile) {
				try {
					zipFile.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
		return entryNames; 
	} 

	/** 
	 * 取得压缩文件对象的注释 
	 *  
	 * @param entry 压缩文件对象 
	 * @return 压缩文件对象的注释 
	 */ 
	public static String getEntryComment(ZipEntry entry) { 
		String result = ""; 
		try {

            result = entry.getComment();
            if(null != result) {
                result = new String(result.getBytes("GB2312"), "8859_1");
            }

		} catch (Exception e) { 
			e.printStackTrace(); 
		} 
		return result; 
	} 
 
	/** 
	 * 取得压缩文件对象的名称 
	 *  
	 * @param entry 压缩文件对象 
	 * @return 压缩文件对象的名称 
	 */ 
	public static String getEntryName(ZipEntry entry) { 
		String result = ""; 
		try {
			result = entry.getName();
			if(null != result) {
				result = new String(result.getBytes("GB2312"), "8859_1");
			}
		} catch (Exception e) { 
			e.printStackTrace(); 
		} 
		return result; 
	} 
}