/* ====================================================================
 * 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 "DiffViewModel.h"
#include "ScModel.h"
#include "PostCmdResult.h"
#include "commands/DiffParam.h"
#include "commands/DiffCmd.h"
#include "events/ScParamEvent.h"
#include "events/EventSupport.h"
#include "events/DiffSummarizeEvent.h"
#include "sublib/TargetRepository.h"
#include "svn/DiffSummarize.h"
#include "svn/Path.h"

// qt
#include <QtCore/QThread>
#include <QtGui/QApplication>


class DiffViewModelParamVisitor :
  public ParamVisitor<DiffParam>
{
public:
  DiffViewModelParamVisitor( DiffViewModel* model )
    : _model(model)
  {
  }

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

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

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

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

class SummarizeBaton : public svn::DiffSummarizeBaton
{
public:
  SummarizeBaton( ID tid ) :_tid(tid)
  {
  }

  void summarize( svn::DiffSummarizePtr summarize )
  {
    QObject* target = TargetRepository::get(_tid);
    if( target )
    {
      postEvent( target, new DiffSummarizeEvent(summarize) );
    }
  }

private:
  ID _tid;
};

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

class DiffThread : public QThread
{
public:
  DiffThread( ScModel* model, DiffViewModel::DiffCmds& cmds )
    : _model(model), _cmds(cmds), _run(true)
  {
  }
  
  ~DiffThread()
  {
  }

  void run()
  {
    while( _run && _cmds.size() > 0 )
    {
      DiffViewModel::DiffCmds::iterator it = _cmds.begin();
      if( it != _cmds.end() )
      {
        _model->runBlocked(*it);
        _cmds.erase(it);
      }
    }

    for( DiffViewModel::DiffCmds::iterator it = _cmds.begin(); it != _cmds.end(); it++ )
    {
      delete *it;
    }
  }

  void stop()
  {
    _run = false;
  }

private:
  bool                     _run;
  ScModel*                 _model;
  DiffViewModel::DiffCmds  _cmds;
};

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

DiffViewModel::DiffViewModel( bool changePathOrUrl1, bool changePathOrUrl2,
  bool changeType, bool pegDiff, ScModel* model )
: TargetId(this), _model(model), _changePathOrUrl1(changePathOrUrl1),
  _changePathOrUrl2(changePathOrUrl2), _changeType(changeType),
  _pegDiff(pegDiff), _diffThread(NULL)
{
  _dir        = false;
  _pathOrUrl1 = "unset";
  _pathOrUrl2 = "unset";
  _rev1       = svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified));
  _rev2       = svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified));
  _revPeg     = svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified));
  _recursive  = _model->getOptionCommandRecurse();
  _copies     = true;
  _deletes    = true;
}

DiffViewModel::~DiffViewModel()
{

}

bool DiffViewModel::event( QEvent* e )
{
  switch( e->type() )
  {
  case ScParameterEvent:
    {
      DiffViewModelParamVisitor visitor(this);
      visitor.run(dynamic_cast<ScParamEvent*>(e));
      return true;
    }
  case ScDiffSummarizeEvent:
    {
      DiffSummarizeEvent* se = dynamic_cast<DiffSummarizeEvent*>(e);
      emit addSumEntry(se->getDiffSummarize());
      return true;
    }
  default:
    {
      return super::event(e);
    }
  }
}

bool DiffViewModel::getChangePathOrUrl1()
{
  return _changePathOrUrl1;
}

bool DiffViewModel::getChangePathOrUrl2()
{
  return _changePathOrUrl2;
}

bool DiffViewModel::getChangeType()
{
  return _changeType;
}

bool  DiffViewModel::getDir()
{
  return _dir;
}

const sc::String& DiffViewModel::getPathOrUrl1()
{
  return _pathOrUrl1;
}

const sc::String& DiffViewModel::getPathOrUrl2()
{
  return _pathOrUrl2;
}

const svn::RevisionPtr DiffViewModel::getRevisionPeg()
{
  return _revPeg;
}

const svn::RevisionPtr DiffViewModel::getRevision1()
{
  return _rev1;
}

const svn::RevisionPtr DiffViewModel::getRevision2()
{
  return _rev2;
}

bool DiffViewModel::getRecursive()
{
  return _recursive;
}

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

bool DiffViewModel::getDeletes()
{
  return _deletes;
}

bool DiffViewModel::getPegDiff()
{
  return _pegDiff;
}

void DiffViewModel::setDir( bool dir )
{
  _dir = dir;
}

void DiffViewModel::setPathOrUrl1( const sc::String& pathOrUrl )
{
  _pathOrUrl1 = pathOrUrl;
}

void DiffViewModel::setPathOrUrl2( const sc::String& pathOrUrl )
{
  _pathOrUrl2 = pathOrUrl;
}

void DiffViewModel::setRevisionPeg( svn::RevisionPtr rev )
{
  _revPeg = rev;
}

void DiffViewModel::setRevision1( svn::RevisionPtr rev )
{
  _rev1 = rev;
}

void DiffViewModel::setRevision2( svn::RevisionPtr rev )
{
  _rev2 = rev;
}

void DiffViewModel::setRecursive( bool on )
{
  _recursive = on;
}

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

void DiffViewModel::setDeletes( bool on )
{
  _deletes = on;
}

void DiffViewModel::setPegDiff( bool on )
{
  _pegDiff = on;
}

bool DiffViewModel::hasSelection()
{
  return _selection.size() > 0;
}

void DiffViewModel::setSelection( const svn::DiffSummarizes& selection )
{
  _selection = selection;
}

bool DiffViewModel::runs()
{
  return _diffThread != NULL;
}

void DiffViewModel::stop()
{
  assert( _diffThread != NULL );

  if( _diffThread )
  {
    _diffThread->stop();

    // wait until the thread is stopped.
    // this may take a while if we wait for submerge to exit, so keep the gui alive.
    while( ! _diffThread->wait(10) )
    {
      QApplication::processEvents( QEventLoop::AllEvents, 500 );
    }

    delete _diffThread;
    _diffThread = NULL;
  }
}

void DiffViewModel::diff()
{
  if( _selection.size() > 0 )
  {
    assert( _diffThread == NULL );

    DiffCmds cmds;
    createSelectionCmds(cmds);

    if( cmds.size() > 1 )
    {
      _diffThread = new DiffThread( _model, cmds );
      _diffThread->start();
    }
    else
    {
      _model->runThreaded( cmds.front() );
    }
  }
  else
  {
    DiffParam* param = new DiffParam(
      _pathOrUrl1, _rev1,
      _pathOrUrl2, _rev2,
      getPegDiff(), calcPegRev(), _recursive, _copies, _deletes,
      false /*summarize*/, _dir );

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

void DiffViewModel::patch()
{
  DiffParam* param = new DiffParam(
    _pathOrUrl1, _rev1,
    _pathOrUrl2, _rev2,
    getPegDiff(), calcPegRev(), _recursive, _copies, _deletes,
    false /*summarize*/, true /*patch*/ );

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

void DiffViewModel::createSelectionCmds( DiffCmds& cmds )
{
  for( svn::DiffSummarizes::iterator it = _selection.begin(); it != _selection.end(); it++ )
  {
    QString path1;
    QString path2;

    if( _pathOrUrl1 == _pathOrUrl2 )
    {
      path1 = QString::fromUtf8(_pathOrUrl1);
      QString name = QString::fromUtf8((*it)->getName());

      if( ! path1.contains(name) )
      {
        path1 += "/"; path1 += name;
      }
      path2 = path1;
    }
    else
    {
      path1 = QString::fromUtf8(_pathOrUrl1);
      path2 = QString::fromUtf8(_pathOrUrl2);
      QString name  = QString::fromUtf8((*it)->getName());

      if( ! path1.contains(name) )
      {
        path1 += "/"; path1 += name;
        path2 += "/"; path2 += name;
      }
    }

    DiffParam* param = new DiffParam(
      sc::String(path1.utf8()), _rev1,
      sc::String(path2.utf8()), _rev2,
      getPegDiff(), calcPegRev(), _recursive, _copies, _deletes,
      false /*summarize*/, (*it)->isDir() );

    DiffCmd* cmd = new DiffCmd( param, new PostCmdResult(this) );
    cmds.push_back(cmd);
  }
}

svn::RevisionPtr DiffViewModel::calcPegRev()
{
  if( ! getPegDiff() || ! _revPeg->equals( &svn::RevUnspecified )  )
    return _revPeg;

  if( svn::Path::isUrl(_pathOrUrl1) )
  {
    return svn::RevisionHead;
  }
  else
  {
    return svn::RevisionBase;
  }
}

void DiffViewModel::summarize()
{
  DiffParam* param = new DiffParam(
    _pathOrUrl1, _rev1,
    _pathOrUrl2, _rev2,
    getPegDiff(), calcPegRev(), _recursive, _copies, _deletes,
    true /*summarize*/, false /*patch*/, new SummarizeBaton(getTid()) );

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

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