package org.tmatesoft.svn.core.wc2;

import java.util.*;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNExternal;

/**
 * Represents copy operation.
 * Copies each source in <code>sources</code> to operation's <code>target</code>
 * representing working copy path, 
 * or converts a disjoint working copy to a copied one,
 * or does virtual copy (see below).
 * 
 * <ul>
 * <li>
 * <b> If <code>disjoint</code> and <code>virtual</code> are <code>false</code>:</b>
 * 
 * <p/>
 * If multiple <code>sources</code> are given, <code>target</code> must be a
 * directory, and <code>sources</code> will be copied as children of
 * directory.
 * 
 * <p/>
 * Each <code>src</code> in <code>sources</code> must be files or
 * directories under version control, or URLs of a versioned item in the
 * repository. If <code>sources</code> has multiple items, they must be all
 * repository URLs or all working copy paths.
 * 
 * <p/>
 * The parent of <code>target</code> must already exist.
 * 
 * <p/>
 * If <code>sources</code> has only one item, attempts to copy it to
 * <code>target</code>. If <code>failWhenDstExists</code> is <code>false</code> 
 * and <code>target</code> already exists,
 * attempts to copy the item as a child of <code>target</code>. If
 * <code>failWhenDstExists</code> is <code>true</code>
 * and <code>target</code> already exists, throws an {@link SVNException} with
 * the {@link SVNErrorCode#FS_ALREADY_EXISTS} error code.
 * 
 * <p/>
 * If <code>sources</code> has multiple items, and
 * <code>failWhenDstExists</code> is <code>false</code>,
 * all <code>sources</code> are copied as children of <code>target</code>. If
 * any child of <code>target</code> already exists with the same name any item
 * in <code>sources</code>, throws an {@link SVNException} with the
 * {@link SVNErrorCode#FS_ALREADY_EXISTS} error code.
 * 
 * <p/>
 * If <code>sources</code> has multiple items, and
 * <code>failWhenDstExists</code> is <code>true</code>,
 * throws an {@link SVNException} with the
 * {@link SVNErrorCode#CLIENT_MULTIPLE_SOURCES_DISALLOWED}.
 * 
 * <p/>
 * If the caller's {@link org.tmatesoft.svn.core.wc.ISVNEventHandler} is non-<code>null</code>, invokes 
 * it for each item added at the new location.
 * 
 * <p/>
 * This method is just a variant of a local add operation, where
 * <code>sources</code> are scheduled for addition as copies. No changes
 * will happen to the repository until a commit occurs. This scheduling can
 * be removed with {@link SvnRevert}.
 *
 * <p/>
 * Note: this routine requires repository access only when sources are URLs.
 * 
 * </li>
 * 
 * <li>
 * 
 * <b> If <code>disjoint</code> is <code>true</code>:</b>
 * 
 * <p/>
 * <code>Targets</code> represent the roots of the working copies located in another working copies.
 * 
 * <p/> 
 * This copy operation uses only <code>sources</code> as operation's parameters.
 * 
 * <p/>
 * Note: this routine does not require repository access. However if it's
 * performed on an old format working copy where repository root urls were
 * not written, the routine will connect to the repository to fetch the
 * repository root urls.
 * 
 * {@link #run()} throws {@link SVNException} in the following cases:
 *             <ul>
 *             <li/>exception with {@link SVNErrorCode#UNSUPPORTED_FEATURE}
 *             error code - if <code>target</code> is either not a
 *             directory, or has no parent at all; if the current local
 *             filesystem parent of <code>target</code> is actually a
 *             child of it in the repository 
 *             <li/>exception with
 *             {@link SVNErrorCode#ENTRY_EXISTS} error code - if <code>
 *             target</code> is not a disjoint working copy, i.e. there is
 *             already a versioned item under the parent path of <code>
 *             target</code>; if <code>target</code> is not in the
 *             repository yet (has got a schedule for addition flag) 
 *             <li/>
 *             exception with {@link SVNErrorCode#WC_INVALID_SCHEDULE} error
 *             code - if <code>target</code> is not from the same
 *             repository as the parent directory; if the parent of <code>
 *             target</code> is scheduled for deletion; if <code>target
 *             </code> is scheduled for deletion 
 *             </ul>
 * 
 * </li>
 * <li>
 * 
 * <b> If <code>virtual</code> is <code>true</code>:</b>
 * 
 * <p/>
 * Copies/moves administrative version control information of a source files
 * to administrative information of a destination file. For example, if you
 * have manually copied/moved a source files to a <code>target</code> one (manually means
 * just in the filesystem, not using version control operations) and then
 * would like to turn this copying/moving into a complete version control
 * copy or move operation, use this method that will finish all the work for
 * you - it will copy/move all the necessary administrative information
 * (kept in the source <i>.svn</i> directory) to the <code>target</code> <i>.svn</i>
 * directory.
 * <p>
 * In that case when you have your files copied/moved in the filesystem, you
 * can not perform standard (version control) copying/moving - since the
 * <code>target</code> already exists and the source may be already deleted. Use this
 * method to overcome that restriction.
 * 
 * <p/>
 * This operation uses <code>sources</code> and <code>move</code> parameters.
 * If <code>move</code> is <code>true</code> then completes moving
 * <code>src</code> to <code>target</code>, otherwise completes copying <code>src</code> to <code>dst</code>
 * {@link #run()} throws {@link SVNException} if one of the following is true:
 *             <ul>
 *             <li><code>move = true</code>
 *             and <code>src</code> still exists <li><code>target</code> does
 *             not exist <li><code>target</code> is a directory <li><code>src
 *             </code> is a directory <li><code>src</code> is not under
 *             version control <li><code>target</code> is already under version
 *             control <li>if <code>src</code> is copied but not scheduled
 *             for addition, and SVNKit is not able to locate the copied
 *             directory root for <code>src</code>
 *             </ul>
 * </li>
 * </ul>
 *             
 * @author TMate Software Ltd.
 * @version 1.7
 * @see SvnCopySource
 */

public class SvnCopy extends SvnOperation<Void> {
    
    private Collection<SvnCopySource> sources = new HashSet<SvnCopySource>();
    private boolean move;
    private boolean makeParents;
    private boolean failWhenDstExist;
    private boolean ignoreExternals;
    private boolean virtual;
    private boolean disjoint;
    private boolean allowMixedRevisions;
    private boolean metadataOnly;
    private boolean pinExternals;
    private Map<SvnTarget, List<SVNExternal>> externalsToPin;

    protected SvnCopy(SvnOperationFactory factory) {
        super(factory);
        this.sources = new HashSet<SvnCopySource>();
        this.allowMixedRevisions = true;
    }

    /**
     * Returns operation's all copy sources, object containing information about what to copy.
     * 
     * @return the copy sources of the operation, unmodifiable
     */
    public Collection<SvnCopySource> getSources() {
        return Collections.unmodifiableCollection(sources);
    }
    
    /**
     * Adds copy source information to the operation
     * 
     * @param source copy source information 
     */
    public void addCopySource(SvnCopySource source) {
        if (source != null) {
            this.sources.add(source);
        }
    }
    
    /**
     * Gets whether to do copy as move operation (delete, then add with history).
     * 
     * @return <code>true</code> if move operation should be done, otherwise <code>false</code>
     */
    public boolean isMove() {
        return move;
    }

    /**
     * Sets whether to do copy as move operation (delete, then add with history).
     * 
     * @param isMove <code>true</code> if move operation should be done, otherwise <code>false</code>
     */
    public void setMove(boolean isMove) {
        this.move = isMove;
    }
    
    /**
     * Gets whether to make parent folders if don't exist.
     * 
     * @return <code>true</code> if non-existent parent directories should be created, otherwise <code>false</code>
     */
    public boolean isMakeParents() {
        return makeParents;
    }

    /**
     * Sets whether to make parent folders if don't exist.
     * 
     * @param isMakeParents <code>true</code> if non-existent parent directories should be created, otherwise <code>false</code>
     */
    public void setMakeParents(boolean isMakeParents) {
        this.makeParents = isMakeParents;
    }

    /**
     * Gets whether to fail if <code>target</code> already exists.
     * 
     * @return <code>true</code> if fail when <code>target</code> already exists, otherwise <code>false</code>
     * @see SvnRemoteCopy
     */
    public boolean isFailWhenDstExists() {
        return failWhenDstExist;
    }

    /**
     * Sets whether to fail if <code>target</code> already exists.
     * 
     * @param isFailWhenDstExist <code>true</code> if fail when <code>target</code> already exists, otherwise <code>false</code>
     * @see SvnRemoteCopy
     */
    public void setFailWhenDstExists(boolean isFailWhenDstExist) {
        this.failWhenDstExist = isFailWhenDstExist;
    }

    /**
     * Returns whether to ignore externals definitions.
     * 
     * @return <code>true</code> if externals definitions should be ignored, otherwise <code>false</code>
     */
    public boolean isIgnoreExternals() {
        return ignoreExternals;
    }

    /**
     * Sets whether to ignore externals definitions.
     * 
     * @param ignoreExternals <code>true</code> if externals definitions should be ignored, otherwise <code>false</code>
     */
    public void setIgnoreExternals(boolean ignoreExternals) {
        this.ignoreExternals = ignoreExternals;
    }

    /**
     * Returns whether copy is virtual copy.
     * 
     * @return <code>true</code> if it is virtual copy, otherwise <code>false</code>
     */
    public boolean isVirtual() {
        return virtual;
    }

    /**
     * Sets whether copy is virtual copy.
     * 
     * @param virtual <code>true</code> if it is virtual copy, otherwise <code>false</code>
     */
    public void setVirtual(boolean virtual) {
        this.virtual = virtual;
    }

    /**
     * Returns whether copy is disjoint working copy.
     * 
     * @return <code>true</code> if it is disjoint working copy, otherwise <code>false</code>
     */
    public boolean isDisjoint() {
        return disjoint;
    }

    /**
     * Sets whether copy is disjoint working copy.
     * 
     * @param disjoint <code>true</code> if it is disjoint working copy, otherwise <code>false</code>
     */
    public void setDisjoint(boolean disjoint) {
        this.disjoint = disjoint;
    }

    /**
     * Gets whether the operation changes working copy
     * @return <code>true</code> if the operation changes the working copy, otherwise <code>false</code>
     */
    @Override
    public boolean isChangesWorkingCopy() {
        return true;
    }

    /**
     * Gets whether the coping of directories with mixed revisions is allowed
     * @return <code>true</code> if coping of directories with mixed revisions is allowed
     */
    public boolean isAllowMixedRevisions() {
        return allowMixedRevisions;
    }

    /**
     * Sets whether the coping of directories with mixed revisions is allowed
     *
     * @param allowMixedRevisions <code>true</code> if coping of directories with mixed revisions is allowed
     */
    public void setAllowMixedRevisions(boolean allowMixedRevisions) {
        this.allowMixedRevisions = allowMixedRevisions;
    }

    public boolean isMetadataOnly() {
        return metadataOnly;
    }

    public void setMetadataOnly(boolean metadataOnly) {
        this.metadataOnly = metadataOnly;
    }

    public boolean isPinExternals() {
        return pinExternals;
    }

    public void setPinExternals(boolean pinExternals) {
        this.pinExternals = pinExternals;
    }

    public Map<SvnTarget, List<SVNExternal>> getExternalsToPin() {
        return externalsToPin;
    }

    public void setExternalsToPin(Map<SvnTarget, List<SVNExternal>> externalsToPin) {
        this.externalsToPin = externalsToPin;
    }
}
