/*******************************************************************************
 * Copyright (c) 2009, 2017 Cloudsmith Inc. and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Cloudsmith Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.touchpoint.natives.actions;

import java.io.*;
import java.util.ArrayList;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.engine.Profile;
import org.eclipse.equinox.internal.p2.touchpoint.natives.*;
import org.eclipse.equinox.p2.engine.spi.ProvisioningAction;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.osgi.util.NLS;

/**
 * Copies from PARM_COPY_SOURCE to PARAM_COPY_TARGET
 * The optional parameter PARAM_COPY_OVERWRITE overwrites and existing file if set to true, else
 * and existing file with the same name is an error. The default is false.
 * If the source is a directory, a merge copy to the target is performed.
 * Copy will copy files and directories (recursively).
 *  
 */
public class CopyAction extends ProvisioningAction {
	public static final String ID = "cp"; //$NON-NLS-1$

	@Override
	public IStatus execute(Map<String, Object> parameters) {
		return copy(parameters, true);
	}

	/** Perform the copy.
	 * 
	 * @param parameters action parameters
	 * @param restoreable  flag indicating if the operation should be backed up
	 * @return status
	 */
	public static IStatus copy(Map<String, Object> parameters, boolean restoreable) {
		String target = (String) parameters.get(ActionConstants.PARM_COPY_TARGET);
		IBackupStore backupStore = restoreable ? (IBackupStore) parameters.get(NativeTouchpoint.PARM_BACKUP) : null;

		if (target == null)
			return new Status(IStatus.ERROR, Activator.ID, IStatus.OK, NLS.bind(Messages.param_not_set, ActionConstants.PARM_COPY_TARGET, ID), null);

		String source = (String) parameters.get(ActionConstants.PARM_COPY_SOURCE);
		if (source == null)
			return new Status(IStatus.ERROR, Activator.ID, IStatus.OK, NLS.bind(Messages.param_not_set, ActionConstants.PARM_COPY_SOURCE, ID), null);

		String overwrite = (String) parameters.get(ActionConstants.PARM_COPY_OVERWRITE);
		Profile profile = (Profile) parameters.get(ActionConstants.PARM_PROFILE);
		IInstallableUnit iu = (IInstallableUnit) parameters.get(ActionConstants.PARM_IU);

		String originalSource = source;
		if (source.equals(ActionConstants.PARM_AT_ARTIFACT)) {
			String artifactLocation = (String) parameters.get(NativeTouchpoint.PARM_ARTIFACT_LOCATION);
			if (artifactLocation == null) {
				IArtifactKey artifactKey = (IArtifactKey) parameters.get(NativeTouchpoint.PARM_ARTIFACT);
				return Util.createError(NLS.bind(Messages.artifact_not_available, artifactKey));
			}
			source = artifactLocation;
		}

		File sourceFile = new File(source);
		File targetFile = new File(target);
		File[] copiedFiles = null;
		try {
			copiedFiles = mergeCopy(sourceFile, targetFile, Boolean.parseBoolean(overwrite), backupStore);
		} catch (IOException e) {
			return new Status(IStatus.ERROR, Activator.ID, IStatus.OK, NLS.bind(Messages.copy_failed, sourceFile.getPath()), e);
		}
		// keep copied file in the profile as memento for CleanupCopy
		StringBuffer copiedFileNameBuffer = new StringBuffer();
		for (File copiedFile : copiedFiles) {
			copiedFileNameBuffer.append(copiedFile.getAbsolutePath()).append(ActionConstants.PIPE);
		}

		profile.setInstallableUnitProperty(iu, "copied" + ActionConstants.PIPE + originalSource + ActionConstants.PIPE + target, copiedFileNameBuffer.toString()); //$NON-NLS-1$

		return Status.OK_STATUS;
	}

	@Override
	public IStatus undo(Map<String, Object> parameters) {
		return CleanupcopyAction.cleanupcopy(parameters, false);
	}

	/**
	 * Merge-copy file or directory.
	 * @param source
	 * @param target
	 * @param overwrite
	 * @throws IOException
	 */
	private static File[] mergeCopy(File source, File target, boolean overwrite, IBackupStore backupStore) throws IOException {
		ArrayList<File> copiedFiles = new ArrayList<>();
		xcopy(copiedFiles, source, target, overwrite, backupStore);
		return copiedFiles.toArray(new File[copiedFiles.size()]);
	}

	/**
	 * Merge-copy file or directory.
	 * @param copiedFiles - ArrayList where copied files are collected
	 * @param source
	 * @param target
	 * @param overwrite
	 * @throws IOException
	 */
	private static void xcopy(ArrayList<File> copiedFiles, File source, File target, boolean overwrite, IBackupStore backupStore) throws IOException {
		if (!source.exists())
			throw new IOException("Source: " + source + "does not exists"); //$NON-NLS-1$//$NON-NLS-2$

		if (source.isDirectory()) {
			if (target.exists() && target.isFile()) {
				if (!overwrite)
					throw new IOException("Target: " + target + " already exists"); //$NON-NLS-1$//$NON-NLS-2$
				if (backupStore != null)
					backupStore.backup(target);
				else
					target.delete();
			}
			if (!target.exists())
				target.mkdirs();
			copiedFiles.add(target);
			File[] children = source.listFiles();
			if (children == null)
				throw new IOException("Error while retrieving children of directory: " + source); //$NON-NLS-1$
			for (File child : children) {
				xcopy(copiedFiles, child, new File(target, child.getName()), overwrite, backupStore);
			}
			return;
		}
		if (target.exists() && !overwrite)
			throw new IOException("Target: " + target + " already exists"); //$NON-NLS-1$//$NON-NLS-2$

		if (!target.getParentFile().exists() && !target.getParentFile().mkdirs())
			throw new IOException("Target: Path " + target.getParent() + " could not be created"); //$NON-NLS-1$//$NON-NLS-2$

		try {
			Util.copyStream(new FileInputStream(source), true, new FileOutputStream(target), true);
		} catch (IOException e) {
			// get the original IOException to the log
			e.printStackTrace();
			throw new IOException("Error while copying:" + source.getAbsolutePath()); //$NON-NLS-1$
		}
		copiedFiles.add(target);
	}
}
