package org.tmatesoft.svn.core.wc2;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.wc.SVNExternal;
import org.tmatesoft.svn.core.wc.DefaultSVNCommitParameters;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.ISVNExternalsHandler;
import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.hooks.ISvnExternalsHandler;

 /**
 * Represents copy to repository operation. 
 * Copies each source in <code>sources</code> to operation's <code>target</code> destination.
 * <code>Target</code> should represent repository URL.
 * 
 * <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
 * <code>target</code>.
 * 
 * <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/>
 * {@link ISVNAuthenticationManager Authentication manager} (whether
 * provided directly through the appropriate constructor or in an
 * {@link ISVNRepositoryPool} instance) and {@link #getCommitHandler()
 * commit handler} are used to immediately attempt to commit the copy action
 * in the repository.
 * 
 * <p/>
 * If the caller's {@link ISVNEventHandler} is non-<code>null</code>, invokes it for each item added at the
 * new location.
 * 
 * <p/>
 * When performing a wc-to-url copy (tagging|branching from a working copy)
 * it's possible to fix revisions of external working copies (if any) which
 * are located within the working copy being copied. For example, imagine
 * you have a working copy and on one of its subdirectories you set an 
 * <code>"svn:externals"</code> property which does not contain
 * a revision number. Suppose you have made a tag from your working copy and
 * in some period of time a user checks out that tag. It could have happened
 * that the external project has evolved since the tag creation moment and
 * the tag version is no more compatible with it. So, the user has a broken
 * project since it will not compile because of the API incompatibility
 * between the two versions of the external project: the HEAD one and the
 * one existed in the moment of the tag creation. That is why it appears
 * useful to fix externals revisions during a wc-to-url copy. To enable
 * externals revision fixing a user should implement
 * {@link ISVNExternalsHandler}. The user's implementation
 * {@link ISVNExternalsHandler#handleExternal(File,SVNURL,SVNRevision,SVNRevision,String,SVNRevision)}
 * method will be called on every external that will be met in the working
 * copy. If the user's implementation returns non-<code>null</code> 
 * external revision, it's compared with the
 * revisions fetched from the external definition. If they are different,
 * the user's revision will be written in the external definition of the
 * tag. Otherwise if the returned revision is equal to the revision from the
 * external definition or if the user's implementation returns <code>null</code> 
 * for that external, it will be skipped (i.e. left as is, unprocessed).
 * 
 * <p/>
 * Note: this routine requires repository access.
 * 
 * {@link #run()} returns {@link SVNCommitInfo} commit information information about the new committed revision.
 * 
 * @author TMate Software Ltd.
 * @version 1.7
 */
public class SvnRemoteCopy extends AbstractSvnCommit {

     private boolean move;
     private boolean makeParents;
     private boolean failWhenDstExists;
     private ISvnExternalsHandler externalsHandler;
     private ISvnCommitParameters commitParameters;
     private boolean disableLocalModifications;
     private boolean pinExternals;
     private Map<SvnTarget, List<SVNExternal>> externalsToPin;

    private Collection<SvnCopySource> sources;
    
    protected SvnRemoteCopy(SvnOperationFactory factory) {
        super(factory);
        sources = new ArrayList<SvnCopySource>();
    }

    /**
     * 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 move <code>true</code> if move operation should be done, otherwise <code>false</code>
     */
    public void setMove(boolean move) {
        this.move = move;
    }

    /**
     * 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 makeParents <code>true</code> if non-existent parent directories should be created, otherwise <code>false</code>
    */
    public void setMakeParents(boolean makeParents) {
        this.makeParents = makeParents;
    }

    /**
     * Sets whether to disable local modifications.
     * 
     * @return <code>true</code> if local modifications are disabled, otherwise <code>false</code>
     * @see #setDisableLocalModifications(boolean)
     */
    public boolean isDisableLocalModifications() {
        return disableLocalModifications;
    }

    /**
     * Sets whether to disable local modifications.
     * If <code>true</code> and any local modification is found, 
     * {@link #run()} method throws {@link SVNException} exception with {@link SVNErrorCode#ILLEGAL_TARGET} code.
     * 
     * @param disableLocalModifications <code>true</code> if local modifications are disabled, otherwise <code>false</code>
     */
    public void setDisableLocalModifications(boolean disableLocalModifications) {
        this.disableLocalModifications = disableLocalModifications;
    }

    /**
     * Returns all operation's sources.
     * 
     * @return sources of the operation
     * @see SvnRemoteCopy
     */
    public Collection<SvnCopySource> getSources() {
        return sources;
    }

    /**
     * And one source to the operation's sources.
     * 
     * @param source source of the operation
     * @see SvnRemoteCopy
     */
    public void addCopySource(SvnCopySource source) {
        if (source != null) {
            this.sources.add(source);
        }
    }

    /**
     * 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 failWhenDstExists;
    }

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

    /**
     * Runs copy operation.
     * 
     * @return {@link SVNCommitInfo} commit information information about the new committed revision. 
     */
    @Override
    public SVNCommitInfo run() throws SVNException {
        return super.run();
    }

    /**
     * Gets operation's externals handler.
     * 
     * @return externals handler of the operation
     * @see SvnRemoteCopy
     */
    public ISvnExternalsHandler getExternalsHandler() {
        return externalsHandler;
    }

    /**
     * Sets operation's externals handler.
     * 
     * @param externalsHandler externals handler of the operation
     * @see SvnRemoteCopy
     */
    public void setExternalsHandler(ISvnExternalsHandler externalsHandler) {
        this.externalsHandler = externalsHandler;
    }

    /**
     * Returns operation's parameters of the commit.
     * If no user parameters were previously specified, once creates and returns
     * {@link DefaultSVNCommitParameters default} ones.
     * 
     * @return commit parameters of the operation
     * @see ISvnCommitParameters
     */
    public ISvnCommitParameters getCommitParameters() {
        return commitParameters;
    }

    /**
     * Sets operation's parameters of the commit.
     * When no parameters are set {@link DefaultSVNCommitParameters default}ones
     * are used.
     * 
     * @param commitParameters commit parameters of the operation
     * @see ISvnCommitParameters
     */
    public void setCommitParameters(ISvnCommitParameters commitParameters) {
        this.commitParameters = commitParameters;
    }

     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;
     }

     /**
      * 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 false;
     }

     @Override
     protected File getOperationalWorkingCopy() {
         SvnCopySource firstSource = getSources().iterator().next();
         if (firstSource.getSource().isLocal()) {
             return firstSource.getSource().getFile();
         }
         return null;
     }
}