/*******************************************************************************
 * Copyright (c) 2007, 2011 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.wst.server.core.internal;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.server.core.internal.tar.TarEntry;
import org.eclipse.wst.server.core.internal.tar.TarInputStream;
/**
 * 
 */
public class InstallableRuntime2 implements IInstallableRuntime {
	private IConfigurationElement element;
	private byte[] BUFFER = null;

	// Default sizes (infinite logarithmic progress will be used when default is employed)
	private int DEFAULT_DOWNLOAD_SIZE = 10000000;
	private int DEFAULT_FILE_COUNT = 1000;

	public InstallableRuntime2(IConfigurationElement element) {
		super();
		this.element = element;
	}

	/**
	 * 
	 * @return the id
	 */
	public String getId() {
		try {
			return element.getAttribute("id");
		} catch (Exception e) {
			return null;
		}
	}

	public String getName() {
		return getArchivePath();
	}

	public String getArchiveUrl() {
		try {
			return element.getAttribute("archiveUrl");
		} catch (Exception e) {
			// ignore
		}
		return null;
	}

	public String getArchivePath() {
		try {
			return element.getAttribute("archivePath");
		} catch (Exception e) {
			// ignore
		}
		return null;
	}

	public int getArchiveSize() {
		try {
			String size = element.getAttribute("archiveSize");
			return Integer.parseInt(size);
		} catch (Exception e) {
			// ignore
		}
		return -1;
	}

	public int getFileCount() {
		try {
			String size = element.getAttribute("fileCount");
			return Integer.parseInt(size);
		} catch (Exception e) {
			// ignore
		}
		return -1;
	}

	public String getLicenseURL() {
		try {
			return element.getAttribute("licenseUrl");
		} catch (Exception e) {
			// ignore
		}
		return null;
	}

	/*
	 * @see IInstallableRuntime#getLicense(IProgressMonitor)
	 */
	public String getLicense(IProgressMonitor monitor) throws CoreException {
		URL url = null;
		ByteArrayOutputStream out = null;
		try {
			String licenseURL = getLicenseURL();
			if (licenseURL == null)
				return null;
			
			url = new URL(licenseURL);
			InputStream in = url.openStream();
			out = new ByteArrayOutputStream();
			copyWithSize(in, out, null, 0);
			return new String(out.toByteArray());
		} catch (Exception e) {
			if (Trace.WARNING) {
				Trace.trace(Trace.STRING_WARNING, "Error loading license", e);
			}
			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0,
					NLS.bind(Messages.errorInstallingServer, e.getLocalizedMessage()), e));
		} finally {
			try {
				if (out != null)
					out.close();
			} catch (IOException e) {
				// ignore
			}
		}
	}

	/*
	 * @see IInstallableRuntime#install(IPath)
	 */
	public void install(final IPath path) {
		Job installRuntimeJob = new Job(Messages.jobInstallingRuntime) {
			public boolean belongsTo(Object family) {
				return ServerPlugin.PLUGIN_ID.equals(family);
			}
			
			protected IStatus run(IProgressMonitor monitor) {
				try {
					install(path, monitor);
				} catch (CoreException ce) {
					return ce.getStatus();
				}
				
				return Status.OK_STATUS;
			}
		};
		
		installRuntimeJob.schedule();
	}

	private void copyWithSize(InputStream in, OutputStream out, IProgressMonitor monitor, int size) throws IOException {
		if (BUFFER == null)
			BUFFER = new byte[8192];
		SubMonitor progress = SubMonitor.convert(monitor, size);
		int r = in.read(BUFFER);
		while (r >= 0) {
			out.write(BUFFER, 0, r);
			progress.worked(r);
			r = in.read(BUFFER);
		}
	}

	private void download(InputStream in, OutputStream out, IProgressMonitor monitor, String name, int size) throws IOException {
		if (BUFFER == null)
			BUFFER = new byte[8192];
		
		String msg = NLS.bind((size > 0) ? Messages.taskDownloadSizeKnown : Messages.taskDownloadSizeUnknown,
				new Object [] { name, "{0}", Integer.toString(size / 1024) });
		SubMonitor progress = SubMonitor.convert(monitor, NLS.bind(msg, "0"), (size > 0) ? size : DEFAULT_DOWNLOAD_SIZE);
		
		int r = in.read(BUFFER);
		int total = 0;
		int lastTotal = 0;
		while (r >= 0) {
			out.write(BUFFER, 0, r);
			total += r;
			if (total >= lastTotal + 8192) {
				lastTotal = total;
				progress.subTask(NLS.bind(msg, Integer.toString(lastTotal / 1024)));
			}
			progress.worked(r);
			// if size is not known, use infinite logarithmic progress
			if (size <= 0)
				progress.setWorkRemaining(DEFAULT_DOWNLOAD_SIZE);
			
			if (progress.isCanceled())
				break;
			r = in.read(BUFFER);
		}
	}

	/*
	 * @see IInstallableRuntime#install(IPath, IProgressMonitor)
	 */
	public void install(IPath path, IProgressMonitor monitor) throws CoreException {
		SubMonitor progress = SubMonitor.convert(monitor, 1000);
		URL url = null;
		File temp = null;
		try {
			url = new URL(getArchiveUrl());
			temp = File.createTempFile("runtime", "");
			temp.deleteOnExit();
		} catch (IOException e) {
			if (monitor != null)
				monitor.done();
			if (Trace.WARNING) {
				Trace.trace(Trace.STRING_WARNING, "Error creating url and temp file", e);
			}
			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0,
				NLS.bind(Messages.errorInstallingServer, e.getLocalizedMessage()), e));
		}
		String name = url.getPath();
		int slashIdx = name.lastIndexOf('/');
		if (slashIdx >= 0)
			name = name.substring(slashIdx + 1);
		
		int archiveSize = getArchiveSize();
		
		// download
		FileOutputStream fout = null;
		try {
			InputStream in = url.openStream();
			fout = new FileOutputStream(temp);
			download(in, fout, progress.newChild(500), name, archiveSize);
			progress.setWorkRemaining(500);
		} catch (Exception e) {
			if (monitor != null)
				monitor.done();
			if (Trace.WARNING) {
				Trace.trace(Trace.STRING_WARNING, "Error downloading runtime", e);
			}
			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0,
				NLS.bind(Messages.errorInstallingServer, e.getLocalizedMessage()), e));
		} finally {
			try {
				if (fout != null)
					fout.close();
			} catch (IOException e) {
				// ignore
			}
		}
		if (progress.isCanceled())
			throw new CoreException(Status.CANCEL_STATUS);
		
		FileInputStream in = null;
		try {
			in = new FileInputStream(temp);
			if (name.endsWith("zip"))
				unzip(in, path, progress.newChild(500));
			else if (name.endsWith("tar"))
				untar(in, path, progress.newChild(500));
			else if (name.endsWith("tar.gz")) {
				File tarFile = File.createTempFile("runtime", ".tar");
				tarFile.deleteOnExit();
				String tarName = name;
				if (slashIdx >= 0)
					tarName = name.substring(0, name.length() - 3);
				
				progress.subTask(NLS.bind(Messages.taskUncompressing, tarName));
				int tempSize = Integer.MAX_VALUE;
				if (temp.length() < Integer.MAX_VALUE)
					tempSize = (int)temp.length();
				
				ungzip(in, tarFile, progress.newChild(250), tempSize);
				progress.setWorkRemaining(250);
				if (!progress.isCanceled()) {
					in = new FileInputStream(tarFile);
					untar(in, path, progress.newChild(250));
				}
			}
		} catch (Exception e) {
			if (Trace.SEVERE) {
				Trace.trace(Trace.STRING_SEVERE, "Error uncompressing runtime", e);
			}
			throw new CoreException(new Status(IStatus.ERROR, ServerPlugin.PLUGIN_ID, 0,
				NLS.bind(Messages.errorInstallingServer, e.getLocalizedMessage()), e));
		} finally {
			try {
				if (in != null)
					in.close();
			} catch (IOException e) {
				// ignore
			}
			progress.done();
		}
	}

	/**
	 * Unzip the input stream into the given path.
	 * 
	 * @param in
	 * @param path
	 * @param monitor
	 * @throws IOException
	 */
	private void unzip(InputStream in, IPath path, IProgressMonitor monitor) throws IOException {
		int fileCnt = getFileCount();
		SubMonitor progress = SubMonitor.convert(monitor, (fileCnt > 0) ? fileCnt : DEFAULT_FILE_COUNT);
		String archivePath = getArchivePath();
		BufferedInputStream bin = new BufferedInputStream(in);
		ZipInputStream zin = new ZipInputStream(bin);
		ZipEntry entry = zin.getNextEntry();
		while (entry != null) {
			String name = entry.getName();
			progress.subTask(NLS.bind(Messages.taskUncompressing, name));
			if (archivePath != null && name.startsWith(archivePath)) {
				name = name.substring(archivePath.length());
				if (name.length() > 1)
					name = name.substring(1);
			}
			
			if (name != null && name.length() > 0) {
				if (entry.isDirectory())
					path.append(name).toFile().mkdirs();
				else {
					FileOutputStream fout = new FileOutputStream(path.append(name).toFile());
					copyWithSize(zin, fout, progress.newChild(1), (int)entry.getSize());
					fout.close();
					// if count is not known, use infinite logarithmic progress
					if (fileCnt <= 0)
						progress.setWorkRemaining(DEFAULT_FILE_COUNT);
				}
			}
			zin.closeEntry();
			entry = zin.getNextEntry();
		}
		zin.close();
	}

	/**
	 * Untar the input stream into the given path.
	 * 
	 * @param in
	 * @param path
	 * @param monitor
	 * @throws IOException
	 */
	protected void untar(InputStream in, IPath path, IProgressMonitor monitor) throws IOException {
		int fileCnt = getFileCount();
		SubMonitor progress = SubMonitor.convert(monitor, (fileCnt > 0) ? fileCnt : 500);
		String archivePath = getArchivePath();
		BufferedInputStream bin = new BufferedInputStream(in);
		TarInputStream zin = new TarInputStream(bin);
		TarEntry entry = zin.getNextEntry();
		while (entry != null) {
			String name = entry.getName();
			progress.subTask(NLS.bind(Messages.taskUncompressing, name));
			if (archivePath != null && name.startsWith(archivePath)) {
				name = name.substring(archivePath.length());
				if (name.length() > 1)
					name = name.substring(1);
			}
			
			if (name != null && name.length() > 0) {
				if (entry.getFileType() == TarEntry.DIRECTORY)
					path.append(name).toFile().mkdirs();
				else {
					File dir = path.append(name).removeLastSegments(1).toFile();
					if (!dir.exists())
						dir.mkdirs();
					
					FileOutputStream fout = new FileOutputStream(path.append(name).toFile());
					copyWithSize(zin, fout, progress.newChild(1), (int)entry.getSize());
					fout.close();
					if (fileCnt <= 0)
						progress.setWorkRemaining(500);
				}
			}
			entry = zin.getNextEntry();
		}
		zin.close();
	}

	protected void ungzip(InputStream in, File tarFile, IProgressMonitor monitor, int size) throws IOException {
		GZIPInputStream gzin = null;
		FileOutputStream fout = null;
		try {
			gzin = new GZIPInputStream(in);
			fout = new FileOutputStream(tarFile);
			copyWithSize(gzin, fout, monitor, size);
		} finally {
			if (gzin != null) {
				try {
					gzin.close();
				} catch (IOException e) {
					// ignore
				}
				if (fout != null) {
					try {
						fout.close();
					} catch (IOException e) {
						// ignore
					}
				}
			}
		}
	}

	public String toString() {
		return "InstallableRuntime2[" + getId() + "]";
	}
}