/*
 * $Id: ZipUtils.java,v 1.17 2007/10/30 15:48:05 korowa Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: ZipUtils.java,v $
 * Revision 1.17  2007/10/30 15:48:05  korowa
 * Beim Archivieren und Exportieren werden bei Documentennamen die Ids miteingegeben
 *
 * Revision 1.16  2007/10/25 15:01:58  korowa
 * Neue Zip-Funktion hinzugefügt
 *
 * Revision 1.15  2007/08/28 12:16:04  korowa
 * Export- und Importfunktionen hinzugefügt
 *
 * Revision 1.14  2006/04/11 19:49:10  marbobo
 * - rar wieder entfernt
 * + sevenZip() hinzugefügt - Auf diese Weise kann man nun auch Unicode-Archive erzeugen
 *
 * Revision 1.13  2006/04/04 22:29:13  marbobo
 * + rar() hinzugefügt - Auf diese Weise kann man nun auch Unicode-Archive erzeugen
 *
 * Revision 1.12  2004/11/10 15:02:46  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.11  2004/06/27 17:33:37  marbobo
 * + Quellcode maintenance
 *
 * Revision 1.10  2003/12/24 10:11:58  bigmac
 * Projektweite Import-Optimierung
 *
 * Revision 1.9  2003/11/30 13:05:43  koppy
 * zipinputstream in VKCInputstream gewrappt
 *
 * Revision 1.8  2003/06/26 14:57:11  lawern
 * Streams können jetzt auch entpackt werden
 *
 * Revision 1.7  2003/06/18 12:36:41  koppy
 * + getUnzippedStream(...)
 *
 * Revision 1.6  2003/06/10 14:50:45  lawern
 * Anpassung an Pfade mit Leerzeichen
 *
 * Revision 1.5  2003/03/10 01:23:21  koppy
 * Teile aus unzip(...) nach doUnzip(...) extrahiert
 *
 * Revision 1.4  2003/02/27 13:45:59  lawern
 * zip-Methode um einen Parameter erweitert.
 *
 */
package vkc.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import vkc.misc.VKCLogManager;
import vkc.view.WebShare;

/**
 * Klasse zum Packen und Entpacken von Zipdateien
 * 
 * <p></p>
 *
 * @author bigmac
 */
final public class ZipUtils {
	private ZipUtils() {
	}

	/**
	 * entpackt die Zipdatei "file" in das Verzeichnis "dir"
	 * 
	 * <p></p>
	 *
	 * @param file - Die zu entpackende Zip-Datei
	 * @param dir - Zielverzeichnis, in das die Zip-Einträge entpackwerden
	 *        sollen
	 * @param overwrite - falls true und das Zielverzeichnis bereits existiert,
	 *        wird es gelöscht. falls false und das Zielverzeichnis bereits
	 *        existiert, wird eine IOException geworfen.
	 */
	final public static void unzip(File file, File dir, boolean overwrite) throws IllegalArgumentException, IOException {
		if (file == null) {
			throw new IllegalArgumentException("no input file specified (null)");
		}

		if (!file.exists()) {
			throw new IOException("file not found: " + file);
		}

		if (file.isDirectory()) {
			throw new IllegalArgumentException("input file points to a directory: " + file);
		}

		if (dir == null) {
			throw new IllegalArgumentException("no destination directory specified (null)");
		}

		if (dir.exists()) {
			if (!overwrite) {
				throw new IOException("can't overwrite existing file: " + dir);
			} else if (dir.isDirectory()) {
				if (!FileUtils.deleteDirectory(dir)) {
					throw new IOException("could not delete existing directory: " + dir);
				}
			} else {
				if (!FileUtils.deleteFile(dir)) {
					throw new IOException("could not delete existing file: " + dir);
				}
			}
		}

		if (!dir.mkdirs()) {
			throw new IOException("could not create destination directory: " + dir);
		}

		doUnzip(file, dir);
	}

	/**
	 * entpackt den Stream "zipStream" in das Verzeichnis "dir"
	 * 
	 * <p></p>
	 *
	 * @param zipStream - Der zu entpackende InputStreami
	 * @param dir - Zielverzeichnis, in das die Zip-Einträge entpackwerden
	 *        sollen
	 * @param overwrite - falls true und das Zielverzeichnis bereits existiert,
	 *        wird es gelöscht. falls false und das Zielverzeichnis bereits
	 *        existiert, wird eine IOException geworfen.
	 */
	final public static void unzip(InputStream zipStream, File dir, boolean overwrite) throws IllegalArgumentException, IOException {
		if (zipStream == null) {
			throw new IllegalArgumentException("no input stream specified (null)");
		}

		if (dir == null) {
			throw new IllegalArgumentException("no destination directory specified (null)");
		}

		if (dir.exists()) {
			if (!overwrite) {
				throw new IOException("can't overwrite existing file: " + dir);
			} else if (dir.isDirectory()) {
				if (!FileUtils.deleteDirectory(dir)) {
					throw new IOException("could not delete existing directory: " + dir);
				}
			} else {
				if (!FileUtils.deleteFile(dir)) {
					throw new IOException("could not delete existing file: " + dir);
				}
			}
		}

		if (!dir.mkdirs()) {
			throw new IOException("could not create destination directory: " + dir);
		}

		doUnzip(zipStream, dir);
	}

	/**
	 * s. unzip( File file , File dir , boolean overwrite  )
	 */
	final public static void unzip(File file, File dir) throws IllegalArgumentException, IOException {
		unzip(file, dir, true);
	}

	/**
	 * s. unzip( InputStream zipStream , File dir , boolean overwrite  )
	 */
	final public static void unzip(InputStream zipStream, File dir) throws IllegalArgumentException, IOException {
		unzip(zipStream, dir, true);
	}

	/**
	 * entpackt die Zipdatei "file" in das Verzeichnis "dir" OHNE Überprüfung
	 * auf Existenz der Datei und dem Zielverzeichnis
	 *
	 * @param file die zu entpackende ZIP-Datei
	 * @param dir das Zielverzeichnis
	 *
	 * @throws IOException falls Dateien oder Verzeichnisse nicht angelegt
	 *         werden können
	 */
	final public static void doUnzip(File file, File dir) throws IOException {
		File outFile = null;
		File parent = null;

		OutputStream out = null;
		InputStream zipIS = null;
		int chunkSize = 50000;
		byte[] chunk = new byte[chunkSize];
		int readBytes = 0;
		ZipEntry zipEntry = null;

		ZipFile zipFile = new ZipFile(file);
		java.util.Enumeration zipEntries = zipFile.entries();

		try {
			while (zipEntries.hasMoreElements()) {
				try {
					zipEntry = (ZipEntry) zipEntries.nextElement();

					if (zipEntry.isDirectory()) {
						File newDir = new File(dir, zipEntry.getName());

						if (newDir.exists()) {
							continue;
						}

						if (!newDir.mkdirs()) {
							throw new IOException("directory could not be created: " + newDir.getParent());
						}

						continue;
					}

					outFile = new File(dir, zipEntry.getName());
					parent = outFile.getParentFile();

					if (parent != null) {
						if (!parent.exists()) {
							if (!parent.mkdirs()) {
								throw new IOException("directory could not be created: " + parent);
							}
						}
					}

					zipIS = zipFile.getInputStream(zipEntry);
					out = new BufferedOutputStream(new FileOutputStream(outFile), chunkSize);

					while ((readBytes = zipIS.read(chunk, 0, chunkSize)) > 0) {
						out.write(chunk, 0, readBytes);
					}
					
					zipIS.close();
					zipIS = null;

					out.flush();
					out.close();
					out = null;
				} catch (IOException ex) {
					FileUtils.closeOutputStream(out);
					FileUtils.closeInputStream(zipIS);
					throw ex;
				}
			}
		} finally {
			zipFile.close();
		}
	}

	/**
	 * entpackt die Zipdatei "file" in das Verzeichnis "dir" OHNE Überprüfung
	 * auf Existenz der Datei und dem Zielverzeichnis
	 *
	 * @param file die zu entpackende ZIP-Datei
	 * @param dir das Zielverzeichnis
	 *
	 * @throws IOException falls Dateien oder Verzeichnisse nicht angelegt
	 *         werden können
	 */
	final public static void doUnzip(InputStream stream, File dir) throws IOException {
		File outFile = null;
		File parent = null;

		OutputStream out = null;
		int chunkSize = 50000;
		byte[] chunk = new byte[chunkSize];
		int readBytes = 0;
		ZipEntry zipEntry = null;

		ZipInputStream zipStream = new ZipInputStream(stream);

		try {
			while ((zipEntry = zipStream.getNextEntry()) != null) {
				try {
					if (zipEntry.isDirectory()) {
						zipStream.closeEntry();

						File newDir = new File(dir, zipEntry.getName());

						if (newDir.exists()) {
							continue;
						}

						if (!newDir.mkdirs()) {
							throw new IOException("directory could not be created: " + newDir.getParent());
						}

						continue;
					}

					outFile = new File(dir, zipEntry.getName());
					parent = outFile.getParentFile();

					if (parent != null) {
						if (!parent.exists()) {
							if (!parent.mkdirs()) {
								throw new IOException("directory could not be created: " + parent);
							}
						}
					}

					out = new BufferedOutputStream(new FileOutputStream(outFile), chunkSize);

					while ((readBytes = zipStream.read(chunk, 0, chunkSize)) > 0) {
						out.write(chunk, 0, readBytes);
					}
					
					zipStream.closeEntry();
					out.flush();
					out.close();
					out = null;
				} catch (IOException ex) {
					zipStream.closeEntry();
					FileUtils.closeOutputStream(out);
					throw ex;
				}
			}
		} finally {
			zipStream.close();
		}
	}

	/**
	 * Packt ein Verzeichnis rekursiv oder eine Datei in eine Zip-Datei
	 * 
	 * <p>
	 * Wichtig: - PowerArchiver (6) beschwert sich in einem Dialog über eine
	 * korrupte Zipdatei-Struktur, entpackt sie aber trotzdem. - Umlaute in
	 * Dateinamen werden nicht korrekt berücksichtigt. s. Java-Bugparade (Id
	 * 4415733): 1. Don't use a umlaut in file/path-names in a zip-file. 2.
	 * Use JDK 1.1.8
	 * </p>
	 * 
	 * <p></p>
	 *
	 * @param zipFile - Name der Zieldatei
	 * @param inputFile - Datei oder Verzeichnis, das verpackt werden soll
	 * @param overwrite - falls true, wird eine bereits existierende Zipdatei
	 * @param baseName - Verzeichnisname der in der Zip-Datei Root-Verzeichnis
	 *        werden soll. gelöscht.
	 */
	public static void zip(File inputFile, File zipFile, boolean overwrite, String baseName) throws IllegalArgumentException, IOException {
		if (inputFile == null) {
			throw new IllegalArgumentException("input file/directory must not be null");
		}

		inputFile = inputFile.getCanonicalFile();

		if (!inputFile.exists()) {
			throw new IOException("input file does not exist: " + inputFile);
		}

		if (zipFile.exists()) {
			if (zipFile.isDirectory()) {
				throw new IllegalArgumentException("zipFile points to a existing directory: " + zipFile);
			}

			if (!overwrite) {
				throw new IOException("destination zipfile already exist: " + zipFile);
			} else if (!FileUtils.deleteFile(zipFile)) {
				throw new IOException("zipfile could not be deleted: " + zipFile);
			}
		}

		// evtl. existierende Zip-Datei ist gelöscht
		BufferedOutputStream bOut = new BufferedOutputStream(new FileOutputStream(zipFile));
		ZipOutputStream zipOut = new ZipOutputStream(bOut);
		ZipEntry entry = null;
		BufferedInputStream fileIn = null;
		int chunkSize = 50000;
		byte[] chunk = new byte[chunkSize];
		int readBytes = 0;

		int baseNameLength = baseName.length();
		String entryName = null;

		// Dateien in den (Unter-) Verzeichnissen werden iterativ
		// in den Vector gepackt.  Falls der Vector leer ist, sind
		// alle Dateien verpackt.
		// File-Vector initialisieren
		java.util.Vector vector = new java.util.Vector();
		vector.add(inputFile);

		try {
			while (vector.size() > 0) {
				File file = (File) vector.remove(0);

				if (file.isDirectory()) {
					File[] list = file.listFiles();

					for (int i = 0; i < list.length; i++) {
						vector.addElement(list[i]);
					}
					
					continue;
				}

				try {
					entryName = file.getCanonicalPath();

					entryName = entryName.substring(baseNameLength);

					entry = new ZipEntry(entryName);
					zipOut.putNextEntry(entry);

					fileIn = new BufferedInputStream(new FileInputStream(file));

					while ((readBytes = fileIn.read(chunk, 0, chunkSize)) > 0) {
						zipOut.write(chunk, 0, readBytes);
					}
				} finally {
					zipOut.closeEntry();
					FileUtils.closeInputStream(fileIn);
				}
			}
		} finally {
			FileUtils.closeOutputStream(zipOut);
		}
	}

	/**
	 * s. zip( File inputFile , File zipFile , boolean overwrite  )
	 * 
	 * <p></p>
	 */
	public static void zip(File inputFile, File zipFile) throws IllegalArgumentException, IOException {
		zip(inputFile, zipFile, true);
	}

	public static void zip(File inputFile, File zipFile, boolean overwrite) throws IllegalArgumentException, IOException {
		//baseName berechnen
		String baseName = null;

		File parentFile = inputFile.getParentFile();

		if (parentFile == null) {
			// FileSystemroot
			baseName = inputFile.getCanonicalPath();
		} else {
			baseName = parentFile.getCanonicalPath();
		}

		if (!baseName.endsWith(File.separator)) {
			baseName += File.separator;
		}

		//Zip-Methode aufrufen
		zip(inputFile, zipFile, overwrite, baseName);
	}

	/**
	 * Liefert den InputStream einer im ZIP-Archiv liegenden Datei
	 *
	 * @param zipFile das ZIP-Archiv
	 * @param fileName der Datei- bzw. auch Pfadname
	 *
	 * @return InputStream der im ZIP-Archiv liegenden Datei, falls vorhanden,
	 *         sonst null
	 *
	 * @throws IOException
	 */
	public static final InputStream getUnzippedStream(ZipFile zipFile, String fileName) throws IOException {
		java.util.Enumeration zipEntries = zipFile.entries();

		ZipEntry zipEntry;

		while (zipEntries.hasMoreElements()) {
			zipEntry = (ZipEntry) zipEntries.nextElement();

			String entryName = zipEntry.getName().replace('\\', '/');

			if (entryName.equals(fileName)) {
				return new VKCInputStream(new BufferedInputStream(zipFile.getInputStream(zipEntry)), zipEntry.getSize());
			}
		}

		return null;
	}

	private static void printUsage() {
		System.out.println("\nusage:\n\n");
		System.out.println("    ZipUtils -unzip <zipfile> <destination directory>\n");
		System.out.println("    ZipUtils -zip   <file|directory> <destination file>");
	}

	public static void main(String[] argv) {
		VKCLogManager.init("c:/temp/test_", ".log");
		VKCLogManager.setEnableLogging(true);
		VKCLogManager.setLogToConsole(true);

		try {
			if (argv.length != 3) {
				printUsage();

				return;
			}

			if (argv[0].equals("-unzip")) {
				ZipUtils.unzip(new File(argv[1]), new File(argv[2]), true);
			} else if (argv[0].equals("-zip")) {
				ZipUtils.zip(new File(argv[1]), new File(argv[2]), true);
			} else {
				printUsage();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public static final void sevenZip(File source, File target) throws IOException {
		if (source != null && target != null) {
			String src = target.getParentFile().getCanonicalPath() + File.separator + new SimpleDateFormat("-yyyy-MM-dd-kk-mm-ss").format(new Date()) + ".7z";
			File file = new File(src);
			String appDir = WebShare.getWebApplicationDir().getCanonicalPath() + File.separator + "WEB-INF" + File.separator + "sevenzip" + File.separator + VKCUtils.getOSIdent() + File.separator;
			Process p = Runtime.getRuntime().exec(appDir + "7za a \"" + file.getCanonicalPath() + "\" \"" + source.getCanonicalPath() + "\"");
			
			//Folgenede Anweisungen sind zwingend erforderlich, da sonst 7za blockiert wird
			BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()) );
			
			while (in.readLine() != null) {
			}
			
			file.renameTo(target);
		}
	}

	
	
	/**
	 * Speichert mitgeliverte Files in angegebene Zip-Datei
	 * 
	 * @param source Liste mit FileObjekten die zu einem Archiv hinzugefügt werden müssen
	 * @param target Zielarchiv-File
	 * @throws IOException
	 */
	public static final void sevenZip(List source, File target) throws IOException {
		if (source != null && !source.isEmpty() && target != null) {
			String src = target.getParentFile().getCanonicalPath() + File.separator + new SimpleDateFormat("-yyyy-MM-dd-kk-mm-ss").format(new Date()) + ".7z";
			Iterator iterSource = source.iterator();
			String sourceFiles = "";
			while(iterSource.hasNext()){
				File sourceFile = (File) iterSource.next();				
				sourceFiles+="\""+sourceFile.getCanonicalPath()+"\" ";
			}
			File file = new File(src);
			String appDir = WebShare.getWebApplicationDir().getCanonicalPath() + File.separator + "WEB-INF" + File.separator + "sevenzip" + File.separator + VKCUtils.getOSIdent() + File.separator;
			Process p = Runtime.getRuntime().exec(appDir + "7za a \"" + file.getCanonicalPath() +"\" "+ sourceFiles );
			
			//Folgenede Anweisungen sind zwingend erforderlich, da sonst 7za blockiert wird
			BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()) );
			
			while (in.readLine() != null) {				
			}
			
			file.renameTo(target);
		}
	}
	
	public static final boolean sevenUnZip(File source, File target) {
		if (source != null && target != null) {
			
			String appDir;
			try {
				appDir = WebShare.getWebApplicationDir().getCanonicalPath() + File.separator + "WEB-INF" + File.separator + "sevenzip" + File.separator + VKCUtils.getOSIdent() + File.separator;
				String command = appDir + "7za x \"" + source.getCanonicalPath() + "\" -o\"" + target.getCanonicalPath()+"\" -y -aoa";
				//String command = appDir + "7za t \"" + source.getCanonicalPath()+"\" -r";
				Process p = Runtime.getRuntime().exec(command);
				
				//Folgenede Anweisungen sind zwingend erforderlich, da sonst 7za blockiert wird
				BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()) );
				
				while (in.readLine() != null) {
					//System.out.println(in.readLine());
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
			
			return true;
		}
		return false;
	}
}