/*
 * ====================================================================
 * 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 java.util.Collection;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc2.*;

/**
 * The <b>SVNStatusClient</b> class provides methods for obtaining information
 * on the status of Working Copy items. The functionality of
 * <b>SVNStatusClient</b> corresponds to the <code>'svn status'</code> command of the native SVN
 * command line client.
 * 
 * <p>
 * One of the main advantages of <b>SVNStatusClient</b> lies in that fact that
 * for each processed item the status information is collected and put into an
 * <b>SVNStatus</b> object. Further there are two ways how this object can be
 * passed to a developer (depending on the version of the <b>doStatus()</b>
 * method that was invoked):
 * <ol>
 * <li>the <b>SVNStatus</b> can be passed to a developer's status handler (that
 * should implement <b>ISVNStatusHandler</b>) in which the developer retrieves
 * status information and decides how to interprete that info;
 * <li>another way is that an appropriate <b>doStatus()</b> method just returns
 * that <b>SVNStatus</b> object.
 * </ol>
 * Those methods that match the first variant can be called recursively -
 * obtaining status information for all child entries, the second variant just
 * the reverse - methods are called non-recursively and allow to get status info
 * on a single item.
 * 
 * @version 1.3
 * @author TMate Software Ltd.
 * @since 1.2
 * @see ISVNStatusHandler
 * @see SVNStatus
 * @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
 */
public class SVNStatusClient extends SVNBasicClient {

    private ISVNStatusFileProvider myFilesProvider;

    /**
     * Constructs and initializes an <b>SVNStatusClient</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>SVNStatusClient</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>SVNStatusClient</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 SVNStatusClient(ISVNAuthenticationManager authManager, ISVNOptions options) {
        super(authManager, options);
    }

    /**
     * Constructs and initializes an <b>SVNStatusClient</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>SVNStatusClient</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 SVNStatusClient(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
        super(repositoryPool, options);
    }

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

    protected void initDefaults() {
        setFilesProvider(null);
    }

    /**
     * Collects status information on Working Copy items and passes it to a
     * <code>handler</code>.
     * 
     * @param path
     *            local item's path
     * @param recursive
     *            relevant only if <code>path</code> denotes a directory: <span
     *            class="javakeyword">true</span> to obtain status info
     *            recursively for all child entries, <span
     *            class="javakeyword">false</span> only for items located
     *            immediately in the directory itself
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @param reportAll
     *            <span class="javakeyword">true</span> to collect status
     *            information on those items that are in a <i>'normal'</i> state
     *            (unchanged), otherwise <span class="javakeyword">false</span>
     * @param includeIgnored
     *            <span class="javakeyword">true</span> to force the operation
     *            to collect information on items that were set to be ignored
     *            (like <i>'--no-ignore'</i> option in the SVN client's <i>'svn
     *            status'</i> command to disregard default and
     *            <i>'svn:ignore'</i> property ignores), otherwise <span
     *            class="javakeyword">false</span>
     * @param handler
     *            a caller's status handler that will be involved in processing
     *            status information
     * @return the revision number the status information was collected against
     * @throws SVNException
     * @see ISVNStatusHandler
     * @deprecated use
     *             {@link #doStatus(File, SVNRevision, SVNDepth, boolean, boolean, boolean, boolean, ISVNStatusHandler, Collection)}
     *             instead
     */
    public long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, ISVNStatusHandler handler) throws SVNException {
        return doStatus(path, SVNRevision.HEAD, SVNDepth.fromRecurse(recursive), remote, reportAll, includeIgnored, false, handler, null);
    }

    /**
     * Collects status information on Working Copy items and passes it to a
     * <code>handler</code>.
     * 
     * <p>
     * Calling this method is equivalent to
     * 
     * <code>doStatus(path, SVNRevision.HEAD, recursive, remote, reportAll, includeIgnored, collectParentExternals, handler)</code>.
     * 
     * @param path
     *            local item's path
     * @param recursive
     *            relevant only if <code>path</code> denotes a directory: <span
     *            class="javakeyword">true</span> to obtain status info
     *            recursively for all child entries, <span
     *            class="javakeyword">false</span> only for items located
     *            immediately in the directory itself
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @param reportAll
     *            <span class="javakeyword">true</span> to collect status
     *            information on all items including those ones that are in a
     *            <i>'normal'</i> state (unchanged), otherwise <span
     *            class="javakeyword">false</span>
     * @param includeIgnored
     *            <span class="javakeyword">true</span> to force the operation
     *            to collect information on items that were set to be ignored
     *            (like <i>'--no-ignore'</i> option in the SVN client's <code>'svn status'</code>
     *            command to disregard default and <i>'svn:ignore'</i> property
     *            ignores), otherwise <span class="javakeyword">false</span>
     * @param collectParentExternals
     *            <span class="javakeyword">false</span> to make the operation
     *            ignore information on externals definitions (like
     *            <i>'--ignore-externals'</i> option in the SVN client's <code>'svn status'</code>
     *            command), otherwise <span class="javakeyword">true</span>
     * @param handler
     *            a caller's status handler that will be involved in processing
     *            status information
     * @return the revision number the status information was collected against
     * @throws SVNException
     * @deprecated use
     *             {@link #doStatus(File, SVNRevision, SVNDepth, boolean, boolean, boolean, boolean, ISVNStatusHandler, Collection)}
     *             instead
     */
    public long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, boolean collectParentExternals, final ISVNStatusHandler handler) throws SVNException {
        return doStatus(path, SVNRevision.HEAD, SVNDepth.fromRecurse(recursive), remote, reportAll, includeIgnored, collectParentExternals, handler, null);
    }

    /**
     * Collects status information on Working Copy items and passes it to a
     * <code>handler</code>.
     * 
     * @param path
     *            local item's path
     * @param revision
     *            if <code>remote</code> is <span
     *            class="javakeyword">true</span> this revision is used to
     *            calculate status against
     * @param recursive
     *            relevant only if <code>path</code> denotes a directory: <span
     *            class="javakeyword">true</span> to obtain status info
     *            recursively for all child entries, <span
     *            class="javakeyword">false</span> only for items located
     *            immediately in the directory itself
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @param reportAll
     *            <span class="javakeyword">true</span> to collect status
     *            information on all items including those ones that are in a
     *            <i>'normal'</i> state (unchanged), otherwise <span
     *            class="javakeyword">false</span>
     * @param includeIgnored
     *            <span class="javakeyword">true</span> to force the operation
     *            to collect information on items that were set to be ignored
     *            (like <i>'--no-ignore'</i> option in the SVN client's <code>'svn status'</code>
     *            command to disregard default and <i>'svn:ignore'</i> property
     *            ignores), otherwise <span class="javakeyword">false</span>
     * @param collectParentExternals
     *            <span class="javakeyword">false</span> to make the operation
     *            ignore information on externals definitions (like
     *            <i>'--ignore-externals'</i> option in the SVN client's <code>'svn status'</code>
     *            command), otherwise <span class="javakeyword">true</span>
     * @param handler
     *            a caller's status handler that will be involved in processing
     *            status information
     * @return the revision number the status information was collected against
     * @throws SVNException
     * @deprecated use
     *             {@link #doStatus(File, SVNRevision, SVNDepth, boolean, boolean, boolean, boolean, ISVNStatusHandler, Collection)}
     *             instead
     */
    public long doStatus(File path, SVNRevision revision, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, boolean collectParentExternals, final ISVNStatusHandler handler)
            throws SVNException {
        return doStatus(path, revision, SVNDepth.fromRecurse(recursive), remote, reportAll, includeIgnored, collectParentExternals, handler, null);
    }

    /**
     * Given a <code>path</code> to a working copy directory (or single file),
     * calls <code>handler</code> with a set of {@link SVNStatus} objects which
     * describe the status of the <code>path</code>, and its children (recursing
     * according to <code>depth</code>).
     * 
     * <p/>
     * If <code>reportAll</code> is set, retrieves all entries; otherwise,
     * retrieves only "interesting" entries (local modifications and/or out of
     * date).
     * 
     * <p/>
     * If <code>remote</code> is set, contacts the repository and augments the
     * status objects with information about out-of-dateness (with respect to
     * <code>revision</code>).
     * 
     * <p/>
     * If {@link #isIgnoreExternals()} returns <span
     * class="javakeyword">false</span>, then recurses into externals
     * definitions (if any exist and <code>depth</code> is either
     * {@link SVNDepth#INFINITY} or {@link SVNDepth#UNKNOWN}) after handling the
     * main target. This calls the client notification handler (
     * {@link ISVNEventHandler}) with the {@link SVNEventAction#STATUS_EXTERNAL}
     * action before handling each externals definition, and with
     * {@link SVNEventAction#STATUS_COMPLETED} after each.
     * 
     * <p/>
     * <code>changeLists</code> is a collection of <code>String</code>
     * changelist names, used as a restrictive filter on items whose statuses
     * are reported; that is, doesn't report status about any item unless it's a
     * member of one of those changelists. If <code>changeLists</code> is empty
     * (or <span class="javakeyword">null</span>), no changelist filtering
     * occurs.
     * 
     * @param path
     *            working copy path
     * @param revision
     *            if <code>remote</code> is <span
     *            class="javakeyword">true</span>, status is calculated against
     *            this revision
     * @param depth
     *            tree depth to process
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @param reportAll
     *            <span class="javakeyword">true</span> to collect status
     *            information on all items including those ones that are in a
     *            <i>'normal'</i> state (unchanged), otherwise <span
     *            class="javakeyword">false</span>
     * @param includeIgnored
     *            <span class="javakeyword">true</span> to force the operation
     *            to collect information on items that were set to be ignored
     *            (like <i>'--no-ignore'</i> option in the SVN client's <code>'svn status'</code>
     *            command to disregard default and <i>'svn:ignore'</i> property
     *            ignores), otherwise <span class="javakeyword">false</span>
     * @param collectParentExternals
     *            obsolete (not used)
     * @param handler
     *            a caller's status handler that will be involved in processing
     *            status information
     * @param changeLists
     *            collection with changelist names
     * @return returns the actual revision against which the working copy was
     *         compared; the return value is not meaningful (-1) unless
     *         <code>remote</code> is set
     * @throws SVNException
     * @since 1.2, SVN 1.5
     */
    public long doStatus(File path, SVNRevision revision, SVNDepth depth, boolean remote, boolean reportAll, boolean includeIgnored, boolean collectParentExternals, final ISVNStatusHandler handler,
            final Collection<String> changeLists) throws SVNException {
        
        final SvnGetStatus getStatus = getOperationsFactory().createGetStatus();
        getStatus.setReportExternals(!isIgnoreExternals());
        getStatus.setFileListHook(SvnCodec.fileListHook(myFilesProvider));
        getStatus.setApplicalbeChangelists(changeLists);
        getStatus.setCollectParentExternals(collectParentExternals);
        getStatus.setDepth(depth);
        getStatus.setRevision(revision);
        getStatus.setRemote(remote);
        getStatus.setReportAll(reportAll);
        getStatus.setReportIgnored(includeIgnored);
        getStatus.setSingleTarget(SvnTarget.fromFile(path));
        
        getStatus.setReceiver(new ISvnObjectReceiver<SvnStatus>() {
            public void receive(SvnTarget target, SvnStatus status) throws SVNException {
                SVNWCContext context = getStatus.getOperationFactory().getWcContext();
                handler.handleStatus(SvnCodec.status(context, status));
            }
        });
        
        getStatus.run();
        
        return getStatus.getRemoteRevision();
    }

    /**
     * Collects status information on a single Working Copy item.
     * 
     * @param path
     *            local item's path
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @return an <b>SVNStatus</b> object representing status information for
     *         the item
     * @throws SVNException
     */
    public SVNStatus doStatus(final File path, boolean remote) throws SVNException {
        return doStatus(path, remote, false);
    }

    /**
     * Collects status information on a single Working Copy item.
     * 
     * @param path
     *            local item's path
     * @param remote
     *            <span class="javakeyword">true</span> to check up the status
     *            of the item in the repository, that will tell if the local
     *            item is out-of-date (like <i>'-u'</i> option in the SVN
     *            client's <code>'svn status'</code> command), otherwise <span
     *            class="javakeyword">false</span>
     * @param collectParentExternals
     *            <span class="javakeyword">false</span> to make the operation
     *            ignore information on externals definitions (like
     *            <i>'--ignore-externals'</i> option in the SVN client's <code>'svn status'</code>
     *            command), otherwise <span class="javakeyword">false</span>
     * @return an <b>SVNStatus</b> object representing status information for
     *         the item
     * @throws SVNException
     */
    public SVNStatus doStatus(File path, boolean remote, boolean collectParentExternals) throws SVNException {
        final SVNStatus[] result = new SVNStatus[] {null};
        final File absPath = path.getAbsoluteFile();
        ISVNStatusHandler handler = new ISVNStatusHandler() {
            public void handleStatus(SVNStatus status) {
                if (absPath.equals(status.getFile())) {
                    if (result[0] != null && result[0].getContentsStatus() == SVNStatusType.STATUS_EXTERNAL && absPath.isDirectory()) {
                        result[0] = status;
                        result[0].markExternal();
                    } else if (result[0] == null) {
                        result[0] = status;
                    }
                }
            }
        };
        doStatus(absPath, SVNRevision.HEAD, SVNDepth.EMPTY, remote, true, true, collectParentExternals, handler, null);
        return result[0];
    }

    public void setFilesProvider(ISVNStatusFileProvider filesProvider) {
        myFilesProvider = filesProvider;
    }
}
