/* ====================================================================
 * 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 "LogViewModel.h"
#include "ScModel.h"
#include "DiffViewModel.h"
#include "MergeViewModel.h"
#include "Bookmark.h"
#include "Project.h"
#include "PostCmdResult.h"
#include "events/LogEvent.h"
#include "events/ScParamEvent.h"
#include "events/EventSupport.h"
#include "commands/LogParam.h"
#include "commands/LogCmd.h"
#include "commands/DiffParam.h"
#include "commands/DiffCmd.h"
#include "commands/CatParam.h"
#include "commands/CatCmd.h"
#include "commands/InfoParam.h"
#include "commands/InfoCmd.h"
#include "commands/PropSetRevParam.h"
#include "commands/PropSetRevCmd.h"
#include "sublib/TargetRepository.h"
#include "svn/LogEntry.h"
#include "svn/Info.h"
#include "svn/InfoBaton.h"

class LogViewModelParamVisitor :
  public ParamVisitor<LogParam>,
  public ParamVisitor<PropSetRevParam>,
  public ParamVisitor<DiffParam>,
  public ParamVisitor<CatParam>,
  public ParamVisitor<InfoParam>
{
public:
  LogViewModelParamVisitor( LogViewModel* model )
    : _model(model)
  {
  }

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

  void visit( LogParam* p )
  {
    _model->logResult( p, _event->getError() );
  }

  void visit( PropSetRevParam* p )
  {
  }

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

  void visit( CatParam* p )
  {
    _model->catResult( p, _event->getError() );
  }

  void visit( InfoParam* p )
  {
    _model->infoResult( p, _event->getError() );
  }

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

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

class LogBaton : public svn::LogBaton
{
public:
  LogBaton( ID tid ) :_tid(tid)
  {
  }

  svn::Error* receiveMessage( const svn::LogEntryPtr log )
  {
    QObject* target = TargetRepository::get(_tid);
    if( target )
    {
      postEvent( target, new LogEvent(log) );
    }
    return SVN_NO_ERROR;
  }

private:
  ID _tid;
};

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

class LogViewInfoBaton : public svn::InfoBaton
{
public:
  void info( svn::InfoPtr info )
  {
    _root = info->getRootUrl();
  }
  
  const sc::String& getRoot() const
  {
    return _root;
  }
  
private:
  sc::String _root;
};

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

class SelectedRevisions
{
public:
  SelectedRevisions( const svn::LogEntries& entries, bool undo )
    : _undo(undo)
  {
    _revStop  = entries.front()->getRevnumber();
    _revStart = entries.back()->getRevnumber();

    if( _revStart == _revStop )
      _revStart--;
  }

  svn::Revnumber getStartRevision()
  {
    if( _undo )
    {
      return _revStop;
    }
    else
    {
      return _revStart;
    }
  }

  svn::Revnumber getStopRevision()
  {
    if( _undo )
    {
      return _revStart;
    }
    else
    {
      return _revStop;
    }
  }

private:
  bool           _undo;
  svn::Revnumber _revStart;
  svn::Revnumber _revStop;
};

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

LogViewModel::LogViewModel( const sc::String& name, bool dir, Bookmark* bm,
  ScModel* model )
: TargetId(this), _name(name), _dir(dir), _bookmark(bm), _model(model)
{
  _limit      = true;
  _limitVal   = 50;
  _copies     = false;
  _changes    = true;
  _visualDiff = true;

  _revStart = svn::RevisionPtr(new svn::Revision(svn::Revision_Head));
  _revStop  = svn::RevisionPtr(new svn::RevisionNumber(0));

  
  InfoParam* param = new InfoParam( name,
    new svn::Revision(svn::Revision_Head),
    new svn::Revision(svn::Revision_Unspecified) /*no peg*/,
    false, new LogViewInfoBaton() );

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

LogViewModel::~LogViewModel()
{
}

bool LogViewModel::event( QEvent* e )
{
  switch( e->type() )
  {
  case ScParameterEvent:
    {
      LogViewModelParamVisitor visitor(this);
      visitor.run(dynamic_cast<ScParamEvent*>(e));
      return true;
    }
  case ScLogEvent:
    {
      LogEvent* le = dynamic_cast<LogEvent*>(e);
      emit addLogEntry(le->getLog());
      return true;
    }
  default:
    {
      return super::event(e);
    }
  }
}

DiffViewModel* LogViewModel::createDiffViewModel()
{
  SelectedRevisions rev(_selection,false);

  svn::RevisionPtr rev1 = svn::RevisionPtr(new svn::RevisionNumber(rev.getStartRevision()));
  svn::RevisionPtr rev2 = svn::RevisionPtr(new svn::RevisionNumber(rev.getStopRevision()));

  DiffViewModel* model = new DiffViewModel(false,false,false,true,_model);
  model->setPathOrUrl1(_name);
  model->setPathOrUrl2(_name);
  model->setRevision1(rev1);
  model->setRevision2(rev2);
  model->setDir(_dir);

  return model;
}

MergeViewModel* LogViewModel::createMergeViewModel( bool undo )
{
  SelectedRevisions rev(_selection,undo);

  svn::RevisionPtr rev1 = svn::RevisionPtr(new svn::RevisionNumber(rev.getStartRevision()));
  svn::RevisionPtr rev2 = svn::RevisionPtr(new svn::RevisionNumber(rev.getStopRevision()));

  MergeViewModel* model = new MergeViewModel(false,false,false,true, _model);
  model->setPathOrUrl1(_name);
  model->setPathOrUrl2(_name);
  model->setRevision1(rev1);
  model->setRevision2(rev2);

  // add target working copies
  Project* prj = _bookmark->getProject();

  // default target
  model->setDefaultTarget( prj->getCurWorkingCopyPath() );

  // all possible targets
  Project::Items items;
  prj->getItems(items);

  for( Project::Items::iterator it = items.begin(); it != items.end(); it++ )
  {
    const Project::Item& item = *it;

    if( item.isWorkingCopy() )
    {
      model->addTarget( item.getSource() );
    }
  }

  return model;
}

const sc::String& LogViewModel::getName() const
{
  return _name;
}

bool LogViewModel::isDir() const
{
  return _dir;
}

Bookmark* LogViewModel::getBookmark() const
{
  return _bookmark;
}

void LogViewModel::setStartRevision( svn::RevisionPtr rev )
{
  _revStart = rev;
}

void LogViewModel::setStopRevision( svn::RevisionPtr rev )
{
  _revStop = rev;
}

void LogViewModel::setCopies( bool on )
{
  _copies = on;
}

void LogViewModel::setChanges( bool on )
{
  _changes = on;
}

void LogViewModel::setLimit( bool on )
{
  _limit = on;
}

void LogViewModel::setLimitVal( int limit )
{
  _limitVal = limit;
}

void LogViewModel::setVisualDiff(bool visual)
{
  _visualDiff = visual;
}

void LogViewModel::setSelection( svn::LogEntries& entries )
{
  _selection = entries;
}

void LogViewModel::setSelection( svn::Paths& paths )
{
  _selectionPaths = paths;
}

svn::RevisionPtr LogViewModel::getStartRevision()
{
  return _revStart;
}

svn::RevisionPtr LogViewModel::getStopRevision()
{
  return _revStop;
}

bool LogViewModel::getCopies()
{
  return _copies;
}

bool LogViewModel::getChanges()
{
  return _changes;
}

bool LogViewModel::getLimit()
{
  return _limit;
}

int LogViewModel::getLimitVal()
{
  return _limitVal;
}

bool LogViewModel::isVisualDiff()
{
  return _visualDiff;
}

void LogViewModel::log()
{
  svn::Paths paths;
  paths.push_back(_name);

  LogParam* param = new LogParam( paths, _revStart, _revStop,
    (_limit ? _limitVal : 0), _changes, !_copies, new LogBaton(getTid()) );

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

void LogViewModel::logResult( LogParam*, const sc::Error* )
{
  emit doneLogEntry();
}

void LogViewModel::propsetrev()
{
  PropSetRevParam* param = new PropSetRevParam(
    sc::String("svn:log"), _selection[0]->getMessage(), _name,
    svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    false /*allow newline in svn:author*/ );

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

void LogViewModel::diffc()
{
  assert(_selection.size()>0);

  DiffParam* param = new DiffParam(
    _name, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber()-1)),
    _name, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    false, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    _dir, true, true, false, _dir || ! _visualDiff );

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

void LogViewModel::diffCommitPath()
{
  assert(_selectionPaths.size()>0);

  sc::String path = _root + *_selectionPaths.begin();
  
  DiffParam* param = new DiffParam(
    path, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber()-1)),
    path, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    true, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    _dir, true, true, false, ! _visualDiff );
  
  DiffCmd* cmd = new DiffCmd( param, new PostCmdResult(this) );
  _model->runThreaded(cmd);
}

void LogViewModel::diffwc()
{
  assert(_selection.size()>0);

  DiffParam* param = new DiffParam(
    _name, svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())),
    _name, svn::RevisionPtr(new svn::Revision(svn::Revision_Working)),
    false, svn::RevisionPtr() /*no peg*/, _dir, true, true, false, _dir || ! _visualDiff );

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

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

void LogViewModel::cat()
{
  assert(_selection.size()>0);

  CatParam* param = new CatParam( _name,
    svn::RevisionPtr(new svn::RevisionNumber(_selection[0]->getRevnumber())) );

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

void LogViewModel::catResult( CatParam* p, const sc::Error* e )
{
  emit showCat(
    QString::fromUtf8(p->getPathOrUrl()), QString::fromLocal8Bit(p->getValue()) );
}

void LogViewModel::infoResult( InfoParam* p, const sc::Error* e )
{
  _root = ((LogViewInfoBaton*)p->getBaton())->getRoot();
}
