/*
 * ====================================================================
 * 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.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
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.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.internal.wc16.SVNBasicDelegate;
import org.tmatesoft.svn.core.wc2.SvnGetInfo;
import org.tmatesoft.svn.core.wc2.SvnInfo;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.ISVNDebugLog;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * The <b>SVNBasicClient</b> is the base class of all <b>SVN</b>*<b>Client</b>
 * classes that provides a common interface and realization.
 *
 * <p>
 * All of <b>SVN</b>*<b>Client</b> classes use inherited methods of
 * <b>SVNBasicClient</b> to access Working Copies metadata, to create a driver
 * object to access a repository if it's necessary, etc. In addition
 * <b>SVNBasicClient</b> provides some interface methods - such as those that
 * allow you to set your {@link ISVNEventHandler event handler}, obtain run-time
 * configuration options, and others.
 *
 * @version 1.3
 * @author TMate Software Ltd.
 * @since 1.2
 */
public class SVNBasicClient {

    private static final String SVNKIT_WC_17_PROPERTY = "svnkit.wc.17";
    private static final String SVNKIT_WC_17_DEFAULT = "true";
    private static final String SVNKIT_WC_17_EXPECTED = "true";

    private SvnOperationFactory operationFactory;
    private boolean ignoreExternals;
    private boolean leaveConflictsUnresolved;
    private ISVNPathListHandler pathListHandler;
    private ISVNDebugLog debugLog;

    protected SVNBasicClient(ISVNAuthenticationManager authManager, ISVNOptions options) {
        if (authManager == null) {
            authManager = SVNWCUtil.createDefaultAuthenticationManager();
        }
        if (options == null) {
            options = SVNWCUtil.createDefaultOptions(true);
        }
        this.operationFactory = new SvnOperationFactory();
        setOptions(options);
        this.operationFactory.setAuthenticationManager(authManager);
        this.operationFactory.setRepositoryPool(new DefaultSVNRepositoryPool(authManager, options));
        this.operationFactory.setAutoDisposeRepositoryPool(true);

        initDefaults();
    }

    protected SVNBasicClient(ISVNRepositoryPool pool, ISVNOptions options) {
        boolean autoDisposeRepositoryPool = false;
        if (pool == null) {
            pool = new DefaultSVNRepositoryPool(SVNWCUtil.createDefaultAuthenticationManager(), options);
            autoDisposeRepositoryPool = true;
        }
        if (options == null) {
            options = SVNWCUtil.createDefaultOptions(true);
        }
        this.operationFactory = new SvnOperationFactory();
        setOptions(options);
        this.operationFactory.setRepositoryPool(pool);
        this.operationFactory.setAutoDisposeRepositoryPool(autoDisposeRepositoryPool);
        initDefaults();
    }

    protected SVNBasicClient(SvnOperationFactory of) {
        this.operationFactory = of;
        setOptions(of.getOptions());
        initDefaults();
    }

    protected void initDefaults() {
        setPathListHandler(null);
        setDebugLog(null);
        setEventPathPrefix(null);
        setEventHandler(null);
    }

    public static boolean isWC17Supported() {
        return SVNKIT_WC_17_EXPECTED.equalsIgnoreCase(System.getProperty(SVNKIT_WC_17_PROPERTY, SVNKIT_WC_17_DEFAULT));
    }

    protected static SVNBasicDelegate dontWC17Support() throws SVNException {
        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_UNSUPPORTED_FORMAT);
        SVNErrorManager.error(err, SVNLogType.CLIENT);
        return null;
    }

    /**
     * Gets run-time configuration options used by this object.
     *
     * @return the run-time options being in use
     */
    public ISVNOptions getOptions() {
        return getOperationsFactory().getOptions();
    }

    /**
     * Sets run-time global configuration options to this object.
     *
     * @param options
     *            the run-time configuration options
     */
    public void setOptions(ISVNOptions options) {
        if (options == null) {
            options = SVNWCUtil.createDefaultOptions(true);
        }
        getOperationsFactory().setOptions(options);
    }

    /**
     * Sets externals definitions to be ignored or not during operations.
     *
     * <p>
     * For example, if external definitions are set to be ignored then a
     * checkout operation won't fetch them into a Working Copy.
     *
     * @param ignore
     *            <span class="javakeyword">true</span> to ignore externals
     *            definitions, <span class="javakeyword">false</span> - not to
     * @see #isIgnoreExternals()
     */
    public void setIgnoreExternals(boolean ignore) {
        this.ignoreExternals = ignore;
    }

    /**
     * Determines if externals definitions are ignored.
     *
     * @return <span class="javakeyword">true</span> if ignored, otherwise <span
     *         class="javakeyword">false</span>
     * @see #setIgnoreExternals(boolean)
     */
    public boolean isIgnoreExternals() {
        return this.ignoreExternals;
    }

    /**
     * Sets (or unsets) all conflicted working files to be untouched by update
     * and merge operations.
     *
     * <p>
     * By default when a file receives changes from the repository that are in
     * conflict with local edits, an update operation places two sections for
     * each conflicting snatch into the working file one of which is a user's
     * local edit and the second is the one just received from the repository.
     * Like this:
     *
     * <pre class="javacode">
     * <<<<<<< .mine
     * user's text
     * =======
     * received text
     * >>>>>>> .r2
     * </pre>
     *
     * <br />
     * Also the operation creates three temporary files that appear in the same
     * directory as the working file. Now if you call this method with
     * <code>leave</code> set to <span class="javakeyword">true</span>, an
     * update will still create temporary files but won't place those two
     * sections into your working file. And this behaviour also concerns merge
     * operations: any merging to a conflicted file will be prevented. In
     * addition if there is any registered event handler for an
     * <b>SVNDiffClient</b> or <b>SVNUpdateClient</b> instance then the handler
     * will be dispatched an event with the status type set to
     * {@link SVNStatusType#CONFLICTED_UNRESOLVED}.
     *
     * <p>
     * The default value is <span class="javakeyword">false</span> until a
     * caller explicitly changes it calling this method.
     *
     * @param leave
     *            <span class="javakeyword">true</span> to prevent conflicted
     *            files from merging (all merging operations will be skipped),
     *            otherwise <span class="javakeyword">false</span>
     * @see #isLeaveConflictsUnresolved()
     * @see SVNUpdateClient
     * @see SVNDiffClient
     * @see ISVNEventHandler
     * @deprecated this method should not be used anymore
     */
    public void setLeaveConflictsUnresolved(boolean leave) {
        this.leaveConflictsUnresolved = leave;
    }

    /**
     * Determines if conflicted files should be left unresolved preventing from
     * merging their contents during update and merge operations.
     *
     * @return <span class="javakeyword">true</span> if conflicted files are set
     *         to be prevented from merging, <span
     *         class="javakeyword">false</span> if there's no such restriction
     * @see #setLeaveConflictsUnresolved(boolean)
     * @deprecated this method should not be used anymore
     */
    public boolean isLeaveConflictsUnresolved() {
        return leaveConflictsUnresolved;
    }

    /**
     * Sets an event handler for this object. This event handler will be
     * dispatched {@link SVNEvent} objects to provide detailed information about
     * actions and progress state of version control operations performed by
     * <b>do</b>*<b>()</b> methods of <b>SVN</b>*<b>Client</b> classes.
     *
     * @param dispatcher
     *            an event handler
     */
    public void setEventHandler(ISVNEventHandler dispatcher) {
        getOperationsFactory().setEventHandler(dispatcher);
    }

    /**
     * Sets a path list handler implementation to this object.
     *
     * @param handler
     *            handler implementation
     * @since 1.2.0
     */
    public void setPathListHandler(ISVNPathListHandler handler) {
        this.pathListHandler = handler;
    }
    
    public ISVNPathListHandler getPathListHandler() {
        return this.pathListHandler;
    }

    /**
     * Sets a logger to write debug log information to.
     *
     * @param log
     *            a debug logger
     */
    public void setDebugLog(ISVNDebugLog log) {
        if (log == null) {
            log = SVNDebugLog.getDefaultLog();
        }
        this.debugLog = log;
    }

    /**
     * Returns the debug logger currently in use.
     *
     * <p>
     * If no debug logger has been specified by the time this call occurs, a
     * default one (returned by
     * <code>org.tmatesoft.svn.util.SVNDebugLog.getDefaultLog()</code>) will be
     * created and used.
     *
     * @return a debug logger
     */
    public ISVNDebugLog getDebugLog() {
        return debugLog;
    }

    /**
     * Returns the root of the repository.
     *
     * <p/>
     * If <code>path</code> is not <span class="javakeyword">null</span> and
     * <code>pegRevision</code> is either {@link SVNRevision#WORKING} or
     * {@link SVNRevision#BASE}, then attempts to fetch the repository root from
     * the working copy represented by <code>path</code>. If these conditions
     * are not met or if the repository root is not recorded in the working
     * copy, then a repository connection is established and the repository root
     * is fetched from the session.
     *
     * <p/>
     * When fetching the repository root from the working copy and if
     * <code>access</code> is <span class="javakeyword">null</span>, a new
     * working copy access will be created and the working copy will be opened
     * non-recursively for reading only.
     *
     * <p/>
     * All necessary cleanup (session or|and working copy close) will be
     * performed automatically as the routine finishes.
     *
     * @param path
     *            working copy path
     * @param url
     *            repository url
     * @param pegRevision
     *            revision in which the target is valid
     * @param adminArea
     *            working copy administrative area object
     * @param access
     *            working copy access object
     * @return repository root url
     * @throws SVNException
     * @since 1.2.0
     *
     * @deprecated
     */
    public SVNURL getReposRoot(File path, SVNURL url, SVNRevision pegRevision, SVNAdminArea adminArea, SVNWCAccess access) throws SVNException {
        SVNBasicDelegate delegate = new SVNBasicDelegate(getOperationsFactory().getAuthenticationManager(), getOperationsFactory().getOptions());
        return delegate.getReposRoot(path, url, pegRevision, adminArea, access);
    }

    /**
     * Returns the root of the repository.
     *
     * <p/>
     * If <code>path</code> is not <span class="javakeyword">null</span> and
     * <code>pegRevision</code> is either {@link SVNRevision#WORKING} or
     * {@link SVNRevision#BASE}, then attempts to fetch the repository root from
     * the working copy represented by <code>path</code>. If these conditions
     * are not met or if the repository root is not recorded in the working
     * copy, then a repository connection is established and the repository root
     * is fetched from the session.
     *
     * <p/>
     * All necessary cleanup (session or|and working copy close) will be
     * performed automatically as the routine finishes.
     *
     * @param path
     *            working copy path
     * @param url
     *            repository url
     * @param pegRevision
     *            revision in which the target is valid
     * @return repository root url
     * @throws SVNException
     * @since 1.2.0
     *
     */
    public SVNURL getReposRoot(File path, SVNURL url, SVNRevision pegRevision) throws SVNException {
        SvnGetInfo info = getOperationsFactory().createGetInfo();
        if (path != null) {
            info.setSingleTarget(SvnTarget.fromFile(path, pegRevision));
        } else {
            info.setSingleTarget(SvnTarget.fromURL(url, pegRevision));
        }
        info.setDepth(SVNDepth.EMPTY);
        SvnInfo i = info.run();
        return i != null ? i.getRepositoryRootUrl() : null;
    }

    /**
     * Removes or adds a path prefix. This method is not intended for users
     * (from an API point of view).
     *
     * @param prefix
     *            a path prefix
     * @deprecated
     */
    public void setEventPathPrefix(String prefix) {
    }
    
    public SvnOperationFactory getOperationsFactory() {
        return this.operationFactory;
    }

}