package org.tmatesoft.svn.core.wc2;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * Represents checkout operation. Checks out a working copy of <code>source</code> at revision,
 * looked up at {@link SvnTarget#getPegRevision()}, using <code>target</code> as the
 * root directory of the newly checked out working copy.
 * 
 * <p/>
 * If source {@link SvnTarget#getPegRevision()}> is {@link SVNRevision#UNDEFINED} or invalid, then it
 * defaults to {@link SVNRevision#HEAD}.
 * 
 * <p/>
 * If <code>depth</depth> is {@link SVNDepth#INFINITY}, checks out fully
 * recursively. Else if it is {@link SVNDepth#IMMEDIATES}, checks out
 * source <code>target</code> and its immediate entries (subdirectories will be
 * present, but will be at depth {@link SVNDepth#EMPTY} themselves); else
 * {@link SVNDepth#FILES}, checks out source <code>target</code> and its file entries,
 * but no subdirectories; else if {@link SVNDepth#EMPTY}, checks out
 * source <code>target</code> as an empty directory at that depth, with no entries
 * present.
 * 
 * <p/>
 * If <code>depth</depth> is {@link SVNDepth#UNKNOWN}, then behave as if for
 * {@link SVNDepth#INFINITY}, except in the case of resuming a previous
 * checkout of <code>target</code> (i.e., updating), in which case uses the
 * depth of the existing working copy.
 * 
 * <p/>
 * If externals are ignored (<code>ignoreExternals</code> is <code>true</code>), doesn't process
 * externals definitions as part of this operation.
 * 
 * <p/>
 * If <code>isAllowUnversionedObstructions</code> is <code>true</code> 
 * then the checkout tolerates existing
 * unversioned items that obstruct added paths from source target. Only
 * obstructions of the same type (file or directory) as the added item are
 * tolerated. The text of obstructing files is left as-is, effectively
 * treating it as a user modification after the checkout. Working properties
 * of obstructing items are set equal to the base properties. If
 * <code>isAllowUnversionedObstructions</code> is <code>false</code> 
 * then the checkout will abort if there
 * are any unversioned obstructing items.
 * 
 * <p/>
 * If the caller's {@link ISVNEventHandler} is non-<code>null</code>, 
 * it is invoked as the checkout processes.
 * Also {@link ISVNEventHandler#checkCancelled()} will be used at various
 * places during the checkout to check whether the caller wants to stop the
 * checkout.
 * 
 * <p/>
 * This operation requires repository access (in case the repository is not
 * on the same machine, network connection is established).
 *
 * <p/>
 * {@link #run()} method returns value of the revision actually checked out from the repository.
 * 
 * <p/>
 * {@link #run()} throws {@link org.tmatesoft.svn.core.SVNException} in the following cases:
 *             <ul>
 *             <li/>exception with {@link SVNErrorCode#UNSUPPORTED_FEATURE}
 *             error code - if <code>target</code>'s URL refers to a file rather than a directory 
 *             <li/>exception with {@link SVNErrorCode#RA_ILLEGAL_URL} error code 
 *             - if <code>target</code>'s URL
 *             does not exist, or if external item at the <code>revision</code> doesn't exist, 
 *             or if external item at the <code>revision</code> is not file or a directory
 *             <li/>exception with {@link SVNErrorCode#WC_OBSTRUCTED_UPDATE} error code  
 *             - if the working copy item 
 *             is already a working copy item for a different URL 
 *             <li/>exception with {@link SVNErrorCode#WC_NODE_KIND_CHANGE} error code 
 *             - if local file is already exists with the same name that incoming added directory
 *        	   <li/>exception with {@link SVNErrorCode#ENTRY_MISSING_URL} error code 
 *        		- if working copy item that has no URL
 *       </ul>
 * 
 * @author TMate Software Ltd.
 * @version 1.7
 */
public class SvnCheckout extends AbstractSvnUpdate<Long> {
    
    private SvnTarget source;
    private int targetWorkingCopyFormat;

    protected SvnCheckout(SvnOperationFactory factory) {
        super(factory);
        targetWorkingCopyFormat = -1;
    }

    /**
    * Gets a repository location from where a working copy will be checked out.
    * 
    * @return source of repository
    */
    public SvnTarget getSource() {
        return source;
    }

    /**
     * Sets a repository location from where a working copy will be checked out.
     * 
     * @param source source of repository
     */
    public void setSource(SvnTarget source) {
        this.source = source;
    }

    @Override
    protected void ensureArgumentsAreValid() throws SVNException {
        super.ensureArgumentsAreValid();
        if (getRevision() == null) {
            setRevision(SVNRevision.UNDEFINED);
        }
        
        if (getSource() == null || !getSource().isURL() || getSource().getURL() == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_URL);
            SVNErrorManager.error(err, SVNLogType.WC);
        }

        if (!getRevision().isValid() && getFirstTarget() != null) {
            setRevision(getSource().getResolvedPegRevision());            
        }
        if (!getRevision().isValid()) {
            setRevision(SVNRevision.HEAD);
        }
        
        if (getFirstTarget() == null || getFirstTarget().getFile() == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_FILENAME, "Checkout destination path can not be NULL");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        
        if (getRevision().getNumber() < 0 && getRevision().getDate() == null && getRevision() != SVNRevision.HEAD) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
    }
    
    /**
     * 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;
    }

    public void setTargetWorkingCopyFormat(int targetWorkingCopyFormat) {
        this.targetWorkingCopyFormat = targetWorkingCopyFormat;
    }

    public int getTargetWorkingCopyFormat() {
        return targetWorkingCopyFormat;
    }
}
