/*******************************************************************************
 * Copyright (c) 2011 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.openshift.express.internal.core.util;

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.io.PrintWriter;
import java.text.MessageFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;
import org.eclipse.core.runtime.Assert;

/**
 * @author André Dietisheim
 */
public class FileUtils {

	private static final String EXT_TAR_GZ = ".tar.gz";
	private static final char SUFFIX_DELIMITER = '.';
	private static final String NUMERIC_SUFFIX_FILENAME_PATTERN = "{0}/{1}({2}){3}";
	private static final Pattern NUMERIC_SUFFIX_FILENAME_REGEX = Pattern.compile("(.*)\\([0-9]+\\)");

	private static final byte[] buffer = new byte[1024];

	public static boolean canRead(String path) {
		if (path == null) {
			return false;
		}
		return canRead(new File(path));
	}

	public static boolean canRead(File file) {
		if (file == null) {
			return false;
		}
		return file.canRead();
	}

	public static boolean canWrite(String path) {
		if (path == null) {
			return false;
		}
		return canWrite(new File(path));
	}
	
	public static boolean canWrite(File file) {
		if (file == null) {
			return false;
		}
		if (!file.exists()) {
			return canWrite(file.getParent());
		}
		return file.canWrite();
	}
	
	public static boolean exists(File file) {
		return file != null
				&& file.exists();
	}

	public static boolean isDirectory(File file) {
		return file != null
				&& file.isDirectory();
	}

	public static File getSystemTmpFolder() {
		String tmpFolder = System.getProperty("java.io.tmpdir");
		return new File(tmpFolder);
	}

	public static File getRandomTmpFolder() {
		String randomName = String.valueOf(System.currentTimeMillis());
		return new File(getSystemTmpFolder(), randomName);
	}

	/**
	 * Copies the ginve source to the given destination recursively. Overwrites
	 * existing files/directory on the destination path if told so.
	 * 
	 * @param source
	 *            the source file/directory to copy
	 * @param destination
	 *            the destination to copy to
	 * @param overwrite
	 *            overwrites existing files/directories if <code>true</code>.
	 *            Does not overwrite otherwise.
	 * @throws IOException
	 */
	public static void copy(File source, File destination, boolean overwrite) throws IOException {
		if (!exists(source)
				|| destination == null) {
			return;
		}

		if (source.isDirectory()) {
			copyDirectory(source, destination, overwrite);
		} else {
			copyFile(source, destination, overwrite);
		}
	}

	private static void copyDirectory(File source, File destination, boolean overwrite) throws IOException {
		Assert.isLegal(source != null);
		Assert.isLegal(source.isDirectory());
		Assert.isLegal(destination != null);

		destination = getDestinationDirectory(source, destination);

		if (!destination.exists()) {
			destination.mkdir();
			copyPermissions(source, destination);
		}

		for (File content : source.listFiles()) {
			if (content.isDirectory()) {
				copyDirectory(content, new File(destination, content.getName()), overwrite);
			} else {
				copyFile(content, new File(destination, content.getName()), overwrite);
			}
		}
	}

	private static File getDestinationDirectory(File source, File destination) {
		if (!source.getName().equals(destination.getName())) {
			destination = new File(destination, source.getName());
		}
		return destination;
	}

	private static void copyFile(File source, File destination, boolean overwrite) throws IOException {
		Assert.isLegal(source != null);
		Assert.isLegal(source.isFile());
		Assert.isLegal(destination != null);

		destination = getDestinationFile(source, destination);

		if (exists(destination)
				&& !overwrite) {
			return;
		}

		if (isDirectory(destination)) {
			if (!overwrite) {
				return;
			}
			destination.delete();
		}

		writeTo(source, destination);
	}

	private static File getDestinationFile(File source, File destination) {
		if (!source.getName().equals(destination.getName())) {
			destination = new File(destination, source.getName());
		}
		return destination;
	}

	private static final void writeTo(File source, File destination) throws IOException {
		Assert.isLegal(source != null);
		Assert.isLegal(destination != null);

		writeTo(new BufferedInputStream(new FileInputStream(source)), destination);
		copyPermissions(source, destination);
	}

	public static final void writeTo(String content, File destination) throws FileNotFoundException {
		PrintWriter writer = new PrintWriter(destination);
		writer.write(content);
		writer.flush();
		writer.close();
	}

	private static final void writeTo(InputStream in, File destination) throws IOException {
		Assert.isLegal(in != null);
		Assert.isLegal(destination != null);

		OutputStream out = null;
		try {
			out = new BufferedOutputStream(new FileOutputStream(destination));
			for (int read = -1; (read = in.read(buffer)) != -1;) {
				out.write(buffer, 0, read);
			}
			out.flush();
		} finally {
			silentlyClose(in);
			silentlyClose(out);
		}
	}

	/**
	 * Replicates the owner permissions from the source to the destination. Due
	 * to limitation in java6 this is the best we can do (there's no way in
	 * java6 to know if rights are due to owner or group)
	 * 
	 * @param source
	 * @param destination
	 * 
	 * @see File#canRead()
	 * @see File#setReadable(boolean)
	 * @see File#canWrite()
	 * @see File#setWritable(boolean)
	 * @see File#canExecute()
	 * @see File#setExecutable(boolean)
	 */
	private static void copyPermissions(File source, File destination) {
		Assert.isLegal(source != null);
		Assert.isLegal(destination != null);

		destination.setReadable(source.canRead());
		destination.setWritable(source.canWrite());
		destination.setExecutable(source.canExecute());
	}

	private static void silentlyClose(InputStream in) {
		try {
			if (in != null) {
				in.close();
			}
		} catch (IOException e) {
			// ignore
		}
	}

	private static void silentlyClose(OutputStream out) {
		try {
			if (out != null) {
				out.close();
			}
		} catch (IOException e) {
			// ignore
		}
	}

	public static String getParent(String filepath) {
		String parent = null;
		if (!StringUtils.isEmpty(filepath)) {
			parent = new File(filepath).getParent();
		}
		return parent;
	}

	/**
	 * Returns the given filepath with a suffix if the given filepath already
	 * exists.
	 * 
	 * @param filepath
	 * @return the filepath or filepath + numeric suffix if not available.
	 * 
	 * @see #NUMERIC_SUFFIX_FILENAME_PATTERN
	 */
	public static String getAvailableFilepath(String filepath) {
		if (StringUtils.isEmpty(filepath)) {
			return filepath;
		}
		String extension = getExtension(filepath);
		String dir = FilenameUtils.getFullPathNoEndSeparator(filepath);
		String filenameWithoutExtension = stripNumericSuffix(getBaseName(filepath));

		String newFilename = filepath;
		int i = 1;
		while (new File(newFilename).exists()) {
			newFilename = MessageFormat.format(NUMERIC_SUFFIX_FILENAME_PATTERN, dir, filenameWithoutExtension, i++, extension);
		}
		return newFilename;
	}

	/**
	 * Strips the numeric suffix off the given filepath if present. Returns the
	 * given filepath otherwise.
	 * 
	 * @param filepath
	 * @return
	 */
	private static String stripNumericSuffix(String filepath) {
		if (StringUtils.isEmpty(filepath)) {
			return filepath;
		}
		Matcher matcher = NUMERIC_SUFFIX_FILENAME_REGEX.matcher(filepath);
		if (!matcher.matches()
				|| matcher.groupCount() < 1) {
			return filepath;
		}
		return matcher.group(1);
	}

	private static String getExtension(String filename) {
		if (StringUtils.isEmpty(filename)) {
			return filename;
		}
		if(filename.endsWith(EXT_TAR_GZ)){
			return EXT_TAR_GZ;
		}
		return FilenameUtils.getExtension(filename);
	}

	/**
	 * Returns the filename without the suffix
	 * 
	 * @param filename
	 * @return
	 */
	public static String getBaseName(String filename) {
		if (StringUtils.isEmpty(filename)) {
			return filename;
		}
		if(filename.endsWith(EXT_TAR_GZ))
		{
			filename = org.apache.commons.lang.StringUtils.left(filename, filename.length() - EXT_TAR_GZ.length());
		}

		return FilenameUtils.getBaseName(filename);
	}
}
