/**
 * @copyright
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 * @endcopyright
 */

package org.tigris.subversion.javahl;

import java.util.Set;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.File;
import java.io.IOException;

/**
 * This class offers the same commands as the svnadmin commandline
 * client.
 */
public class SVNAdmin
{
    private org.apache.subversion.javahl.SVNRepos aSVNAdmin;

    /**
     * Standard empty constructor, builds just the native peer.
     */
    public SVNAdmin()
    {
        aSVNAdmin = new org.apache.subversion.javahl.SVNRepos();
        cppAddr = aSVNAdmin.getCppAddr();
    }

    /**
     * release the native peer (should not depend on finalize)
     */
    public void dispose()
    {
        aSVNAdmin.dispose();
    }

    /**
     * release the native peer (should use dispose instead)
     */
    protected void finalize()
    {
    }

    /**
     * slot for the address of the native peer. The JNI code is the only user
     * of this member
     */
    protected long cppAddr;

    /**
     * Filesystem in a Berkeley DB
     */
    public static final String BDB = "bdb";

    /**
     * Filesystem in the filesystem
     */
    public static final String FSFS = "fsfs";

    /**
     * @return Version information about the underlying native libraries.
     */
    public Version getVersion()
    {
        return new Version(
                    org.apache.subversion.javahl.NativeResources.getVersion());
    }

    /**
     * create a subversion repository.
     * @param path                  the path where the repository will been
     *                              created.
     * @param disableFsyncCommit    disable to fsync at the commit (BDB).
     * @param keepLog               keep the log files (BDB).
     * @param configPath            optional path for user configuration files.
     * @param fstype                the type of the filesystem (BDB or FSFS)
     * @throws ClientException  throw in case of problem
     */
    public void create(String path, boolean disableFsyncCommit, boolean keepLog,
                       String configPath, String fstype)
            throws ClientException
    {
        try {
            aSVNAdmin.create(new File(path), disableFsyncCommit, keepLog,
                             configPath == null ? null : new File(configPath),
                             fstype);
        } catch (org.apache.subversion.javahl.ClientException ex) {
            throw new ClientException(ex);
        }
    }

    /**
     * deltify the revisions in the repository
     * @param path              the path to the repository
     * @param start             start revision
     * @param end               end revision
     * @throws ClientException  throw in case of problem
     */
    public void deltify(String path, Revision start, Revision end)
            throws ClientException
    {
        try
        {
            aSVNAdmin.deltify(new File(path),
                              start == null ? null : start.toApache(),
                              end == null ? null : end.toApache());
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * dump the data in a repository
     * @param path              the path to the repository
     * @param dataOut           the data will be outputed here
     * @param errorOut          the messages will be outputed here
     * @param start             the first revision to be dumped
     * @param end               the last revision to be dumped
     * @param incremental       the dump will be incremental
     * @throws ClientException  throw in case of problem
     */
    public void dump(String path, OutputInterface dataOut,
                     OutputInterface errorOut, Revision start,
                     Revision end, boolean incremental)
            throws ClientException
    {
        dump(path, dataOut, errorOut, start, end, incremental, false);
    }

    /**
     * dump the data in a repository
     * @param path              the path to the repository
     * @param dataOut           the data will be outputed here
     * @param errorOut          the messages will be outputed here
     * @param start             the first revision to be dumped
     * @param end               the last revision to be dumped
     * @param incremental       the dump will be incremental
     * @param useDeltas         the dump will contain deltas between nodes
     * @throws ClientException  throw in case of problem
     * @since 1.5
     */
    public void dump(String path, OutputInterface dataOut,
                     OutputInterface errorOut, Revision start, Revision end,
                     boolean incremental, boolean useDeltas)
            throws ClientException
    {
        try
        {
            aSVNAdmin.dump(new File(path), new OutputWrapper(dataOut),
                           start == null ? null : start.toApache(),
                           end == null ? null : end.toApache(),
                           incremental, useDeltas,
                           new ReposNotifyHandler(errorOut));
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * make a hot copy of the repository
     * @param path              the path to the source repository
     * @param targetPath        the path to the target repository
     * @param cleanLogs         clean the unused log files in the source
     *                          repository
     * @throws ClientException  throw in case of problem
     */
    public void hotcopy(String path, String targetPath, boolean cleanLogs)
            throws ClientException
    {
        try
        {
            aSVNAdmin.hotcopy(new File(path), new File(targetPath), cleanLogs);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * list all logfiles (BDB) in use or not)
     * @param path              the path to the repository
     * @param receiver          interface to receive the logfile names
     * @throws ClientException  throw in case of problem
     */
    public void listDBLogs(String path, MessageReceiver receiver)
            throws ClientException
    {
        try
        {
            aSVNAdmin.listDBLogs(new File(path), receiver);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * list unused logfiles
     * @param path              the path to the repository
     * @param receiver          interface to receive the logfile names
     * @throws ClientException  throw in case of problem
     */
    public void listUnusedDBLogs(String path, MessageReceiver receiver)
            throws ClientException
    {
        try
        {
            aSVNAdmin.listUnusedDBLogs(new File(path), receiver);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * interface to receive the messages
     */
    public static interface MessageReceiver
        extends org.apache.subversion.javahl.ISVNRepos.MessageReceiver
    {
    }

    /**
     * load the data of a dump into a repository,
     * @param path              the path to the repository
     * @param dataInput         the data input source
     * @param messageOutput     the target for processing messages
     * @param ignoreUUID        ignore any UUID found in the input stream
     * @param forceUUID         set the repository UUID to any found in the
     *                          stream
     * @param relativePath      the directory in the repository, where the data
     *                          in put optional.
     * @throws ClientException  throw in case of problem
     */
    public void load(String path, InputInterface dataInput,
                     OutputInterface messageOutput, boolean ignoreUUID,
                     boolean forceUUID, String relativePath)
            throws ClientException
    {
        load(path, dataInput, messageOutput, ignoreUUID, forceUUID,
             false, false, relativePath);
    }

    /**
     * load the data of a dump into a repository,
     * @param path              the path to the repository
     * @param dataInput         the data input source
     * @param messageOutput     the target for processing messages
     * @param ignoreUUID        ignore any UUID found in the input stream
     * @param forceUUID         set the repository UUID to any found in the
     *                          stream
     * @param usePreCommitHook  use the pre-commit hook when processing commits
     * @param usePostCommitHook use the post-commit hook when processing commits
     * @param relativePath      the directory in the repository, where the data
     *                          in put optional.
     * @throws ClientException  throw in case of problem
     * @since 1.5
     */
    public void load(String path, InputInterface dataInput,
                     OutputInterface messageOutput, boolean ignoreUUID,
                     boolean forceUUID, boolean usePreCommitHook,
                     boolean usePostCommitHook, String relativePath)
            throws ClientException
    {
        try
        {
            aSVNAdmin.load(new File(path), new InputWrapper(dataInput),
                           ignoreUUID, forceUUID, usePreCommitHook,
                           usePostCommitHook, relativePath,
                           new ReposNotifyHandler(messageOutput));
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * list all open transactions in a repository
     * @param path              the path to the repository
     * @param receiver          receives one transaction name per call
     * @throws ClientException  throw in case of problem
     */
    public void lstxns(String path, MessageReceiver receiver)
            throws ClientException
    {
        try
        {
            aSVNAdmin.lstxns(new File(path), receiver);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * recover the berkeley db of a repository, returns youngest revision
     * @param path              the path to the repository
     * @throws ClientException  throw in case of problem
     */
    public long recover(String path)
            throws ClientException
    {
        try
        {
            return aSVNAdmin.recover(new File(path), null);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * remove open transaction in a repository
     * @param path              the path to the repository
     * @param transactions      the transactions to be removed
     * @throws ClientException  throw in case of problem
     */
    public void rmtxns(String path, String [] transactions)
            throws ClientException
    {
        try
        {
            aSVNAdmin.rmtxns(new File(path), transactions);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * set the log message of a revision
     * @param path              the path to the repository
     * @param rev               the revision to be changed
     * @param message           the message to be set
     * @param bypassHooks       if to bypass all repository hooks
     * @throws ClientException  throw in case of problem
     * @deprecated Use setRevProp() instead.
     */
    public void setLog(String path, Revision rev, String message,
                       boolean bypassHooks)
            throws ClientException
    {
        try
        {
            aSVNAdmin.setRevProp(new File(path),
                                 rev == null ? null : rev.toApache(),
                                 "svn:log", message,
                                 !bypassHooks, !bypassHooks);
        }
        catch (org.apache.subversion.javahl.SubversionException ex)
        {
            throw ClientException.fromException(ex);
        }
    }

    /**
     * Change the value of the revision property <code>propName</code>
     * to <code>propValue</code>.  By default, does not run
     * pre-/post-revprop-change hook scripts.
     *
     * @param path The path to the repository.
     * @param rev The revision for which to change a property value.
     * @param propName The name of the property to change.
     * @param propValue The new value to set for the property.
     * @param usePreRevPropChangeHook Whether to run the
     * <i>pre-revprop-change</i> hook script.
     * @param usePostRevPropChangeHook Whether to run the
     * <i>post-revprop-change</i> hook script.
     * @throws SubversionException If a problem occurs.
     * @since 1.5.0
     */
    public void setRevProp(String path, Revision rev, String propName,
                           String propValue, boolean usePreRevPropChangeHook,
                           boolean usePostRevPropChangeHook)
            throws SubversionException
    {
        try
        {
            aSVNAdmin.setRevProp(new File(path),
                                 rev == null ? null : rev.toApache(),
                                 propName, propValue,
                                 usePreRevPropChangeHook,
                                 usePostRevPropChangeHook);
        }
        catch (org.apache.subversion.javahl.SubversionException ex)
        {
            throw new SubversionException(ex);
        }
    }

    /**
     * Verify the repository at <code>path</code> between revisions
     * <code>start</code> and <code>end</code>.
     *
     * @param path              the path to the repository
     * @param messageOut        the receiver of all messages
     * @param start             the first revision
     * @param end               the last revision
     * @throws ClientException If an error occurred.
     */
    public void verify(String path, OutputInterface messageOut,
                       Revision start, Revision end)
            throws ClientException
    {
        try
        {
            aSVNAdmin.verify(new File(path),
                             start == null ? null : start.toApache(),
                             end == null ? null : end.toApache(),
                             new ReposNotifyHandler(messageOut));
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * list all locks in the repository
     * @param path              the path to the repository
     * @throws ClientException  throw in case of problem
     * @since 1.2
     */
    public Lock[] lslocks(String path)
            throws ClientException
    {
        try
        {
            Set<org.apache.subversion.javahl.types.Lock> aLocks =
                                                    aSVNAdmin.lslocks(
                                                        new File(path),
                                                        Depth.toADepth(
                                                              Depth.infinity));
            Lock[] locks = new Lock[aLocks.size()];

            int i = 0;
            for (org.apache.subversion.javahl.types.Lock lock : aLocks)
            {
                locks[i] = new Lock(lock);
                i++;
            }

            return locks;
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    /**
     * remove multiple locks from the repository
     * @param path              the path to the repository
     * @param locks             the name of the locked items
     * @throws ClientException  throw in case of problem
     * @since 1.2
     */
    public void rmlocks(String path, String [] locks)
            throws ClientException
    {
        try
        {
            aSVNAdmin.rmlocks(new File(path), locks);
        }
        catch (org.apache.subversion.javahl.ClientException ex)
        {
            throw new ClientException(ex);
        }
    }

    private class OutputWrapper extends OutputStream
    {
        private OutputInterface outputer;

        OutputWrapper(OutputInterface outputer)
        {
            this.outputer = outputer;
        }

        public void write(int b) throws IOException
        {
            outputer.write(new byte[]{ (byte) ( b & 0xFF) });
        }

        public void write(byte[] b) throws IOException
        {
            outputer.write(b);
        }

        public void close() throws IOException
        {
            outputer.close();
        }
    }

    private class InputWrapper extends InputStream
    {
        private InputInterface inputer;

        InputWrapper(InputInterface inputer)
        {
            this.inputer = inputer;
        }

        public int read() throws IOException
        {
            byte[] b = new byte[1];
            if (inputer.read(b) > 0)
                return b[0];
            else
                return -1;
        }

        public int read(byte[] b) throws IOException
        {
            return inputer.read(b);
        }

        public void close() throws IOException
        {
            inputer.close();
        }
    }

    private class ReposNotifyHandler
        implements org.apache.subversion.javahl.callback.ReposNotifyCallback
    {
        private OutputInterface outputer;

        public ReposNotifyHandler(OutputInterface outputer)
        {
            this.outputer = outputer;
        }

        public void onNotify(org.apache.subversion.javahl.ReposNotifyInformation
                                                                           info)
        {
            String val;

            switch (info.getAction())
            {
                case warning:
                    val = info.getWarning();
                    break;

                case dump_rev_end:
                    val = "* Dumped revision " + info.getRevision() + ".\n";
                    break;

                case verify_rev_end:
                    val = "* Verified revision " + info.getRevision() + ".\n";
                    break;

                case load_txn_committed:
                    if (info.getOldRevision() == Revision.SVN_INVALID_REVNUM)
                        val = "\n------- Committed revision " +
                              info.getNewRevision() + " >>>\n\n";
                    else
                        val = "\n------- Committed new rev " +
                               info.getNewRevision() +
                              " (loaded from original rev " +
                              info.getOldRevision() +
                              ") >>>\n\n";
                    break;

                case load_node_start:
                    switch (info.getNodeAction())
                    {
                        case change:
                            val = "     * editing path : " + info.getPath() +
                                  " ...";
                            break;

                        case deleted:
                            val = "     * deleting path : " + info.getPath() +
                                  " ...";
                            break;

                        case add:
                            val = "     * adding path : " + info.getPath() +
                                  " ...";
                            break;

                        case replace:
                            val = "     * replacing path : " + info.getPath() +
                                  " ...";
                            break;

                        default:
                            val = null;
                    }
                    break;

                case load_node_done:
                    val = " done.\n";
                    break;

                case load_copied_node:
                    val = "COPIED...";
                    break;

                case load_txn_start:
                    val = "<<< Started new transaction, based on " +
                          "original revision " + info.getOldRevision() + "\n";
                    break;

                case load_normalized_mergeinfo:
                    val = " removing '\\r' from svn:mergeinfo ...";
                    break;

                default:
                    val = null;
            }

            if (val != null)
                try
                {
                    outputer.write(val.getBytes());
                }
                catch (IOException ex)
                {
                    ; // ignore
                }
        }
    }
}
