/* ====================================================================
 * 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 "LogGraphViewModel.h"
#include "ScModel.h"
#include "PostCmdResult.h"
#include "DiffViewModel.h"
#include "commands/LogGraphParam.h"
#include "commands/LogGraphCmd.h"
#include "events/LogEvent.h"
#include "events/ScParamEvent.h"
#include "events/EventSupport.h"
#include "sublib/TargetId.h"
#include "sublib/TargetRepository.h"
#include "svn/LogEntry.h"

// qt
#include <QtCore/QRegExp>
#include <QtCore/QDateTime>
#include <QtXml/QXmlSimpleReader>

// sys
#include "util/max.h"


class LogGraphViewModelParamVisitor :
  public ParamVisitor<LogGraphParam>
{
public:
  LogGraphViewModelParamVisitor( LogGraphViewModel* model )
    : _model(model)
  {
  }

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

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

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

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

class LogGraphBatonImpl : public LogGraphBaton
{
public:
  LogGraphBatonImpl( ID tid ) : _tid(tid)
  {
  }

  ~LogGraphBatonImpl()
  {
  }

  // LogGraphBaton
  void setUrlInfo( const sc::String& root, const sc::String& url )
  {
    _rootPath  = root;
    _startPath = url;
  }

  void addLogMsg( const svn::LogEntryPtr log )
  {
    QObject* target = TargetRepository::get(_tid);
    if( target )
    {
      postEvent( target, new LogEvent(log) );
    }
  }

  const sc::String& getStartPath() const
  {
    return _startPath;
  }

  const sc::String& getRootPath() const
  {
    return _rootPath;
  }


private:
  ID             _tid;

  sc::String     _startPath;
  sc::String     _rootPath;
};

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

class LogParser : public QXmlDefaultHandler
{
  typedef std::vector<QString> Stack;

public:
  bool startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& atts )
  {
    _tags.push_back(qName);    

    if( qName == "logentry" )
    {
      _rev      = atts.value( "revision" );
      _date     = "";
      _author   = "";
      _msg      = "";

      _path     = "";
      _action   = "";
      _copyfrom = "";
      _copyrev  = "";
    }
    else if( qName == "path" )
    {
      _path     = "";
      _action   = atts.value( "action" );
      _copyfrom = atts.value( "copyfrom-path" );
      _copyrev  = atts.value( "copyfrom-rev" );
    }
    else if( qName == "paths" )
    {
      _paths.clear();
    }
    return true;
  }

  bool characters( const QString & ch )
  {
    if( _tags.back() == "author" )
    {
      _author += ch;
    }
    else if( _tags.back() == "date" )
    {
      _date += ch;
    }
    else if( _tags.back() == "path" )
    {
      _path += ch;
    }
    else if( _tags.back() == "msg" )
    {
      _msg += ch;
    }
    return true;
  }

  bool endElement( const QString&, const QString&, const QString& qName )
  {
    _tags.pop_back();

    if( qName == "logentry" )
    {
      QRegExp d( "(\\d{4})-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d{6})Z" );
      /*bool matched =*/ d.exactMatch(_date);
      QStringList list = d.capturedTexts();
      QDate date;
      date.setYMD( list[1].toInt(), list[2].toInt(), list[3].toInt() );
      QTime time;
      time.setHMS( list[4].toInt(), list[5].toInt(), list[6].toInt() );
      QDateTime dtime;
      dtime.setDate(date);
      dtime.setTime(time);

      svn::LogEntryPtr entry = svn::LogEntryPtr( new svn::LogEntry(
        _rev.toLongLong(),
        (svn::Date)dtime.toTime_t()*(svn::Date)1000/*+list[7].toLongLong()*/,
        sc::String(_author.utf8()),
        sc::String(_msg.utf8()))
      );

      for( svn::LogEntry::ChangedPaths::iterator it = _paths.begin(); it != _paths.end(); it++ )
      {
        entry->addPath(*it);
      }

      _entries.push_back( entry );
      _maxRev = std::max( _maxRev, entry->getRevnumber() );
    }
    else if( qName == "path" )
    {
      svn_log_changed_path_t cpath;
      QByteArray cf = _copyfrom.utf8();
      cpath.action        = _action.isEmpty()   ? 0                     : _action[0].latin1();
      cpath.copyfrom_path = _copyfrom.isEmpty() ? NULL                  : (const char*)cf;
      cpath.copyfrom_rev  = _copyrev.isEmpty()  ? svn::InvalidRevnumber : _copyrev.toLongLong();
      svn::LogEntry::Path path(_path.utf8(),&cpath);
      _paths.push_back(path);
    }
    return true;
  }


  void run( const sc::String& xmlLog )
  {
    QFile xmlFile( QString::fromUtf8(xmlLog) );
    QXmlInputSource source( &xmlFile );
    QXmlSimpleReader reader;
    reader.setContentHandler( this );
    reader.parse( source );
  }

private:
  Stack   _tags;

  // temporary data
  QString _rev;
  QString _date;
  QString _author;
  QString _msg;

  QString _path;
  QString _copyfrom;
  QString _copyrev;
  QString _action;

  svn::LogEntry::ChangedPaths _paths;

public:
  // result
  svn::LogEntries _entries;
  svn::Revnumber  _maxRev;
};

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

LogGraphViewModel::LogGraphViewModel( const sc::String& name, bool dir,
  Bookmark* bm, ScModel* model )
: TargetId(this), _name(name), _dir(dir), _bookmark(bm), _model(model),
  _maxRev(0)
{
}

LogGraphViewModel::~LogGraphViewModel()
{
}

bool LogGraphViewModel::event( QEvent* e )
{
  switch( e->type() )
  {
  case ScParameterEvent:
    {
      LogGraphViewModelParamVisitor visitor(this);
      visitor.run(dynamic_cast<ScParamEvent*>(e));
      return true;
    }
  case ScLogEvent:
    {
      LogEvent*        le  = dynamic_cast<LogEvent*>(e);
      svn::LogEntryPtr log = le->getLog();
      _entries.push_back(log);
      _maxRev = std::max(_maxRev,log->getRevnumber());
      return true;
    }
  default:
    {
      return super::event(e);
    }
  }
}

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

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

const sc::String& LogGraphViewModel::getStartPath() const
{
  return _startPath;
}

const sc::String& LogGraphViewModel::getRootPath() const
{
  return _rootPath;
}

svn::Revnumber LogGraphViewModel::getMaxRev() const
{
  return _maxRev;
}

const svn::LogEntries& LogGraphViewModel::getLogEntries() const
{
  return _entries;
}

void LogGraphViewModel::setSelection( const LogGraphSelection& sel )
{
  _selection = sel;
}

DiffViewModel* LogGraphViewModel::createDiffViewModel()
{
  assert(_selection.size()==2);

  DiffViewModel* model = new DiffViewModel(false,false,false,true,_model);

  model->setPathOrUrl1(_selection.front()._path);
  model->setPathOrUrl2(_selection.back()._path);
  model->setRevision1(
    svn::RevisionPtr(const_cast<svn::Revision*>(_selection.front()._rev->dup())));
  model->setRevision2(
    svn::RevisionPtr(const_cast<svn::Revision*>(_selection.back()._rev->dup())));
  model->setDir(_dir);

  return model;
}

void LogGraphViewModel::log()
{
  // clear state
  _entries.clear();

#if 1

  LogGraphBatonImpl* b = new LogGraphBatonImpl(getTid());
  LogGraphParam* param = new LogGraphParam(_name, b);

  // production code
  LogGraphCmd*   cmd   = new LogGraphCmd(param, new PostCmdResult(this) );
  _model->runThreaded(cmd);

#else

  // test code, read svn log xml output
  sc::String file("D:/dev/src/subcommander/sc/bin/debug/sc.xml");
  sc::String path("/trunk/svndiff/svndiff.rc");
  sc::String root("http://subcommander.tigris.org/subcommander");

  //sc::String file("D:/dev/src/subcommander/sc/bin/debug/svn.xml");
  //sc::String path("/trunk");
  //sc::String root("https://svn.collab.net/repos/svn");

  LogParser p;
  p.run( file );

  _startPath = path;
  _rootPath  = root;
  _entries   = p._entries;
  _maxRev    = p._maxRev;

  emit graph();

#endif
}

void LogGraphViewModel::logResult( LogGraphParam* p, const sc::Error* )
{
  LogGraphBatonImpl* b = dynamic_cast<LogGraphBatonImpl*>(p->getBaton());

  _startPath = b->getStartPath();
  _rootPath  = b->getRootPath();

  emit graph();
}
