/*
 * Port for usage with qt-framework and development for tdesvn
 * (C) 2005-2007 by Rajko Albrecht
 * http://tdesvn.alwins-world.de
 */
/*
 * ====================================================================
 * Copyright (c) 2002-2005 The RapidSvn Group.  All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library (in the file LGPL.txt); if not,
 * write to the Free Software Foundation, Inc., 51 Franklin St,
 * Fifth Floor, Boston, MA  02110-1301  USA
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://rapidsvn.tigris.org/.
 * ====================================================================
 */
#if defined( _MSC_VER) && _MSC_VER <= 1200
#pragma warning( disable: 4786 )// debug symbol truncated
#endif
// svncpp
#include "svnqt/client_impl.h"

// subversion api
#include "svn_client.h"

#include "svnqt/exception.h"
#include "svnqt/pool.h"
#include "svnqt/targets.h"
#include "svnqt/svnqt_defines.h"
#include "svnqt/stringarray.h"

#include "svnqt/helper.h"

namespace svn
{
  svn_revnum_t
  Client_impl::checkout (const Path& url, const Path & destPath,
              const Revision & revision,
              const Revision & peg,
              svn::Depth depth,
              bool ignore_externals,
              bool overwrite
                        )
  {
    Pool subPool;
    svn_revnum_t revnum = 0;
    Path up(url);
    svn_error_t * error = 0;
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    error = svn_client_checkout3(&revnum,
                up.cstr(),
                destPath.cstr(),
                peg.revision(),
                revision.revision (),
                internal::DepthToSvn(depth),
                ignore_externals,
                overwrite,
                *m_context,
                subPool);
#else
    bool recurse = depth==DepthInfinity;
    Q_UNUSED(overwrite);
    error = svn_client_checkout2(&revnum,
                           up.cstr(),
                           destPath.cstr(),
                           peg.revision(),
                           revision.revision (),
                           recurse,
                           ignore_externals,
                           *m_context,
                           subPool);
#endif
    if(error != NULL)
      throw ClientException (error);
    return revnum;
  }

  Revision Client_impl::remove (const Path & path,bool force,
                            bool keep_local,
                            const PropertiesMap&revProps)
  {
      Targets targets (path.path());
      return remove(targets,force,keep_local,revProps);
  }

  Revision
  Client_impl::remove (const Targets & targets,
                  bool force,
                  bool keep_local,
                  const PropertiesMap&revProps
                      )
  {
    Pool pool;

    svn_commit_info_t *commit_info = 0;

    svn_error_t * error =
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
            svn_client_delete3(
                               &commit_info,
                               targets.array(pool),
                               force,
                               keep_local,
                               map2hash(revProps,pool),
                               *m_context,
                               pool
                              );
#else
      svn_client_delete2
                    (&commit_info,
                         const_cast<apr_array_header_t*> (targets.array (pool)),
                         force,
                         *m_context,
                         pool);
      Q_UNUSED(keep_local);
      Q_UNUSED(revProps);
#endif
    if(error != 0) {
        throw ClientException (error);
    }
    if (commit_info) {
        return commit_info->revision;
    }
    return Revision::UNDEFINED;
  }

  void
  Client_impl::revert (const Targets & targets,
                  Depth depth,
                  const StringArray&changelist
                      )
  {
    Pool pool;

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    svn_error_t * error =
            svn_client_revert2 ((targets.array (pool)),
                                internal::DepthToSvn(depth),
                                changelist.array(pool),
                                *m_context,
                                pool);

#else
    bool recurse = depth==DepthInfinity;
    Q_UNUSED(changelist);
    svn_error_t * error =
      svn_client_revert ((targets.array (pool)),
                         recurse,
                         *m_context,
                         pool);
#endif
    if(error != NULL) {
        throw ClientException (error);
    }
  }

  void
  Client_impl::add (const Path & path,
                    svn::Depth depth,bool force, bool no_ignore, bool add_parents)
  {
    Pool pool;
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    svn_error_t * error =
            svn_client_add4(path.cstr (),
                             internal::DepthToSvn(depth),
                             force,
                             no_ignore,
                             add_parents,
                             *m_context,
                             pool);
#else
    Q_UNUSED(add_parents);
    svn_error_t * error =
      svn_client_add3 (path.cstr (),
                      depth==DepthInfinity,
                      force,
                      no_ignore,
                      *m_context,
                      pool);
#endif
    if(error != NULL)
      throw ClientException (error);
  }

  Revisions
  Client_impl::update (const Targets & path,
                  const Revision & revision,
                  Depth depth,
                  bool ignore_externals,
                  bool allow_unversioned,
                  bool sticky_depth
                      )
  {
    Pool pool;
    Revisions resulting;
    svn_error_t * error;

    apr_pool_t *apr_pool = pool.pool();
    apr_array_header_t *apr_revisions = apr_array_make (apr_pool,
                      path.size(),
                      sizeof (svn_revnum_t));
    if (depth==DepthUnknown) {
        depth=DepthInfinity;
    }
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    error = svn_client_update3(&apr_revisions,path.array(pool),revision,internal::DepthToSvn(depth),sticky_depth,ignore_externals,allow_unversioned,*m_context,pool);
#else
    bool recurse = depth==DepthInfinity;
    Q_UNUSED(sticky_depth);
    Q_UNUSED(allow_unversioned);
    error = svn_client_update2(&apr_revisions,path.array(pool),revision,recurse,ignore_externals,*m_context,pool);
#endif
    if (error!=NULL) {
        throw ClientException(error);
    }
    for (int i = 0; i < apr_revisions->nelts; ++i)
    {
      svn_revnum_t * _rev =
        &APR_ARRAY_IDX (apr_revisions, i, svn_revnum_t);

      resulting.push_back((*_rev));
    }
    return resulting;
  }

  svn::Revision
  Client_impl::commit (const Targets & targets, const TQString& message,
                       svn::Depth depth,bool keep_locks,
                       const svn::StringArray&changelist,
                       const PropertiesMap&revProps,
                       bool keep_changelist)
  {
    Pool pool;

    m_context->setLogMessage (message);
    svn_commit_info_t *commit_info = NULL;

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    svn_error_t * error =
            svn_client_commit4 (
                &commit_info,
                targets.array (pool),
                internal::DepthToSvn(depth),
                keep_locks,
                keep_changelist,
                changelist.array(pool),
                map2hash(revProps,pool),
                *m_context,
                pool);
#else
    Q_UNUSED(changelist);
    Q_UNUSED(keep_changelist);
    Q_UNUSED(revProps);
    bool recurse = depth==DepthInfinity;

    svn_error_t * error =
      svn_client_commit3
                        (&commit_info,
                         targets.array (pool),
                         recurse,
                         keep_locks,
                         *m_context,
                         pool);
#endif
    if (error != NULL) {
        throw ClientException (error);
    }

    if (commit_info && SVN_IS_VALID_REVNUM (commit_info->revision))
      return (commit_info->revision);

    return svn::Revision::UNDEFINED;
  }

  Revision
    Client_impl::copy(const Targets & srcPaths,
                    const Revision & srcRevision,
                    const Revision &pegRevision,
                    const Path & destPath,
                     bool asChild,bool makeParent,
                     const PropertiesMap&revProps
                     )
    {
        if (srcPaths.size()<1)
        {
            throw ClientException("Wrong size of sources.");
        }
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
        Pool pool;
        svn_commit_info_t *commit_info = 0L;
        apr_array_header_t * sources = apr_array_make(pool,srcPaths.size(),sizeof(svn_client_copy_source_t *));
        for (size_t j=0;j<srcPaths.size();++j)
        {
            svn_client_copy_source_t* source = (svn_client_copy_source_t*)apr_palloc(pool, sizeof(svn_client_copy_source_t));
            source->path = apr_pstrdup(pool,srcPaths[j].path().TOUTF8());
            source->revision=srcRevision.revision();
            source->peg_revision=pegRevision.revision();
            APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source;
        }
        svn_error_t * error =
                svn_client_copy4(&commit_info,
                    sources,
                    destPath.cstr(),
                    asChild,makeParent,map2hash(revProps,pool),*m_context,pool);
        if (error!=0){
            throw ClientException (error);
        }
        if (commit_info) {
            return commit_info->revision;
        }
        return Revision::UNDEFINED;
#else
        Q_UNUSED(asChild);
        Q_UNUSED(makeParent);
        Q_UNUSED(revProps);
        Q_UNUSED(pegRevision);
        Revision rev;
        if (srcPaths.size()>1 && !asChild)
        {
            throw ClientException("Multiple sources not allowed");
        }

        Path _dest;
        TQString base,dir;
        for (size_t j=0;j<srcPaths.size();++j)
        {
            _dest=destPath;
            if (asChild) {
                srcPaths[j].split(dir,base);
                _dest.addComponent(base);
            }
            rev  = copy(srcPaths[j],srcRevision,_dest);
        }
        return rev;
#endif
    }

  Revision
  Client_impl::copy (const Path & srcPath,
                const Revision & srcRevision,
                const Path & destPath)
  {
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
      return copy(srcPath,srcRevision,srcRevision,destPath,true,false);
#else
      Pool pool;
    svn_commit_info_t *commit_info = NULL;
    svn_error_t * error =
        svn_client_copy2
                    (&commit_info,
                       srcPath.cstr (),
                       srcRevision.revision (),
                       destPath.cstr (),
                       *m_context,
                       pool);

    if(error != 0) {
          throw ClientException (error);
    }
    if (commit_info) {
        return commit_info->revision;
    }
    return Revision::UNDEFINED;
#endif
  }

  svn::Revision Client_impl::move (const Path & srcPath,
                const Path & destPath,
                bool force)
  {

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    return move(srcPath,destPath,force,false,false,PropertiesMap());
#else
    Pool pool;
    svn_commit_info_t *commit_info = 0;

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 4))
    svn_error_t * error = svn_client_move4
#else
    svn_error_t * error = svn_client_move3
#endif
                     (&commit_info,
                       srcPath.cstr (),
                       destPath.cstr (),
                       force,
                       *m_context,
                       pool);

    if(error != 0) {
        throw ClientException (error);
    }
    if (commit_info) {
        return commit_info->revision;
    }
    return Revision::UNDEFINED;
#endif
  }

  svn::Revision Client_impl::move (
             const Targets & srcPaths,
             const Path & destPath,
             bool force,
             bool asChild,
             bool makeParent,
             const PropertiesMap&revProps)
  {
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
      Pool pool;
      svn_commit_info_t *commit_info = 0;
      svn_error_t * error = svn_client_move5(
                                             &commit_info,
                                             srcPaths.array(pool),
                                             destPath.cstr(),
                                             force,
                                             asChild,
                                             makeParent,
                                             map2hash(revProps,pool),
                                             *m_context,
                                             pool
                                            );
      if (error!=0) {
          throw ClientException (error);
      }
      if (commit_info) {
          return commit_info->revision;
      }
      return Revision::UNDEFINED;
#else
      Q_UNUSED(makeParent);
      Q_UNUSED(revProps);
      Revision rev;
      if (srcPaths.size()>1 && !asChild)
      {
          throw ClientException("Multiple sources not allowed");
      }
      TQString base,dir;
      Path _dest;
      for (size_t j=0;j<srcPaths.size();++j)
      {
          _dest=destPath;
          if (asChild) {
              srcPaths[j].split(dir,base);
              _dest.addComponent(base);
          }
          rev = move(srcPaths[j],_dest,force);
      }
      return rev;
#endif
  }

  svn::Revision
  Client_impl::mkdir (const Path & path,
                 const TQString& message,
                 bool makeParent,
                 const PropertiesMap&revProps
                     )
  {
    Targets targets(path.path());
    return mkdir(targets,message,makeParent,revProps);
  }

  svn::Revision
  Client_impl::mkdir (const Targets & targets,
                 const TQString&msg,
                 bool makeParent,
                 const PropertiesMap&revProps
                     )
  {
    Pool pool;
    m_context->setLogMessage(msg);

    svn_commit_info_t *commit_info = NULL;

    svn_error_t * error = 0;

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    error = svn_client_mkdir3
            (&commit_info,
              const_cast<apr_array_header_t*>(targets.array (pool)),
              makeParent,
              map2hash(revProps,pool),
              *m_context, pool);
#else
    Q_UNUSED(makeParent);
    Q_UNUSED(revProps);
    error = svn_client_mkdir2
            (&commit_info,
             const_cast<apr_array_header_t*>(targets.array (pool)),
            *m_context, pool);
#endif

    /* important! otherwise next op on repository uses that logmessage again! */
    m_context->setLogMessage(TQString());

    if(error != NULL)
      throw ClientException (error);
    if (commit_info) {
        return commit_info->revision;
    }
    return Revision::UNDEFINED;
  }

  void
  Client_impl::cleanup (const Path & path)
  {
    Pool subPool;
    apr_pool_t * apr_pool = subPool.pool ();

    svn_error_t * error =
      svn_client_cleanup (path.cstr (), *m_context, apr_pool);

    if(error != NULL)
      throw ClientException (error);
  }

  void Client_impl::resolve(const Path & path,Depth depth,const ConflictResult&resolution)
  {
    Pool pool;
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    const svn_wc_conflict_result_t*aResult=resolution.result(pool);
    svn_error_t*error=svn_client_resolve(path.cstr(),internal::DepthToSvn(depth),aResult->choice,*m_context,pool);

#else
    Q_UNUSED(resolution);
    bool recurse=depth==DepthInfinity;
    svn_error_t * error =
      svn_client_resolved (path.cstr (),
                           recurse,
                           *m_context,
                           pool);
#endif
    if(error != NULL) {
        throw ClientException (error);
    }
  }

  svn_revnum_t
  Client_impl::doExport (const Path & srcPath,
              const Path & destPath,
              const Revision & revision,
              const Revision & peg,
              bool overwrite,
              const TQString&native_eol,
              bool ignore_externals,
              svn::Depth depth)
  {
    Pool pool;
    svn_revnum_t revnum = 0;
    const char*_neol;
    if (native_eol==TQString()) {
        _neol = (const char*)0;
    } else {
        _neol = native_eol.TOUTF8();
    }
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    svn_error_t * error =
            svn_client_export4(&revnum,
                                srcPath.cstr(),
                                destPath.cstr(),
                                peg.revision(),
                                revision.revision(),
                                overwrite,
                                ignore_externals,
                                internal::DepthToSvn(depth),
                                _neol,
                                *m_context,
                                pool);
#else
    bool recurse = depth==svn::DepthInfinity;
    svn_error_t * error =
      svn_client_export3(&revnum,
                        srcPath.cstr(),
                        destPath.cstr(),
                        peg.revision(),
                        revision.revision(),
                        overwrite,
                        ignore_externals,
                        recurse,
                        _neol,
                         *m_context,
                         pool);
#endif
    if(error != NULL)
      throw ClientException (error);
    return revnum;
  }

  svn_revnum_t
  Client_impl::doSwitch (
                         const Path & path, const TQString& url,
                         const Revision & revision,
                         Depth depth,
                         const Revision & peg,
                         bool sticky_depth,
                         bool ignore_externals,
                         bool allow_unversioned
                        )
  {
    Pool pool;
    svn_revnum_t revnum = 0;
    svn_error_t * error = 0;

#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    error = svn_client_switch2(
                               &revnum,
                               path.cstr(),
                               url.TOUTF8(),
                               peg.revision(),
                               revision.revision(),
                               internal::DepthToSvn(depth),
                               sticky_depth,
                               ignore_externals,
                               allow_unversioned,
                               *m_context,
                               pool
                              );
#else
    bool recurse = depth==DepthInfinity;
    Q_UNUSED(peg);
    Q_UNUSED(sticky_depth);
    Q_UNUSED(ignore_externals);
    Q_UNUSED(allow_unversioned);
    error = svn_client_switch (&revnum,
                         path.cstr(),
                         url.TOUTF8(),
                         revision.revision (),
                         recurse,
                         *m_context,
                         pool);
#endif
    if(error != NULL) {
        throw ClientException (error);
    }
    return revnum;
  }

  Revision
  Client_impl::import (const Path & path,
                  const TQString& url,
                  const TQString& message,
                  svn::Depth depth,
                  bool no_ignore,bool no_unknown_nodetype,
                  const PropertiesMap&revProps
                      )

  {
    svn_commit_info_t *commit_info = NULL;
    Pool pool;

    m_context->setLogMessage (message);
#if ((SVN_VER_MAJOR == 1) && (SVN_VER_MINOR >= 5)) || (SVN_VER_MAJOR > 1)
    svn_error_t * error =
        svn_client_import3(&commit_info,path.cstr (),url.TOUTF8(),
            internal::DepthToSvn(depth),no_ignore,no_unknown_nodetype,
                                 map2hash(revProps,pool),
                                 *m_context,pool);
#else
    bool recurse = depth==DepthInfinity;
    Q_UNUSED(revProps);
    Q_UNUSED(no_unknown_nodetype);

    svn_error_t * error =
        svn_client_import2(&commit_info,
                        path.cstr (),
                        url.TOUTF8(),
                        !recurse,
                        no_ignore,
                        *m_context,
                        pool);
#endif
    /* important! otherwise next op on repository uses that logmessage again! */
    m_context->setLogMessage(TQString());

    if(error != 0) {
        throw ClientException (error);
    }
    if (commit_info) {
        return commit_info->revision;
    }
    return Revision::UNDEFINED;
  }

  void
  Client_impl::relocate (const Path & path,
                    const TQString& from_url,
                    const TQString& to_url,
                    bool recurse)
  {
    Pool pool;
    svn_error_t * error =
      svn_client_relocate (path.cstr (),
                         from_url.TOUTF8(),
                         to_url.TOUTF8(),
                         recurse,
                         *m_context,
                         pool);

    if(error != NULL)
      throw ClientException (error);
  }

}
