/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.core.helpers;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.zip.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;

public class FileUtils {

	/**
	 * Unzip from a File to an output directory.
	 */
	public static File[] unzipFile(File zipFile, File outputDir) throws IOException {
		InputStream in = new FileInputStream(zipFile);
		try {
			return unzipStream(in, zipFile.length(), outputDir, null, null);
		} catch (IOException e) {
			// add the file name to the message
			throw new IOException(NLS.bind(Messages.Util_Error_Unzipping, zipFile, e.getMessage()));
		} finally {
			in.close();
		}
	}

	/**
	 * Unzip from a File to an output directory, with progress indication.
	 * monitor may be null.
	 */
	public static File[] unzipFile(File zipFile, File outputDir, String taskName, IProgressMonitor monitor) throws IOException {
		InputStream in = new FileInputStream(zipFile);
		try {
			return unzipStream(in, zipFile.length(), outputDir, taskName, monitor);
		} catch (IOException e) {
			// add the file name to the message
			throw new IOException(NLS.bind(Messages.Util_Error_Unzipping, zipFile, e.getMessage()));
		} finally {
			in.close();
		}
	}

	/**
	 * Unzip from a URL to an output directory, with progress indication.
	 * monitor may be null.
	 */
	public static File[] unzipURL(URL zipURL, File outputDir, String taskName, IProgressMonitor monitor) throws IOException {
		int size = zipURL.openConnection().getContentLength();
		InputStream in = zipURL.openStream();
		try {
			return unzipStream(in, size, outputDir, taskName, monitor);
		} catch (IOException e) {
			// add the URL to the message
			throw new IOException(NLS.bind(Messages.Util_Error_Unzipping, zipURL, e.getMessage()));
		} finally {
			in.close();
		}
	}

	/**
	 * Unzip from an InputStream to an output directory.
	 */
	public static File[] unzipStream(InputStream stream, long size, File outputDir, String taskName, IProgressMonitor monitor) throws IOException {
		InputStream is = monitor == null ? stream : stream; // new ProgressMonitorInputStream(stream, size, size, taskName, monitor); TODO Commented code
		ZipInputStream in = new ZipInputStream(new BufferedInputStream(is));
		ZipEntry ze = in.getNextEntry();
		if (ze == null) {
			// There must be at least one entry in a zip file.
			// When there isn't getNextEntry returns null.
			in.close();
			throw new IOException(Messages.Util_Invalid_Zip_File_Format);
		}
		ArrayList unzippedFiles = new ArrayList();
		do {
			File outFile = new File(outputDir, ze.getName());
			unzippedFiles.add(outFile);
			if (ze.isDirectory()) {
				outFile.mkdirs();
			} else {
				if (outFile.exists()) {
					outFile.delete();
				} else {
					outFile.getParentFile().mkdirs();
				}
				try {
					copyStream(in, false, new FileOutputStream(outFile), true);
				} catch (FileNotFoundException e) {
					// TEMP: ignore this for now in case we're trying to replace
					// a running eclipse.exe
				}
				outFile.setLastModified(ze.getTime());
			}
			in.closeEntry();
		} while ((ze = in.getNextEntry()) != null);
		in.close();

		return (File[]) unzippedFiles.toArray(new File[unzippedFiles.size()]);
	}

	// Delete empty directories under dir, including dir itself.
	public static void deleteEmptyDirs(File dir) throws IOException {
		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i += 1) {
				deleteEmptyDirs(files[i]);
			}
			dir.getCanonicalFile().delete();
		}
	}

	// Delete the given file whether it is a file or a directory
	public static void deleteAll(File file) {
		if (!file.exists())
			return;
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			if (files != null)
				for (int i = 0; i < files.length; i++)
					deleteAll(files[i]);
		}
		file.delete();
	}

	/**
	 * Copy an input stream to an output stream.
	 * Optionally close the streams when done.
	 * Return the number of bytes written.
	 */
	public static int copyStream(InputStream in, boolean closeIn, OutputStream out, boolean closeOut) throws IOException {
		try {
			int written = 0;
			byte[] buffer = new byte[16 * 1024];
			int len;
			while ((len = in.read(buffer)) != -1) {
				out.write(buffer, 0, len);
				written += len;
			}
			return written;
		} finally {
			try {
				if (closeIn) {
					in.close();
				}
			} finally {
				if (closeOut) {
					out.close();
				}
			}
		}
	}

	public static void zip(File[] sourceFiles, File destinationArchive) throws IOException {
		zip(sourceFiles, destinationArchive, true);
	}

	public static void zip(File[] sourceFiles, File destinationArchive, boolean includeRoot) throws IOException {
		ZipOutputStream output = new ZipOutputStream(new FileOutputStream(destinationArchive));
		try {
			for (int i = 0; i < sourceFiles.length; i++)
				if (sourceFiles[i].isDirectory())
					zipDir(output, sourceFiles[i], includeRoot ? new Path(sourceFiles[i].getName()) : new Path("")); //$NON-NLS-1$
				else
					zipFile(output, sourceFiles[i], new Path(""));//$NON-NLS-1$
		} finally {
			try {
				// Note! This call will fail miserably if no entries were added to the zip!
				// The file is left open after an exception is thrown.
				output.close();
			} catch (IOException e) {
				// ignore
			}
		}
	}

	/*
	 * Zip the contents of the given directory into the zip file represented by
	 * the given zip stream. Prepend the given prefix to the file paths.
	 */
	private static void zipDir(ZipOutputStream output, File source, IPath prefix) {
		File[] files = source.listFiles();
		for (int i = 0; i < files.length; i++) {
			try {
				if (files[i].isFile())
					zipFile(output, files[i], prefix);
				else
					zipDir(output, files[i], prefix.append(files[i].getName()));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * Add the given file to the zip file represented by the specified stream.
	 * Prepend the given prefix to the path of the file.
	 */
	private static void zipFile(ZipOutputStream output, File sourceFile, IPath prefix) throws IOException {
		InputStream input = new FileInputStream(sourceFile);
		try {
			ZipEntry zipEntry = new ZipEntry(prefix.append(sourceFile.getName()).toString());
			zipEntry.setTime(sourceFile.lastModified());
			output.putNextEntry(zipEntry);
			copyStream(input, true, output, false);
		} finally {
			try {
				input.close();
			} catch (IOException e) {
				// ignore
			}
			try {
				output.closeEntry();
			} catch (IOException e) {
				// ignore
			}
		}
	}
}
