/* ====================================================================
 * Copyright (c) 2003-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 "CommitDialog.h"
#include "CommitItemLvi.h"
#include "WcModel.h"
#include "WcViewModel.h"
#include "ScModel.h"
#include "TextEdit.h"
#include "PostCmdResult.h"
#include "NullCmdResult.h"
#include "Commit.h"
#include "Settings.h"
#include "settings/FontSettings.h"
#include "settings/CommitLogHistory.h"
#include "commands/DiffParam.h"
#include "commands/CommitParam.h"
#include "sublib/Gui.h"
#include "sublib/Splitter.h"
#include "sublib/SplitLayout.h"
#include "sublib/settings/LayoutSettings.h"
#include "svn/Path.h"
#include "svn/CommitItem.h"
#include "util/String.h"
#include "util/Condition.h"
#include "util/max.h"

// qt
#include <QtCore/QTime>
#include <QtGui/QLayout>
#include <QtGui/QPushButton>
#include <QtGui/QLabel>
#include <QtGui/QComboBox>
#include <QtGui/QFileDialog>
#include <QtGui/QToolTip>
#include <QtGui/QCheckBox>
#include <QtGui/QTextEdit>
#include <QtGui/QListView>
#include <QtGui/QGroupBox>
#include <QtGui/QTreeWidget>
#include <QtGui/QHeaderView>
#include <QtGui/QDialogButtonBox>


CommitDialog::CommitDialog( bool direct, bool bookmark, WcViewModel* model,
  CommitLogHistory* logHistory, const QFont& font, QWidget *parent )
: super( parent, NULL, true ),
  _direct(direct), _bookmark(bookmark), _model(model), _logHistory(logHistory),
  _commitTriggered(false), _cond(NULL), _result(NULL)
{
  setName( "CommitDialog" );
  setCaption( _q("subcommander:commit") );

  QVBoxLayout *vbl = new QVBoxLayout(this);
  vbl->setContentsMargins(5,5,5,5);
  vbl->setSpacing(5);
  {
    QGroupBox* gb = new QGroupBox(this);
    gb->setTitle( _q("commit options: ") );
    gb->setFlat(true);
    vbl->addWidget(gb);

    QHBoxLayout* h1 = new QHBoxLayout;
    h1->setContentsMargins(0,10,0,0);
    vbl->addLayout(h1);
    {
      _recurse = new QCheckBox( _q("&recursive"), this );
      _recurse->setChecked( _model ? _model->getRecursive() : false );
      _recurse->setEnabled(_direct);
      h1->addWidget(_recurse);

      _keepLocks = new QCheckBox( _q("&keep locks"), this );
      _keepLocks->setChecked(false);
      _keepLocks->setEnabled(_direct);
      h1->addWidget(_keepLocks);

      h1->addStretch(1); 

      _log = new QComboBox(false,this);
      _log->setMaximumWidth(300);
      h1->addWidget(_log);

      connect( _log, SIGNAL(activated(int)), this, SLOT(logHistory(int)) );
      {
        CommitLogHistory::Logs logs;
        _logHistory->getLogs(logs);

        for( CommitLogHistory::Logs::iterator it = logs.begin(); it != logs.end(); it++ )
        {
          QString qlog = QString::fromUtf8( *it );
          _log->insertItem(qlog,0);
        }
        _log->setCurrentIndex(0);
      }
    }

    Splitter* v = new Splitter( this, Qt::Vertical, Splitter::Last );
    v->setContentsMargins(0,0,0,0);
    vbl->addWidget(v);
    {
      QWidget*     v1 = new QWidget(v);
      QVBoxLayout* vl1  = new QVBoxLayout(v1);
      vl1->setContentsMargins(0,0,0,0);
      v->addWidget(v1);
      {
        _edit = new TextEdit(v1);
        vl1->addWidget(_edit);
        _edit->setFont( font );
        _edit->setUndoRedoEnabled(true);
        _edit->setTabChangesFocus(true);
        _edit->setSizePolicy( QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding) );
        //_edit->setWrapPolicy( QTextEdit::AtWordBoundary );
        //_edit->setWordWrap( QTextEdit::FixedColumnWidth );
        //_edit->setWrapColumnOrWidth(72);
        _edit->setMinimumWidth( 500 );
        _edit->setWordWrapMode( QTextOption::NoWrap );
        
        _pos = new QLabel(v1);
        vl1->addWidget(_pos);
        
        connect( _edit, SIGNAL(cursorPositionChanged()), SLOT(cursorChanged()) );
        connect( _edit, SIGNAL(textChanged()), SLOT(logChanged()) );

        QGridLayout* gl = new QGridLayout;
        gl->setContentsMargins(0,0,0,0);
        gl->setSpacing(0);
        vl1->addLayout(gl);
        {
          QLabel* lUrl  = new QLabel( _q("destination url: "), v1 );
          QLabel* lPath = new QLabel( _q("source path: "), v1 );
          
          gl->addWidget( lUrl, 0, 0 );
          gl->addWidget( lPath, 1, 0 );
          
          _url  = new QLabel(v1);
          _path = new QLabel(v1);
          
          gl->addWidget( _url, 0, 2 );
          gl->addWidget( _path, 1, 2 );
          
          gl->setColStretch( 0, 1 );
          gl->setColStretch( 1, 0 );
          gl->setColSpacing( 1, 10 );
          gl->setColStretch( 2, 10 );
        }        
      }

      QWidget*     v2 = new QWidget(v);
      QVBoxLayout* vl2  = new QVBoxLayout(v2);
      vl2->setContentsMargins(0,0,0,0);
      v->addWidget(v2);
      {
        QTreeWidgetItem* header = new QTreeWidgetItem();
        header->setText( 0, _q("kind") );
        header->setText( 1, _q("flags") );
        header->setText( 2, _q("item") );
        header->setTextAlignment( 0, Qt::AlignLeft );
        header->setTextAlignment( 1, Qt::AlignLeft );
        header->setTextAlignment( 2, Qt::AlignLeft );

        _list = new QTreeWidget(v2);
        vl2->addWidget(_list);
        _list->setHeaderItem(header);
        _list->setColumnCount(3);
        _list->setSelectionMode(QTreeWidget::SingleSelection);
        _list->setSelectionBehavior(QAbstractItemView::SelectRows);
        _list->setAllColumnsShowFocus(true);
        _list->setRootIsDecorated(false);
        _list->setSortingEnabled(true);
        //_list->header()->setResizeMode( QHeaderView::ResizeToContents );
        _list->header()->setStretchLastSection(true);      
        _list->sortItems(2,Qt::Ascending);

        connect( 
          _list, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
          this,  SLOT(doubleClicked(QTreeWidgetItem*,int)) );        
      }
    }


    QDialogButtonBox* db = new QDialogButtonBox(this);
    db->setContentsMargins(5,5,5,5);
    vbl->addWidget(db);
    {
      int minWidth = 0;

      _ls = new QPushButton(this);
      _ls->setEnabled(_direct);
      _ls->setText( _q("List &Items") );
      _ls->setDefault(_direct);
      db->addButton( _ls, QDialogButtonBox::ActionRole );
      minWidth = std::max( minWidth, _ls->sizeHint().width() );
      
      _ok = new QPushButton(this);
      _ok->setEnabled(false);
      _ok->setText( _q("C&ommit") );
      _ok->setDefault(!_direct);
      db->addButton( _ok, QDialogButtonBox::AcceptRole );
      minWidth = std::max( minWidth, _ok->sizeHint().width() );
      
      _ca = new QPushButton(this);
      _ca->setText( _q("&Cancel") );
      _ok->setAutoDefault(true);
      db->addButton( _ca, QDialogButtonBox::RejectRole );
      minWidth = std::max( minWidth, _ca->sizeHint().width() );
      
      _ls->setMinimumWidth(minWidth);
      _ok->setMinimumWidth(minWidth);
      _ca->setMinimumWidth(minWidth);
      
      connect( _ls, SIGNAL(clicked()), SLOT(list()) );
      connect( _ok, SIGNAL(clicked()), SLOT(commit()) );
      connect( _ca, SIGNAL(clicked()), SLOT(cancel()) );
    }
  }

  if( _model )
  {
    connect( 
      _model, SIGNAL(confirmCommit(CommitParam*,bool&)),
      this, SLOT(confirmCommit(CommitParam*,bool&)) );
  }

  Settings s;
  resize( s.layout().getSize( name(), QSize(800,700) ) );
}

CommitDialog::~CommitDialog()
{
  Settings s;
  s.layout().setSize( name(), geometry().size() );

  if( _model )
  {
    disconnect( 
      _model, SIGNAL(confirmCommit(CommitParam*,bool&)),
      this, SLOT(confirmCommit(CommitParam*,bool&)) );
  }
}

void CommitDialog::setCommitItems( const svn::CommitItems& items )
{
  svn::Paths urls;
  svn::Paths paths;
  for( svn::CommitItems::const_iterator it = items.begin(); it != items.end(); it++ )
  {
    const svn::CommitItemPtr item = *it;

    if( ! item->getUrl().isEmpty() )
      urls.push_back( item->getUrl() );  

    if( ! item->getPath().isEmpty() )
      paths.push_back( item->getPath() );        
  }

  QString urlPrefix = QString::fromUtf8(svn::Path::findPrefix(urls));
  QString pathPrefix = QString::fromUtf8(svn::Path::findPrefix(paths));
  
  _url->setText(urlPrefix);
  _path->setText(pathPrefix);

  QList<QTreeWidgetItem*> listItems;
  for( svn::CommitItems::const_iterator it = items.begin(); it != items.end(); it++ )
  {
    const svn::CommitItemPtr item = *it;    
    QString itemStr = QString::fromUtf8(item->getUrl()).remove(0,urlPrefix.length()+1);

    CommitItemLvi* lvi = new CommitItemLvi( item, itemStr );
    listItems.push_back(lvi);
  }
  _list->addTopLevelItems(listItems);

  logChanged();
}

void CommitDialog::setCondition( sc::Condition* cond )
{
  _cond = cond;
}

void CommitDialog::setResult( CommitResult* res )
{
  _result = res;
}

void CommitDialog::doubleClicked( QTreeWidgetItem* item, int column )
{
  // should not be called on implict commits
  assert(_model);

  if( ! item )
    return;

  CommitItemLvi* lvi    = dynamic_cast<CommitItemLvi*>(item);
  svn::CommitItemPtr ci = lvi->getItem();
  
  sc::String path = ci->getPath();
  bool       dir  = ci->isDir();

  if( path.isEmpty() )
    return;

  _model->diffBase( path, dir );
}

void CommitDialog::cursorChanged()
{
  QTextCursor c = _edit->textCursor();
  _pos->setText( QString(_q("Line: %1  Column: %2")).
    arg(c.blockNumber()+1,3).
    arg(c.columnNumber()+1,3) );
}

void CommitDialog::logChanged()
{
  _ok->setEnabled( (!_edit->text().isEmpty()) ); 
}

void CommitDialog::logHistory( int index )
{
  // insertParagraph adds an unwanted newline at the end of our log text,
  // so we use insert.
  _edit->insert(_log->text(index));
}

void CommitDialog::list()
{
  _commitTriggered = true;
  
  _recurse->setEnabled(false);
  _keepLocks->setEnabled(false);
  _ls->setEnabled(false);
  _ok->setEnabled(false);

  _model->commit( _bookmark );
}

void CommitDialog::commit()
{
  _recurse->setEnabled(false);
  _keepLocks->setEnabled(false);
  _ls->setEnabled(false);
  _ok->setEnabled(false);

  // commit not yet triggered by list?
  if( _direct && ! _commitTriggered )
  {
    // trigger it, directly pass log message (see confirmCommit())
    _model->commit(_bookmark);
  }

  finish(true);
  accept();
}

void CommitDialog::cancel()
{
  finish(false);
  reject();
}

void CommitDialog::finish( bool commit )
{
  sc::String log(_edit->text().utf8());

  if( ! log.isEmpty() )
  {
    _logHistory->addLog(log);
  }

  if( _result )
  {
    _result->setCommit(commit);
    _result->setLog(log);
  }

  if( _cond )
  {
    _cond->wakeAll();
  }
}

void CommitDialog::confirmCommit( CommitParam* param, bool& proceed )
{
  if( _commitTriggered )
  {
    // commit was triggered by list()
    param->setRecurse( _recurse->isChecked() );
    param->setKeepLocks( _keepLocks->isChecked() );
    param->setBaton( new Commit(this) );

    proceed = true;
  }
  else if( _direct )
  {
    // not triggered by list() but by commit()

    // skip list and directly pass the log message
    sc::String log(_edit->text().utf8());

    param->setRecurse( _recurse->isChecked() );
    param->setKeepLocks( _keepLocks->isChecked() );
    param->setBaton( new Commit(log) );

    proceed = true;
  }
}
