/* ====================================================================
 * Copyright (c) 2006-2008  Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

// sc
#include "config.h"
#include "WcViewModel.h"
#include "ScModel.h"
#include "Bookmark.h"
#include "PostCmdResult.h"
#include "WcStatusInfo.h"
#include "WcStatusCache.h"
#include "Project.h"
#include "NullCmdProgress.h"
#include "commands/StatusCmd2.h"
#include "commands/StatusParam.h"
#include "commands/DiffCmd.h"
#include "commands/DiffParam.h"
#include "commands/AddCmd.h"
#include "commands/AddParam.h"
#include "commands/DeleteParam.h"
#include "commands/DeleteCmd.h"
#include "commands/RevertParam.h"
#include "commands/RevertCmd.h"
#include "commands/CommitParam.h"
#include "commands/CommitCmd.h"
#include "commands/EditConflictParam.h"
#include "commands/EditConflictCmd.h"
#include "commands/ResolvedParam.h"
#include "commands/ResolvedCmd.h"
#include "commands/PropListParam.h"
#include "commands/PropListCmd.h"
#include "commands/PropGetParam.h"
#include "commands/PropGetCmd.h"
#include "commands/PropSetParam.h"
#include "commands/PropSetCmd.h"
#include "commands/LogParam.h"
#include "commands/LogCmd.h"
#include "commands/LockParam.h"
#include "commands/LockCmd.h"
#include "commands/UnlockParam.h"
#include "commands/UnlockCmd.h"
#include "commands/CleanupParam.h"
#include "commands/CleanupCmd.h"
#include "commands/MkdirParam.h"
#include "commands/MkdirCmd.h"
#include "commands/ExportParam.h"
#include "commands/ExportCmd.h"
#include "commands/UpdateParam.h"
#include "commands/UpdateCmd.h"
#include "commands/MoveParam.h"
#include "commands/MoveCmd.h"
#include "commands/CopyParam.h"
#include "commands/CopyCmd.h"
#include "commands/IgnoreParam.h"
#include "commands/IgnoreCmd.h"
#include "commands/ScCmdData.h"
#include "events/ScParamEvent.h"
#include "svn/Client.h"
#include "svn/WcStatus.h"
#include "svn/WcEntry.h"
#include "svn/Path.h"
#include "util/apr.h"

// qt
#include <QtCore/QProcess>

///////////////////////////////////////////////////////////////////////

Version::Version()
{
  lowest  = LONG_MAX;
  highest = LONG_MIN;
  switched = false;
  modified = false;
}

Version::Version( const Version& src )
{
  lowest  = src.lowest;
  highest = src.highest;
  switched = src.switched;
  modified = src.modified;
}

Version Version::operator+( const Version& v )
{
  Version version(*this);
  version += v;
  return version;
}

Version& Version::operator+=( const Version& v )
{
  modified |= v.modified;
  switched |= v.switched;

  if( v.lowest < lowest)
    lowest = v.lowest;

  if( v.highest > highest)
    highest = v.highest;

  return *this;
}

QString Version::toString() const
{
  QString vs = QString( "%1" ).arg( lowest );
  if( highest > lowest )
  {
    vs += QString( ":%1" ).arg( highest );
  }
  if( modified )
  {
    vs += "M";
  }
  if( switched )
  {
    vs += "S";
  }

  return vs;
}

DeepStatus::DeepStatus() : modified(false)
{
}

bool DeepStatus::operator==( const DeepStatus& rh )
{
  return modified == rh.modified;
}

bool DeepStatus::operator!=( const DeepStatus& rh )
{
  return modified != rh.modified;
}

///////////////////////////////////////////////////////////////////////

class WcViewModelParamVisitor :
  public ParamVisitor<StatusParam>,
  public ParamVisitor<DiffParam>,
  public ParamVisitor<AddParam>,
  public ParamVisitor<DeleteParam>,
  public ParamVisitor<RevertParam>,
  public ParamVisitor<CommitParam>,
  public ParamVisitor<EditConflictParam>,
  public ParamVisitor<MoveParam>,
  public ParamVisitor<CopyParam>,
  public ParamVisitor<ResolvedParam>,
  public ParamVisitor<PropListParam>,
  public ParamVisitor<PropGetParam>,
  public ParamVisitor<PropSetParam>,
  public ParamVisitor<LockParam>,
  public ParamVisitor<UnlockParam>,
  public ParamVisitor<CleanupParam>,
  public ParamVisitor<MkdirParam>,
  public ParamVisitor<ExportParam>,
  public ParamVisitor<UpdateParam>,
  public ParamVisitor<IgnoreParam>,
  public ParamVisitor<LogParam>
{
public:
  WcViewModelParamVisitor( WcViewModel* model )
    : _model(model)
  {
  }

  void run( ScParamEvent* e )
  {
    _event = e;
    _event->getParam()->accept(this);
  }

  void visit( StatusParam* p )
  {
    _model->statusResult( p, _event->getError() );
  }

  void visit( DiffParam* p )
  {
    _model->diffBaseResult( p, _event->getError() );
  }

  void visit( AddParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPaths());
  }

  void visit( DeleteParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPaths());
  }

  void visit( RevertParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPaths());
  }

  void visit( CommitParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPaths());
  }

  void visit( EditConflictParam* p )
  {
    // may have been run some time ago, so triggering resolved will
    // most certainly run on a different selection.

    // hmm, but we know the items here...?
  }

  void visit( ResolvedParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPath());
  }

  void visit( MoveParam* p )
  {
    if( _event->getError() )
      return;

    // refresh source and destination
    svn::Paths paths;
    paths.insert( paths.end(), p->getSrcPathsOrUrls().begin(),
      p->getSrcPathsOrUrls().end() );
    paths.insert( paths.end(), p->getDstPathsOrUrls().begin(),
      p->getDstPathsOrUrls().end() );

    _model->statusRefresh(paths);
  }

  void visit( CopyParam* p )
  {
    if( _event->getError() )
      return;

    // only refresh if we copy inside a working copy. If we did a
    // branch or tag we can't run a refresh on the repository url. We
    // don't need too anyway because the working copy is not changed.
    if( svn::Client::isWorkingCopy(p->getDstPathOrUrl()) )
    {
      // refreshes 1 level too high..
      _model->statusRefresh(p->getDstPathOrUrl());
    }
  }

  void visit( PropListParam* p )
  {
    if( _event->getError() )
      return;

    _model->proplistResult( p, _event->getError() );
  }

  void visit( PropGetParam* p )
  {
    // \todo
  }

  void visit( PropSetParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPath());
  }

  void visit( LockParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPathsOrUrls());
  }

  void visit( UnlockParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPathsOrUrls());
  }

  void visit( CleanupParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPath());
  }

  void visit( MkdirParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPathsOrUrls());
  }

  void visit( ExportParam* p )
  {
  }

  void visit( UpdateParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPath());
  }

  void visit( IgnoreParam* p )
  {
    if( _event->getError() )
      return;

    _model->statusRefresh(p->getPaths());
  }

  void visit( LogParam* p )
  {
  }

private:
  ScParamEvent* _event;
  WcViewModel*  _model;
};

///////////////////////////////////////////////////////////////////////////////


WcViewModel::WcViewModel( Bookmark* bm, ScModel* model )
: TargetId(this), _bookmark(bm), _model(model)
{
}

WcViewModel::~WcViewModel()
{
}

bool WcViewModel::event( QEvent* e )
{
  switch( e->type() )
  {
  case ScParameterEvent:
    {
      WcViewModelParamVisitor visitor(this);
      visitor.run(dynamic_cast<ScParamEvent*>(e));
      return true;
    }
  default:
    {
      return super::event(e);
    }
  }
}

void WcViewModel::reload()
{
  if( _selection.isVersionedDir() )
  {
    svn::WcStatusPtr s = _selection.getSingle();
    status(s->getName(), true );
    return;
  }
  assert(false);
}

void WcViewModel::status()
{
  status( _bookmark->getSource(), false );
}

/** automatic refresh */
void WcViewModel::statusRefresh( const sc::String& path )
{
  if( ! getAutoRefresh() )
    return;

  sc::String refresh = svn::Path::getDirName(path);

  if( ! svn::Client::isWorkingCopy(refresh) )
  {
    refresh = path;
  }

  status( refresh, true );
}

/** automatic refresh */
void WcViewModel::statusRefresh( const svn::Paths& paths )
{
  if( ! getAutoRefresh() )
    return;

  // refresh parent path. If there is more than one path their common parent
  // path is under version control. If there is a single path and it is the
  // working copy root path its parent is not under version control. refresh
  // the path itself in this case.
  sc::String refresh = svn::Path::findPrefix(paths);

  if( paths.size() == 1 && ! svn::Client::isWorkingCopy(refresh) )
  {
    refresh = *paths.begin();
  }

  status( refresh, true );
}

void WcViewModel::status( const sc::String& path, bool refresh )
{
  if( path.isEmpty() )
    return;

  if( ! svn::Client::isWorkingCopy(path) )
  {
    // ask checkout.. checkout from project trunk..
  }

  emit commandStarted(_bookmark);

  StatusParam* param = new StatusParam(
    path,
    new svn::Revision(svn::Revision_Unspecified),
    true        /* recurse */,
    true        /* all     */,
    getUpdate() /* updates */,
    true        /* ignored */,
    refresh );

  StatusCmd2* cmd = new StatusCmd2( param, this, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::statusResult( StatusParam* p, const sc::Error* e )
{
  if( !p->isNested() )
    emit commandFinished(_bookmark);

  if( e && e->getCode() != SVN_ERR_WC_NOT_DIRECTORY )
    return;

  if( p->getStatuss().size() == 0 )
    return;


  // notify status update start.
  emit updatedStart(p->getPath());

  // cleanup, notify the old info.
  updateOld(p->getPath());

  // insert, notify new info.
  updateNew(p->getPath(),p->getStatuss());

  // notify status update end.
  emit updatedFinish(p->getPath());

  // notify updated version;
  updateVersion();
}

void WcViewModel::updateOld( const sc::String& path )
{
  svn::WcStatuss statuss;
  getStatus(path,statuss);

  emit updatedOld(path,statuss);
  delStatus(path);
}

void WcViewModel::updateNew( const sc::String& path, const svn::WcStatuss& statuss )
{
  // insert the new info.
  const StatusInfo& info = addStatus(path,statuss);

  // notify the new info.
  emit updatedNew(path,info.statuss,info.status.modified);

  // folder without child folders?
  if( isLeafFolder(path) )
  {
    emit updatedDeepStatus(path,info.status);
    updateDeepStatus(path);
  }
}

void WcViewModel::diffBase()
{
  svn::WcStatusPtr status = _selection.getSingle();

  DiffParam* param = new DiffParam( 
    status->getName(), svn::RevisionPtr(new svn::Revision(svn::Revision_Base)), 
    status->getName(), svn::RevisionPtr(new svn::Revision(svn::Revision_Working)),
    false, svn::RevisionPtr()/*no peg*/, 
    status->isDir() ? getRecursive() : false,
    true, false, false,
    status->isDir() ? true : false );

  DiffCmd* cmd = new DiffCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::diffBase( const sc::String& path, bool dir )
{
  DiffParam* param = new DiffParam( 
    path, svn::RevisionPtr(new svn::Revision(svn::Revision_Base)), 
    path, svn::RevisionPtr(new svn::Revision(svn::Revision_Working)),
    false, svn::RevisionPtr()/*no peg*/, 
    dir ? getRecursive() : false,
    true, false, false,
    dir ? true : false );

  DiffCmd* cmd = new DiffCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::diffBaseResult( DiffParam* p, const sc::Error* e )
{
  if( p->getPatch() )
  {
    emit showPatch(
      QString::fromUtf8(p->getPathOrUrl1()), QString::fromUtf8(p->getPatchFile()) );
  }
}

void WcViewModel::add()
{
 /* \todo force: ignore already version items */

  svn::Paths paths;
  _selection.getNames(paths);

  AddParam* param = new AddParam( paths, getRecursive(), false /* force */ );
  AddCmd*   cmd   = new AddCmd( param, new PostCmdResult(this) );

  _model->runThreaded(cmd);
}

void WcViewModel::remove()
{
  svn::Paths paths;
  svn::Paths unversioned;

  _selection.getNames(paths);
  _selection.getUnversionNames(unversioned);

  bool proceed = true;
  bool force   = false;
  emit confirmRemove( unversioned.size() != 0, force, proceed );

  if( ! proceed )
    return;

  DeleteParam* param = new DeleteParam( paths/*versioned*/, force );
  DeleteCmd*   cmd   = new DeleteCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::revert()
{
  bool proceed = true;
  emit confirmRevert( proceed );

  if( ! proceed )
    return;

  svn::Paths paths;
  _selection.getNames(paths);

  RevertParam* param = new RevertParam( paths, getRecursive() );
  RevertCmd*   cmd   = new RevertCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::commit( bool bookmark )
{
  svn::Paths paths;

  if( bookmark )
  {
    paths.push_back(_bookmark->getSource());
  }
  else
  {
    _selection.getNames(paths);
  }

  CommitParam* param = new CommitParam( paths, getRecursive(), false, NULL );

  bool proceed = false;
  emit confirmCommit( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  CommitCmd* cmd = new CommitCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::editConflict()
{
  EditConflictParam* param = new EditConflictParam( _selection.getSingle() );
  EditConflictCmd*   cmd   = new EditConflictCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::resolved()
{
  bool proceed = false;
  emit confirmResolved( proceed );

  if( ! proceed )
    return;

  svn::Paths paths;
  _selection.getNames(paths);

  ResolvedParam* param = new ResolvedParam( paths.front(), getRecursive() );
  ResolvedCmd*   cmd   = new ResolvedCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::log( bool bookmark )
{
  // unused
}

void WcViewModel::logGraph()
{
  // unused
}

void WcViewModel::ignore()
{
  svn::Paths paths;
  _selection.getNames(paths);

  IgnoreParam* param = new IgnoreParam( paths );
  IgnoreCmd*   cmd   = new IgnoreCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::blame()
{
  // unused
}

void WcViewModel::lock()
{
  svn::Paths paths;
  _selection.getNames(paths);

  LockParam* param = new LockParam( paths, sc::String(""), false );

  bool proceed = true;
  emit confirmLock( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  LockCmd* cmd = new LockCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::unlock()
{
  svn::Paths paths;
  _selection.getNames(paths);

  UnlockParam* param = new UnlockParam( paths, false );

  bool proceed = true;
  emit confirmUnlock( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  UnlockCmd* cmd = new UnlockCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::cleanup()
{
  svn::Paths paths;
  _selection.getNames(paths);

  CleanupParam* param = new CleanupParam(paths.front());
  CleanupCmd*   cmd   = new CleanupCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::mkdir()
{
  sc::String dir;
  bool proceed = false;
  emit confirmMkdir( dir, proceed );

  if( ! proceed )
    return;

  svn::Paths sel;
  _selection.getNames(sel);

  svn::Paths paths;
  for( svn::Paths::iterator it = sel.begin(); it != sel.end(); it++ )
  {
    sc::String path = (*it);
    path += "/" + dir;

    paths.push_back( path );
  }

  MkdirParam* param = new MkdirParam(paths);
  MkdirCmd*   cmd   = new MkdirCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::exportx()
{
  ExportParam* param = new ExportParam(
    _selection.getSingle()->getName(), sc::String(""),
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    false, sc::String("") );

  bool proceed = false;
  emit confirmExport( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  ExportCmd* cmd = new ExportCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::update( bool bookmark )
{
  sc::String update;

  if( bookmark )
  {
    update = _bookmark->getSource();
  }
  else
  {
    update = _selection.getSingle()->getName();
  }

  UpdateParam* param = new UpdateParam( update,
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    getRecursive() );
  UpdateCmd* cmd = new UpdateCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::updateRev( bool bookmark )
{
  sc::String update;

  if( bookmark )
  {
    update = _bookmark->getSource();
  }
  else
  {
    update = _selection.getSingle()->getName();
  }

  UpdateParam* param = new UpdateParam( update,
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    getRecursive() );

  bool proceed = true;
  emit confirmUpdate( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  UpdateCmd* cmd = new UpdateCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::branchTag( bool bookmark )
{
  svn::Paths paths;

  if( bookmark )
  {
    paths.push_back(_bookmark->getSource());
  }
  else
  {
    _selection.getNames(paths);
  }

  CopyParam* param = new CopyParam( paths, 
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    _bookmark->getProject()->getBranchesUrl() );

  bool proceed = false;
  emit confirmCopy( param, _bookmark->getProject(), proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  CopyCmd* cmd = new CopyCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::move( const sc::String& to, bool base )
{
  bool force = false;
  bool proceed = true;
  emit confirmMove( force, proceed );

  if( ! proceed )
    return;

  svn::Paths srcpaths;
  _selection.getNames(srcpaths);

  svn::Paths dstpaths;

  if( base ) // rename
  {
    sc::String dirname = svn::Path::getDirName(srcpaths[0]);

    sc::String newname;
    newname = dirname + "/" + to;

    dstpaths.push_back(newname);
  }
  else
  {
    for( svn::Paths::iterator it = srcpaths.begin(); it != srcpaths.end(); it++ )
    {
      sc::String dirname  = svn::Path::getDirName(*it);
      sc::String basename = svn::Path::getBaseName(*it);

      sc::String newname;
      newname =  to + "/" + basename;

      dstpaths.push_back(newname);
    }
  }

  MoveParam* param = new MoveParam( srcpaths, dstpaths, force );
  MoveCmd*   cmd   = new MoveCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::copy( const sc::String& to )
{
  // \todo check if we copy into the same dirname, if so ask for a
  // new name, possibly change CopyParam to to svn::Path

  svn::Paths paths;
  _selection.getNames(paths);

  sc::String target = to;
#if 0
  if( paths.size() == 1 && svn::Path::getDirName(paths.front()) == target )
  {
    sc::String base = svn::Path::getBaseName(paths.front());
    emit confirmCopy( base, proceed );
    target = to + base;
  }
#endif

  CopyParam* param = new CopyParam(
    paths, svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    target );
  CopyCmd* cmd = new CopyCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::proplist()
{
  PropListParam* param = new PropListParam( _selection.getSingle()->getName(),
    new svn::Revision(svn::Revision_Unspecified), false );
  PropListCmd* cmd = new PropListCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::proplistResult( PropListParam* param, const sc::Error* err )
{
  emit proplist( param->getPathOrUrl(), param->getItems() );
}

void WcViewModel::propset()
{
  PropSetParam* param = new PropSetParam( sc::NullString,
    sc::NullString, _selection.getSingle()->getName(), false );

  bool proceed = false;
  emit confirmPropSet( param, proceed );

  if( ! proceed )
  {
    delete param;
    return;
  }

  PropSetCmd* cmd = new PropSetCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void WcViewModel::propget()
{
}

void WcViewModel::run( const sc::String& file )
{
  bool result = QProcess::startDetached( "open", QStringList() << "-e" << QString::fromUtf8(file) );
  if( !result )
  {
    //emit error
  }
}

void WcViewModel::addUnvrsnd()
{
}

void WcViewModel::remMissing()
{
}

// StatusResult
void WcViewModel::setWcStatus( const sc::String& path, const svn::WcStatuss& statuss )
{
  WcStatusInfo info( path, apr_time_now(), statuss );
  _model->getStatusCache()->set(info);
}

// StatusResult
bool WcViewModel::getWcStatus( const sc::String& path, svn::WcStatuss& statuss )
{
  WcStatusInfo info = _model->getStatusCache()->get( path );
  statuss = info.getWcStatuss();
  return !info.isEmpty();
}

void WcViewModel::delStatus( const sc::String& path )
{
  Status::iterator it = _status.find(path);
  if( it != _status.end() )
    _status.erase(it);
}

const StatusInfo& WcViewModel::addStatus( const sc::String& path, const svn::WcStatuss& statuss )
{
  svn::WcStatuss statuss2 = statuss;

  // since we run status folder by folder we loose if a folder is switched in
  // its parent folder, fix it.
  if( isSwitchedInParent(path) )
    *statuss2.begin() = svn::WcStatusPtr( (*statuss.begin())->cloneSwitched() );
  
  StatusInfo info;
  info.statuss = statuss2;
  info.version = calcVersion(statuss2);
  info.status  = calcFlatStatus(path,statuss2);
  
  StatusPair p = _status.insert( Status::value_type(path,info) );
  return (*p.first).second;
}

void WcViewModel::getStatus( const sc::String& path, svn::WcStatuss& statuss )
{
  Status::iterator it = _status.find(path);
  if( it == _status.end() )
    return;

  StatusInfo& info = (*it).second;
  statuss = info.statuss;
}

void WcViewModel::getStatus( const sc::String& path, svn::WcStatuss& statuss, bool& deepStatus )
{
  Status::iterator it = _status.find(path);
  if( it == _status.end() )
    return;

  StatusInfo& info = (*it).second;

  statuss    = info.statuss;
  deepStatus = info.status.modified;
}

const sc::String& WcViewModel::getRootPath() const
{
  return _bookmark->getSource();
}

const sc::String& WcViewModel::getRootUrl() const
{
  return _bookmark->getUrl();
}

sc::String WcViewModel::getUrlFromPath( const sc::String& path )
{
  sc::String url;
  svn::Client::getUrlFromPath(path,url);
  return url;
}

Bookmark* WcViewModel::getBookmark()
{
  return _bookmark;
}

void WcViewModel::setSelection( const WcSelection& sel )
{
  _selection = sel;
}

const WcSelection& WcViewModel::getSelection()
{
  return _selection;
}

void WcViewModel::remove( const svn::Paths& unversioned )
{
  // \todo move to svn::io!?
  apr::Pool pool;
  svn_error_t* err;

  for( svn::Paths::const_iterator it = unversioned.begin(); it != unversioned.end(); it++ )
  {
    svn_node_kind_t kind;
    err = svn_io_check_path( *it, &kind, pool );

    if( err != SVN_NO_ERROR )
    {
      // skip
      continue;
    }

    if( kind == svn_node_file )
    {
      err = svn_io_remove_file( *it, pool );
    }
    else if( kind == svn_node_dir )
    {
      err = svn_io_remove_dir( *it, pool );
    }
  }
}

bool WcViewModel::getAutoRefresh()
{
  return _bookmark->getAutoRefresh();
}

bool WcViewModel::getRecursive()
{
  return _bookmark->getRecursive();
}

bool WcViewModel::getUpdate()
{
  return _bookmark->getUpdate();
}

const Version& WcViewModel::getVersion()
{
  return _version;
}

Version WcViewModel::calcVersion( const svn::WcStatuss& statuss )
{
  Version version;

  for( svn::WcStatuss::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it);
    const svn::WcEntry*    entry  = status->getWcEntry();

    if(  status->getTextStatus() == svn::WcStatus_Added
      || status->getTextStatus() == svn::WcStatus_Unversioned
      )
    {
      continue;
    }

    version.modified |= status->isChanged();
    version.switched |= status->isSwitched();

    if( entry && ((entry->getRevnumber() < version.lowest) || (version.lowest == 0)) )
    {
      version.lowest = entry->getRevnumber();
    }
    if( entry && (entry->getRevnumber() > version.highest) )
    {
      version.highest = entry->getRevnumber();
    }
  }

  return version;
}

void WcViewModel::updateVersion()
{
  Version v;
  for( Status::iterator it = _status.begin(); it != _status.end(); it++ )
  {
    v += (*it).second.version;
  }

  _version = v;

  emit updatedVersion(_version);
}

DeepStatus WcViewModel::calcFlatStatus( const sc::String& path, const svn::WcStatuss& statuss )
{
  DeepStatus deepStatus;

  for( svn::WcStatuss::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it);

    if( status->isChanged() )
    {
      deepStatus.modified = true;
      break;
    }
  }

  return deepStatus;
}

DeepStatus WcViewModel::calcDeepStatus( const sc::String& path, const svn::WcStatuss& statuss )
{
  DeepStatus deepStatus;

  for( svn::WcStatuss::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it);

    if( status->isChanged() )
    {
      deepStatus.modified = true;
      //continue;
      break;
    }

    // skip ourself, we don't want to incorperate our old deep status in calculation
    // of our new deep status.
    if( path == status->getName() )
      continue;

    if( status->isDir() )
    {
      Status::iterator it2 = _status.find( status->getName() );
      if( it2 != _status.end() )
      {
        if( (*it2).second.status.modified )
        {
          deepStatus.modified = true;
          //continue;
          break;
        }
      }
    }
  }

  return deepStatus;
}

void WcViewModel::updateDeepStatus( const sc::String& path )
{
  sc::String base = path;

  while( true )
  {
    base = svn::Path::getDirName(base);

    Status::iterator it = _status.find( base );
    if( it == _status.end() )
      return;

    (*it).second.status = calcDeepStatus( base, (*it).second.statuss );

    emit updatedDeepStatus( base, (*it).second.status );
  }
}

bool WcViewModel::isLeafFolder( const sc::String& path )
{
  Status::iterator it = _status.find(path);
  if( it == _status.end() )
    assert(false);

  svn::WcStatuss& statuss = (*it).second.statuss;

  for( svn::WcStatuss::iterator sit = statuss.begin(); sit != statuss.end(); sit++ )
  {
    if( (*sit)->isDir() && (*sit)->getName() != path )
      return false;
  }
  return true;
}

bool WcViewModel::isSwitchedInParent( const sc::String& path )
{
  sc::String parent = svn::Path::getDirName(path);

  Status::iterator it = _status.find(parent);
  if( it == _status.end() )
    return false;

  svn::WcStatuss& statuss = (*it).second.statuss;
  
  for( svn::WcStatuss::iterator sit = statuss.begin(); sit != statuss.end(); sit++ )
  {
    if( (*sit)->getName() == path )
      return (*sit)->isSwitched();
  }
  return false;
}

