/* ====================================================================
 * Copyright (c) 2007-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 "RpViewModel.h"
#include "ScModel.h"
#include "Bookmark.h"
#include "PostCmdResult.h"
#include "NullCmdProgress.h"
#include "Project.h"
#include "DiffViewModel.h"
#include "MergeViewModel.h"
#include "commands/ListCmd2.h"
#include "commands/ListParam.h"
#include "commands/DiffCmd.h"
#include "commands/DiffParam.h"
#include "commands/MergeCmd.h"
#include "commands/MergeParam.h"
#include "commands/MkdirCmd.h"
#include "commands/MkdirParam.h"
#include "commands/MoveParam.h"
#include "commands/MoveCmd.h"
#include "commands/CopyParam.h"
#include "commands/CopyCmd.h"
#include "commands/CheckoutParam.h"
#include "commands/CheckoutCmd.h"
#include "commands/DeleteParam.h"
#include "commands/DeleteCmd.h"
#include "commands/SwitchParam.h"
#include "commands/SwitchCmd.h"
#include "commands/ScCmdData.h"
#include "events/ScParamEvent.h"
#include "svn/DirEntry.h"
#include "svn/Path.h"

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

class RpViewModelParamVisitor :
  public ParamVisitor<ListParam>,
  public ParamVisitor<DiffParam>,
  public ParamVisitor<MergeParam>,
  public ParamVisitor<MkdirParam>,
  public ParamVisitor<MoveParam>,
  public ParamVisitor<CopyParam>,
  public ParamVisitor<SwitchParam>,
  public ParamVisitor<DeleteParam>
{
public:
  RpViewModelParamVisitor( RpViewModel* model )
    : _model(model)
  {
  }

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

  void visit( ListParam* p )
  {
    _model->listResult( p, _event->getError() );
  }

  void visit( DiffParam* p )
  {
  }

  void visit( MergeParam* p )
  {
  }

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

    _model->listRefresh( *(p->getPathsOrUrls().begin()) );
  }

  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->listRefresh(paths);
  }

  void visit( CopyParam* p )
  {
  }

  void visit( SwitchParam* p )
  {
  }

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

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

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

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

RpViewModel::RpViewModel( Bookmark* bm, ScModel* model )
: TargetId(this), _bookmark(bm), _model(model),
  _rev(svn::InvalidRevnumber)
{
}

RpViewModel::~RpViewModel()
{
}

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

void RpViewModel::reload()
{
  svn::DirEntryPtr s = _selection.getSingle();
  list( s->getName(), true );
}

void RpViewModel::list()
{
  list( _bookmark->getSource(), false );
}

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

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

  // check repository root
  //if( ! svn::Client::isWorkingCopy(refresh) )
  //{
    //refresh = path;
  //}

  list( refresh, true );
}

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

  // reload parent folders, except for our root.
  for( svn::Paths::const_iterator it = paths.begin(); it != paths.end(); it++ )
  {
    sc::String path;
    if( (*it).getByteCnt() > _bookmark->getSource().getByteCnt() )
      path = svn::Path::getDirName(*it);
    else
      path = (*it);

    list( path, true );
  }
}

void RpViewModel::list( const sc::String& url, bool refresh )
{
  if( url.isEmpty() )
    return;

  // available url but no refresh..
  Entries::iterator it = _entries.find( url );
  if( it != _entries.end() && !refresh )
    return;

  // already running list for url?
  Running::iterator itr = _running.find( url );
  if( itr != _running.end() )
    return;

  _running.insert(url);

  ListParam* param = new ListParam(
    url,
    new svn::Revision(svn::Revision_Unspecified),
    false /* never recurse */,
    refresh );

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

void RpViewModel::listResult( ListParam* p, const sc::Error* e )
{
  // remove url from running list
  _running.erase( p->getPathOrUrl() );

  if( e )
    return;

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

  // if we know this path, clear its old info.
  Entries::iterator it = _entries.find( p->getPathOrUrl() );
  if( it != _entries.end() )
  {
    // notify that this path is going to change
    emit updatedOld( p->getPathOrUrl(), p->getEntries() );

    _entries.erase(it);
  }

  // and insert the new one.
  EntriesInfo info;
  info.entries = p->getEntries();

  _entries.insert( Entries::value_type(p->getPathOrUrl(),info) );
  _rev = p->getRevnumber();

  // notify that we have new data.
  emit updatedNew( p->getPathOrUrl(), p->getEntries() );
}

void RpViewModel::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 RpViewModel::checkout( bool bookmark )
{
  sc::String url;

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

  CheckoutParam* param = new CheckoutParam( 
    url, _bookmark->getProject()->getCurWorkingCopyPath(),
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)),
    svn::RevisionPtr(new svn::Revision(svn::Revision_Head)),
    true );

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

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

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

void RpViewModel::switchx( bool bookmark )
{
  sc::String url;

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

  SwitchParam* param = new SwitchParam( 
    _bookmark->getProject()->getCurWorkingCopyPath(), url,
    svn::RevisionPtr(new svn::Revision(svn::Revision_Head)),
    true );

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

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

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

void RpViewModel::diff( bool bookmark )
{
  DiffViewModel* model = new DiffViewModel(false,false,true,true,_model);

  if( bookmark )
  {
    model->setPathOrUrl1(_bookmark->getSource());
    model->setPathOrUrl2(_bookmark->getSource());
  }
  else
  {
    const svn::DirEntries& entries = _selection.get();

    model->setDir( entries[0]->isDir() );
    model->setPathOrUrl1( entries[0]->getName() );
    model->setPathOrUrl2( entries[0]->getName() );
    if( entries.size() == 2 )
      model->setPathOrUrl2( entries[1]->getName() );
  }
  model->setRevision1(svn::RevisionHead);
  model->setRevision2(svn::RevisionHead);

  emit showDiff( model );
}

void RpViewModel::merge( bool bookmark )
{
  MergeViewModel* model = new MergeViewModel(false,false,true,true,_model);

  if( bookmark )
  {
    model->setPathOrUrl1(_bookmark->getSource());
    model->setPathOrUrl2(_bookmark->getSource());
  }
  else
  {
    const svn::DirEntries& entries = _selection.get();

    model->setPathOrUrl1( entries[0]->getName() );
    model->setPathOrUrl2( entries[0]->getName() );
    if( entries.size() == 2 )
      model->setPathOrUrl2( entries[1]->getName() );
  }
  model->setRevision1(svn::RevisionHead);
  model->setRevision2(svn::RevisionHead);
  model->setTarget( _bookmark->getProject()->getCurWorkingCopyPath() );

  emit showMerge(model);
}

void RpViewModel::mkdir( bool bookmark )
{
  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 RpViewModel::import( bool bookmark )
{
#if 0
  ExternProviderImpl externProvider(_rpModel);

  ImportDialog* dlg =
    new ImportDialog( &externProvider, _rpModel->isCmdRecursive(), this );

  svn::Paths paths;
  _rpModel->getSelection(paths);

  dlg->setDstUrl( QString::fromUtf8(paths.front()) );

  int result = dlg->exec();

  if( result != QDialog::Accepted )
  {
    return;
  }

  ImportParam*   param = dlg->getParameters();
  PostCmdResult* pcres = new PostCmdResult(this);

  _rpModel->import( param, pcres );
#endif
}

void RpViewModel::exportx( bool bookmark )
{
#if 0
  ExternProviderImpl externProvider(_rpModel);

  ExportDialog* dlg =
    new ExportDialog( &externProvider, _rpModel->isCmdForce(), this );

  svn::Paths paths;
  _rpModel->getSelection(paths);

  dlg->initSrcUrl(QString::fromUtf8(paths.front()) );

  int result = dlg->exec();

  if( result != QDialog::Accepted )
  {
    return;
  }

  ExportParam*   param = dlg->getParameters();
  PostCmdResult* pcres = new PostCmdResult(this);

  _rpModel->exportx( param, pcres );
#endif
}

void RpViewModel::remove()
{
  bool proceed = false;
  emit confirmRemove(proceed);

  if( ! proceed )
    return;

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

  DeleteParam* param = new DeleteParam(sel, false);
  DeleteCmd*   cmd   = new DeleteCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void RpViewModel::cat()
{
}

void RpViewModel::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 RpViewModel::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 RpViewModel::setRpList( const sc::String& url, svn::Revnumber rev, const svn::DirEntries& list )
{
  _model->getListCache()->setRpList( url, rev, list );
}

bool RpViewModel::getRpList( const sc::String& url, svn::Revnumber rev, svn::DirEntries& list )
{
  _model->getListCache()->getRpList( url, rev, list );
  return !list.empty();
}

void RpViewModel::getEntries( const sc::String& path, svn::DirEntries& entries )
{
  ///< \todo rev..!?
  _model->getListCache()->getRpList( path, _rev, entries );
}

void RpViewModel::getEntriesOld( const sc::String& path, svn::DirEntries& entries )
{
  Entries::iterator it = _entries.find( path );
  if( it != _entries.end() )
  {
    entries = (*it).second.entries;
  }
}

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

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

bool RpViewModel::getAutoRefresh()
{
  return true/*_bookmark->getAutoRefresh()*/;
}

void RpViewModel::setSelection( const RpSelection& sel )
{
  _selection = sel;
}

const RpSelection& RpViewModel::getSelection()
{
  return _selection;
}
