/* ====================================================================
 * 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 "WcModel.h"
#include "ScModel.h"
#include "Project.h"
#include "WcStatusInfo.h"
#include "commands/AddCmd.h"
#include "commands/AddParam.h"
#include "commands/CommitCmd.h"
#include "commands/CommitParam.h"
#include "commands/CopyCmd.h"
#include "commands/CopyParam.h"
#include "commands/DiffCmd.h"
#include "commands/DiffParam.h"
#include "commands/LogCmd.h"
#include "commands/LogParam.h"
#include "commands/MkdirCmd.h"
#include "commands/MkdirParam.h"
#include "commands/MoveCmd.h"
#include "commands/MoveParam.h"
#include "commands/PropGetCmd.h"
#include "commands/PropGetParam.h"
#include "commands/PropListCmd.h"
#include "commands/PropListParam.h"
#include "commands/PropSetCmd.h"
#include "commands/PropSetParam.h"
#include "commands/StatusCmd.h"
#include "commands/StatusCmd2.h"
#include "commands/StatusParam.h"
#include "commands/RevertCmd.h"
#include "commands/RevertParam.h"
#include "commands/UpdateCmd.h"
#include "commands/UpdateParam.h"
#include "commands/EditConflictCmd.h"
#include "commands/EditConflictParam.h"
#include "commands/ResolvedCmd.h"
#include "commands/ResolvedParam.h"
#include "commands/CleanupCmd.h"
#include "commands/CleanupParam.h"
#include "sublib/PathOps.h"
#include "util/Guard.h"
#include "util/apr.h"
#include "svn/WcStatus.h"
#include "svn/WcEntry.h"
#include "svn/Revision.h"

// qt
#include <QtCore/QString>


WcModel::WcModel( ScModel* model ) : BaseModel(model), _wcId(-1)
{
}

void WcModel::setProject( const Project* prj )
{
  _prj = prj;
}

const Project* WcModel::getProject() const
{
  return _prj;
}

void WcModel::setRoot( long wcId, const sc::String& rootPath )
{
  reset();

  _wcId = wcId;

  _rootPath = rootPath;
  _currPath = rootPath;
}

void WcModel::setCurrentPath( const sc::String& currPath )
{
  reset();

  _currPath = currPath;
}

sc::String WcModel::getParentPath()
{
  if( _rootPath == _currPath )
  {
    return sc::NullString;
  }

  return sc::String( 
    QString::fromUtf8(_currPath).section( '/', 0, -2 ).utf8() );
}

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

const sc::String& WcModel::getRootUrl() const
{
  MapStatus::const_iterator it = _statussAll.find( _rootPath );
  if( it == _statussAll.end() )
  {
    return sc::NullString;
  }

  return (*it).second->getWcEntry()->getUrl();
}

const sc::String& WcModel::getCurrentPath() const
{
  return _currPath;
}

const sc::String& WcModel::getCurrentUrl() const
{
  MapStatus::const_iterator it = _statussAll.find( _currPath );
  if( it == _statussAll.end() )
  {
    return sc::NullString;
  }

  return (*it).second->getWcEntry()->getUrl();
}

long WcModel::getWcId() const
{
  return _wcId;
}

void WcModel::reset()
{
  _statussAll.clear();
  _statussSel.clear();
  _statussMod.clear();

  _version.lowest   = 0;
  _version.highest  = 0;
  _version.modified = false;
  _version.switched = false;
}

void WcModel::addStatus( const svn::WcStatuss& statussNew )
{
  _statussNew.clear();

  for( svn::WcStatuss::const_iterator it = statussNew.begin(); it != statussNew.end(); it++ )
  {
    _statussAll.insert( MapStatus::value_type((*it)->getName(),*it) );
  }

  update( _currPath, statussNew );
  updateVersion();
}

void WcModel::setSelection( const svn::WcStatuss& statussSel )
{
  _statussSel = statussSel;
}

void WcModel::add( AddParam* param, CmdResultCallback* cb )
{
  AddCmd* cmd = new AddCmd( param, cb );
  _model->runAsync(cmd);
}

void WcModel::revert( RevertParam* param, CmdResultCallback* cb )
{
  RevertCmd* cmd = new RevertCmd( param , cb );
  _model->runAsync(cmd);
}

void WcModel::commit( CommitParam* param, CmdResultCallback* cb )
{
  CommitCmd* cmd = new CommitCmd( param, cb );
  _model->runAsync(cmd);
}

void WcModel::mkdir( MkdirParam* param, CmdResultCallback* cb )
{
  MkdirCmd* cmd = new MkdirCmd( param, cb );
  _model->runAsync(cmd);
}

void WcModel::status( StatusParam* param, CmdResultCallback* cb )
{
  if( param->getRefresh() )
  {
    reset();
  }
  //StatusCmd* cmd = new StatusCmd( param, cb );
  //_model->runAsync( cmd );
  //clear();

  StatusCmd2* cmd2 = new StatusCmd2( param, this, cb );
  _model->runAsync( cmd2 );
}

void WcModel::update( UpdateParam* param, CmdResultCallback* cb )
{
  UpdateCmd* cmd = new UpdateCmd( param, cb );
  _model->runAsync( cmd );
}

void WcModel::edit( EditConflictParam* param, CmdResultCallback* cb )
{
  EditConflictCmd* cmd = new EditConflictCmd( param, cb );
  _model->runAsync( cmd );
}

void WcModel::resolved( ResolvedParam* param, CmdResultCallback* cb )
{
  ResolvedCmd* cmd = new ResolvedCmd( param, cb );
  _model->runAsync( cmd );
}

void WcModel::remove( DeleteParam* param, CmdResultCallback* cb )
{
  BaseModel::remove( param, cb );
}

void WcModel::cleanup( CleanupParam* param, CmdResultCallback* cb )
{
  CleanupCmd* cmd = new CleanupCmd( param, cb );
  _model->runAsync( cmd );
}

void WcModel::proplist( PropListParam* param, CmdResultCallback* cb, bool async )
{
  PropListCmd* cmd = new PropListCmd( param, cb );
  _model->run( cmd, async );
}

void WcModel::propset( PropSetParam* param, CmdResultCallback* cb, bool async )
{
  PropSetCmd* cmd = new PropSetCmd( param, cb );
  _model->run( cmd, async );
}

void WcModel::propget( PropGetParam* param, CmdResultCallback* cb, bool async )
{
  PropGetCmd* cmd = new PropGetCmd( param, cb );
  _model->run( cmd, async );
}

void WcModel::copy2( const sc::String& dst, CmdResultCallback* cb )
{
  svn::Paths paths;
  getSelection(paths);

  CopyParam* param = new CopyParam( paths, 
    svn::RevisionPtr(new svn::Revision(svn::Revision_Unspecified)), dst );

  copy( param, cb );
}

void WcModel::diffBaseWorking( CmdResultCallback* cb )
{
  svn::WcStatusPtr status = *(_statussSel.begin());

  DiffParam* param = new DiffParam( 
    status->getName(), svn::RevisionPtr(new svn::Revision(svn::Revision_Base)), 
    status->getName(), svn::RevisionPtr(new svn::Revision(svn::Revision_Working)),
    false, svn::RevisionPtr()/*no peg*/, 
    status->isDir() ? isCmdRecursive() : false,
    true, false, false,
    status->isDir() ? true : false );

  diff( param, cb );
}

void WcModel::remove( const svn::Paths& unversioned )
{
  // TODO move to svn::io?
  apr::Pool pool;
  svn_error_t* err;

  for( svn::Paths::const_iterator it = unversioned.begin(); it != unversioned.end(); it++ )
  {
    svn_node_kind_t kind;
    err = svn_io_check_path( *it, &kind, pool );

    if( err != SVN_NO_ERROR )
    {
      // skip
      continue;
    }

    if( kind == svn_node_file )
    {
      err = svn_io_remove_file( *it, pool );
    }
    else if( kind == svn_node_dir )
    {
      err = svn_io_remove_dir( *it, pool );
    }
  }
}

const WcModel::MapStatus& WcModel::getAllStatus() const
{
  return _statussAll;
}

const WcModel::MapStatus& WcModel::getNewStatus() const
{
  return _statussNew;
}

void WcModel::getSelection( svn::Paths& paths ) const
{
  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    paths.push_back( (*it)->getName() );
  }
}

void WcModel::getSelection( svn::WcStatuss& statuss ) const
{
  statuss = _statussSel;
}

bool WcModel::isAddable()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    allow &= (*it)->isAddable();
  }

  return allow;
}

bool WcModel::isRevertable()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    allow &= isRevertable( (*it) );
  }

  return allow;
}

bool WcModel::isRevertable( svn::WcStatusPtr status )
{
  // no lock here, this method calls itself recursivly..
  // the lock is in isRevertable().
  const svn::WcEntry* entry = status->getWcEntry();

  if( status->isRevertable() )
  {
    return true;
  }

  // non revertable file
  if( entry && ! entry->isDir() )
  {
    return false;
  }
  // non revertable dir
  else 
  {
    /* recursivly checking if there are revertable items in a folder
     * is SLOW for larger working copies. Another problem is that the
     * code below may check a deep item mutiple times if the folder
     * and its parent folders are selected. 
     *
     * So don't be perfect and simply allow revert on any folder. If
     * there is nothing to revert, revert won't do anything.
     */
    return true;

#if 0
    // check recursive if there are revertable files in the dir
    bool allow = false;

    for( svn::WcStatuss::const_iterator it = _statussAll.begin(); it != _statussAll.end(); it++ )
    {
      svn::WcStatusPtr    sta = (*it);
      const svn::WcEntry* ent = sta->getWcEntry();

      // a file is in this dir if its path/file starts with dir 
      size_t s1 = status->getName().getByteCnt();
      size_t s2 = sta   ->getName().getByteCnt();
      int equal = ::strncmp( status->getName(), sta->getName(), s1 );

      // not equal or the exact same string
      if( equal != 0 || (equal == 0 && s1 == s2) )
      {
        continue;
      }

      allow |= isRevertable(sta);
    }

    return allow;
#endif
  }
}

bool WcModel::isRemoveable()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    allow &= (*it)->isRemoveable();
  }

  return allow;
}

bool WcModel::isDiffable()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return !status->isAddable() && status->isChanged() || status->isDir();
}

bool WcModel::isCommitable()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    svn::WcStatusPtr    status = (*it);
    const svn::WcEntry* entry  = status->getWcEntry();

    if( entry && entry->isDir() )
    {
      allow &= status->isChanged() || isRevertable( status );
    }
    else
    {
      allow &= status->isChanged() && (! status->isAddable());
    }
  }

  return allow;
}

bool WcModel::isConflicted()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return status->isConflicted();
}

bool WcModel::isPropConflicted()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return status->isPropConflicted();
}

bool WcModel::isMoveable()
{
  return isVersioned();
}

bool WcModel::isVersioned()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return status->isVersioned();
}

bool WcModel::isVersionedAll()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    allow &= (*it)->isVersioned();
  }

  return allow;
}

bool WcModel::isUnversionedAll()
{
  if( _statussSel.size() == 0 )
  {
    return false;
  }

  bool allow = true;

  for( svn::WcStatuss::const_iterator it = _statussSel.begin(); it != _statussSel.end(); it++ )
  {
    allow &= !(*it)->isVersioned();
  }

  return allow;
}

bool WcModel::isVersionedDir()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return status->isVersioned() && status->isDir();
}

bool WcModel::isVersionedFile()
{
  if( _statussSel.size() != 1 )
  {
    return false;
  }

  svn::WcStatusPtr status = *(_statussSel.begin());
  return status->isVersioned() && ! status->isDir();
}

bool WcModel::getStatusAll() const
{
  return false; //_model->getOptionStatusAll();
}

bool WcModel::getStatusUpdates() const
{
  return false; //_model->getOptionStatusUpdates();
}

bool WcModel::getStatusIgnored() const
{
  return false; //_model->getOptionStatusIgnored();
}

bool WcModel::getStatusRecurse() const
{
  return false; //_model->getOptionStatusRecurse();
}

bool WcModel::isRepository() const
{
  return false;
}

bool WcModel::isWorkingCopy() const
{
  return true;
}

/**
 * \brief svnversion
 */

WcModel::Version WcModel::getVersion()
{
  return _version;
}

void WcModel::updateVersion()
{
  for( MapStatus::iterator it = _statussNew.begin(); it != _statussNew.end(); it++ )
  {
    const svn::WcStatusPtr status = (*it).second;
    const svn::WcEntry*    entry  = status->getWcEntry();

    if(
      status->getTextStatus() == svn::WcStatus_Added       ||
      status->getTextStatus() == svn::WcStatus_Unversioned
      )
    {
      continue;
    }

    _version.modified |= status->isChanged();
    _version.switched |= status->isSwitched();

    if( entry && ((entry->getRevnumber() < _version.lowest) || (_version.lowest == 0)) )
    {
      _version.lowest = entry->getRevnumber();
    }
    if( entry && (entry->getRevnumber() > _version.highest) )
    {
      _version.highest = entry->getRevnumber();
    }
  }
}

bool WcModel::isWorkingCopy( const sc::String& path )
{
  return _model->isWorkingCopy(path);
}

const WcModel::MapStatus& WcModel::getModStatus() const
{
  return _statussMod;
}

// todo move to helper class
void WcModel::update( const sc::String& root, const svn::WcStatuss& statuss )
{
  for( svn::WcStatuss::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    svn::WcStatusPtr    status = (*it);
    const svn::WcEntry* entry  = status->getWcEntry();

    _statussNew.insert( MapStatus::value_type( status->getName(), status) );

    // "." is always modified.
    if( status->getName() == root )
    {
      _statussMod.insert( MapStatus::value_type( status->getName(), status) );
      continue;
    }

    if( status->isChangedOrFlaged() || getStatusAll() )
    {
      QString inWcPath = stripPath( QString::fromUtf8(status->getName()), QString::fromUtf8(root) );

      // extract the first cnt subpath elements
      int cnt = 0;
      while( true )
      {
        QString path = inWcPath.section( '/', 0, cnt++ );

        // we have an itermediate path, and its not the same as the source
        if( path.length() != 0 && path != inWcPath )
        {
          sc::String folder = root;
          folder += "/";
          folder += sc::String(path.utf8());

          // add intermediate folder if we don't have it.
          if( _statussMod.find(folder) == _statussMod.end() )
          {
            MapStatus::iterator ita = _statussAll.find( folder );
            if( ita != _statussAll.end() )
            {
              svn::WcStatusPtr s = (*ita).second;
              _statussMod.insert( MapStatus::value_type( folder, s ) );
              _statussNew.insert( MapStatus::value_type( folder, s ) );
            }
            else
            {
              // we hit this, i don't understand what's going wrong.
              //int i = 0;
              //assert(false);
            }
          }
        }
        else
        { 
          // handle empty paths or an equal dir path
          if( entry && entry->isDir() )
          {
            _statussMod.insert( MapStatus::value_type( sc::String(path.utf8()), status ) );
          }
          break;
        }
      }
    }
  }
}

void WcModel::setWcStatus( const sc::String& path, const svn::WcStatuss& statuss )
{
  WcStatusInfo info( path, apr_time_now(), statuss );
  _cache.set(info);
}

bool WcModel::getWcStatus( const sc::String& path, svn::WcStatuss& statuss )
{
  WcStatusInfo info = _cache.get( path );
  statuss = info.getWcStatuss();
  return !info.isEmpty();
}
