/*
 * ====================================================================
 * Copyright (c) 2004-2012 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core.wc;

import java.io.File;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.wc16.SVNMoveClient16;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc2.SvnCopy;
import org.tmatesoft.svn.core.wc2.SvnCopySource;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;

/**
 * The <b>SVNMoveClient</b> provides an extra client-side functionality over
 * standard (i.e. compatible with the SVN command line client) move operations.
 * This class helps to overcome the SVN limitations regarding move operations.
 * Using <b>SVNMoveClient</b> you can easily:
 * <ul>
 * <li>move versioned items to other versioned ones within the same Working
 * Copy, what even allows to replace items scheduled for deletion, or those that
 * are missing but are still under version control and have a node kind
 * different from the node kind of the source (!);
 * <li>move versioned items belonging to one Working Copy to versioned items
 * that belong to absolutely different Working Copy;
 * <li>move versioned items to unversioned ones;
 * <li>move unversioned items to versioned ones;
 * <li>move unversioned items to unversioned ones;
 * <li>revert any of the kinds of moving listed above;
 * <li>complete a copy/move operation for a file, that is if you have manually
 * copied/moved a versioned file to an unversioned file in a Working copy, you
 * can run a 'virtual' copy/move on these files to copy/move all the necessary
 * administrative version control information.
 * </ul>
 * 
 * @version 1.3
 * @author TMate Software Ltd.
 * @since 1.2
 */
public class SVNMoveClient extends SVNBasicClient {

    /**
     * Constructs and initializes an <b>SVNMoveClient</b> object with the
     * specified run-time configuration and authentication drivers.
     * 
     * <p>
     * If <code>options</code> is <span class="javakeyword">null</span>, then
     * this <b>SVNMoveClient</b> will be using a default run-time configuration
     * driver which takes client-side settings from the default SVN's run-time
     * configuration area but is not able to change those settings (read more on
     * {@link ISVNOptions} and {@link SVNWCUtil}).
     * 
     * <p>
     * If <code>authManager</code> is <span class="javakeyword">null</span>,
     * then this <b>SVNMoveClient</b> will be using a default authentication and
     * network layers driver (see
     * {@link SVNWCUtil#createDefaultAuthenticationManager()}) which uses
     * server-side settings and auth storage from the default SVN's run-time
     * configuration area (or system properties if that area is not found).
     * 
     * @param authManager
     *            an authentication and network layers driver
     * @param options
     *            a run-time configuration options driver
     */
    public SVNMoveClient(ISVNAuthenticationManager authManager, ISVNOptions options) {
        super(authManager, options);
    }

    /**
     * Constructs and initializes an <b>SVNMoveClient</b> object with the
     * specified run-time configuration and repository pool object.
     * 
     * <p/>
     * If <code>options</code> is <span class="javakeyword">null</span>, then
     * this <b>SVNMoveClient</b> will be using a default run-time configuration
     * driver which takes client-side settings from the default SVN's run-time
     * configuration area but is not able to change those settings (read more on
     * {@link ISVNOptions} and {@link SVNWCUtil}).
     * 
     * <p/>
     * If <code>repositoryPool</code> is <span class="javakeyword">null</span>,
     * then {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory} will be used
     * to create {@link SVNRepository repository access objects}.
     * 
     * @param repositoryPool
     *            a repository pool object
     * @param options
     *            a run-time configuration options driver
     */
    public SVNMoveClient(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
        super(repositoryPool, options);
    }

    public SVNMoveClient(SvnOperationFactory of) {
        super(of);
    }

    /**
     * Moves a source item to a destination one.
     * 
     * <p>
     * <code>dst</code> should not exist. Furher it's considered to be versioned
     * if its parent directory is under version control, otherwise
     * <code>dst</code> is considered to be unversioned.
     * 
     * <p>
     * If both <code>src</code> and <code>dst</code> are unversioned, then
     * simply moves <code>src</code> to <code>dst</code> in the filesystem.
     * 
     * <p>
     * If <code>src</code> is versioned but <code>dst</code> is not, then
     * exports <code>src</code> to <code>dst</code> in the filesystem and
     * removes <code>src</code> from version control.
     * 
     * <p>
     * If <code>dst</code> is versioned but <code>src</code> is not, then moves
     * <code>src</code> to <code>dst</code> (even if <code>dst</code> is
     * scheduled for deletion).
     * 
     * <p>
     * If both <code>src</code> and <code>dst</code> are versioned and located
     * within the same Working Copy, then moves <code>src</code> to
     * <code>dst</code> (even if <code>dst</code> is scheduled for deletion), or
     * tries to replace <code>dst</code> with <code>src</code> if the former is
     * missing and has a node kind different from the node kind of the source.
     * If <code>src</code> is scheduled for addition with history,
     * <code>dst</code> will be set the same ancestor URL and revision from
     * which the source was copied. If <code>src</code> and <code>dst</code> are
     * located in different Working Copies, then this method copies
     * <code>src</code> to <code>dst</code>, tries to put the latter under
     * version control and finally removes <code>src</code>.
     * 
     * @param src
     *            a source path
     * @param dst
     *            a destination path
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li><code>dst</code> already exists <li><code>src</code> does
     *             not exist
     *             </ul>
     */
    public void doMove(File src, File dst) throws SVNException {
        final SvnWcGeneration srcGeneration = SvnOperationFactory.detectWcGeneration(src, true, false);
        final SvnWcGeneration dstGeneration = SvnOperationFactory.detectWcGeneration(dst, true, true);
        if (srcGeneration == SvnWcGeneration.V17 && dstGeneration == SvnWcGeneration.V17) {
            final SvnCopy copy = getOperationsFactory().createCopy();
            copy.setMove(true);
            copy.setSingleTarget(SvnTarget.fromFile(dst));
            copy.addCopySource(SvnCopySource.create(SvnTarget.fromFile(src), SVNRevision.WORKING));
            copy.setFailWhenDstExists(true);
            copy.run();
        } else {
            try {
                final SVNMoveClient16 oldClient = new SVNMoveClient16(getOperationsFactory().getAuthenticationManager(), getOptions());
                oldClient.doMove(src, dst);
            } catch (SVNException e) {
                if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_UNSUPPORTED_FORMAT) {
                    final SvnCopy cp = getOperationsFactory().createCopy();
                    cp.setMove(true);
                    cp.setSingleTarget(SvnTarget.fromFile(dst));
                    cp.addCopySource(SvnCopySource.create(SvnTarget.fromFile(src), SVNRevision.WORKING));
                    cp.setFailWhenDstExists(true);
                    cp.run();
                }
            }
        }
    }

    /**
     * Reverts a previous move operation back. Provided in pair with
     * {@link #doMove(File, File) doMove()} and used to roll back move
     * operations. In this case <code>src</code> is considered to be the target
     * of the previsous move operation, and <code>dst</code> is regarded to be
     * the source of that same operation which have been moved to
     * <code>src</code> and now is to be restored.
     * 
     * <p>
     * <code>dst</code> could exist in that case if it has been a WC directory
     * that was scheduled for deletion during the previous move operation.
     * Furher <code>dst</code> is considered to be versioned if its parent
     * directory is under version control, otherwise <code>dst</code> is
     * considered to be unversioned.
     * 
     * <p>
     * If both <code>src</code> and <code>dst</code> are unversioned, then
     * simply moves <code>src</code> back to <code>dst</code> in the filesystem.
     * 
     * <p>
     * If <code>src</code> is versioned but <code>dst</code> is not, then
     * unmoves <code>src</code> to <code>dst</code> in the filesystem and
     * removes <code>src</code> from version control.
     * 
     * <p>
     * If <code>dst</code> is versioned but <code>src</code> is not, then first
     * tries to make a revert on <code>dst</code> - if it has not been committed
     * yet, it will be simply reverted. However in the case <code>dst</code> has
     * been already removed from the repository, <code>src</code> will be copied
     * back to <code>dst</code> and scheduled for addition. Then
     * <code>src</code> is removed from the filesystem.
     * 
     * <p>
     * If both <code>src</code> and <code>dst</code> are versioned then the
     * following situations are possible:
     * <ul>
     * <li>If <code>dst</code> is still scheduled for deletion, then it is
     * reverted back and <code>src</code> is scheduled for deletion.
     * <li>in the case if <code>dst</code> exists but is not scheduled for
     * deletion, <code>src</code> is cleanly exported to <code>dst</code> and
     * removed from version control.
     * <li>if <code>dst</code> and <code>src</code> are from different
     * repositories (appear to be in different Working Copies), then
     * <code>src</code> is copied to <code>dst</code> (with scheduling
     * <code>dst</code> for addition, but not with history since copying is made
     * in the filesystem only) and removed from version control.
     * <li>if both <code>dst</code> and <code>src</code> are in the same
     * repository (appear to be located in the same Working Copy) and:
     * <ul style="list-style-type: lower-alpha">
     * <li>if <code>src</code> is scheduled for addition with history, then
     * copies <code>src</code> to <code>dst</code> specifying the source
     * ancestor's URL and revision (i.e. the ancestor of the source is the
     * ancestor of the destination);
     * <li>if <code>src</code> is already under version control, then copies
     * <code>src</code> to <code>dst</code> specifying the source URL and
     * revision as the ancestor (i.e. <code>src</code> itself is the ancestor of
     * <code>dst</code>);
     * <li>if <code>src</code> is just scheduled for addition (without history),
     * then simply copies <code>src</code> to <code>dst</code> (only in the
     * filesystem, without history) and schedules <code>dst</code> for addition;
     * </ul>
     * then <code>src</code> is removed from version control.
     * </ul>
     * 
     * @param src
     *            a source path
     * @param dst
     *            a destination path
     * @throws SVNException
     *             if <code>src</code> does not exist
     * 
     */
    // move that considered as move undo.
    public void undoMove(File src, File dst) throws SVNException {
        SVNMoveClient16 oldClient = new SVNMoveClient16(getOperationsFactory().getAuthenticationManager(), getOptions());
        oldClient.undoMove(src, dst);
    }

    /**
     * Copies/moves administrative version control information of a source file
     * to administrative information of a destination file. For example, if you
     * have manually copied/moved a source file to a target 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 target <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
     * target already exists and the source may be already deleted. Use this
     * method to overcome that restriction.
     * 
     * @param src
     *            a source file path (was copied/moved to <code>dst</code>)
     * @param dst
     *            a destination file path
     * @param move
     *            if <span class="javakeyword">true</span> then completes moving
     *            <code>src</code> to <code>dst</code>, otherwise completes
     *            copying <code>src</code> to <code>dst</code>
     * @throws SVNException
     *             if one of the following is true:
     *             <ul>
     *             <li><code>move = </code><span class="javakeyword">true</span>
     *             and <code>src</code> still exists <li><code>dst</code> does
     *             not exist <li><code>dst</code> is a directory <li><code>src
     *             </code> is a directory <li><code>src</code> is not under
     *             version control <li><code>dst</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>
     */
    public void doVirtualCopy(File src, File dst, boolean move) throws SVNException {
        final SvnCopy copy = getOperationsFactory().createCopy();
        copy.addCopySource(SvnCopySource.create(SvnTarget.fromFile(src), SVNRevision.WORKING));
        copy.setSingleTarget(SvnTarget.fromFile(dst));
        copy.setMove(move);
        copy.setVirtual(true);
        copy.run();
    }

}
