/**
 * ==========================================================================
 * Copyright (c) 2003-2010  Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * This file is part of Subcommander.
 *
 * Subcommander  is free software: you  can redistribute  it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 2 of the License,  or (at your option)
 * any later version.
 *
 * Subcommander  is  distributed  in  the  hope  that  it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS  FOR A  PARTICULAR PURPOSE. See the  GNU General Public License
 * for more details.
 *
 * You should have  received a copy of the  GNU General Public License  along
 * with Subcommander. If not, see <http://www.gnu.org/licenses/>.
 * ==========================================================================
 */

// sc
#include "Project.h"

// qt
#include <QtCore/QString>

// sys
#include <cassert>


static Project::Item    NullItem;
static Project::SubItem NullSubItem;

static long getNextId()
{
  // keep out of way of existing ids
  static long id = 100000;

  return id++;
}


Project::Project( const Uuid& id, const sc::String& name )
: _id(id), _name(name), _index(0), _sortpos(0), _wcCurId(0)
{
}

Project::Project( const Project& src ) : _id(src._id)
{
  *this = src;
}

Project::~Project()
{
}

void Project::operator=( const Project& src )
{
  _id        = src._id;
  _name      = src._name;
  _index     = src._index;
  _sortpos   = src._sortpos;

  _items     = src._items;

  _wcCurId   = src._wcCurId;
}

bool Project::operator==( const Project& src ) const
{
  return
    _id           == src._id           &&
    _name         == src._name         &&
    _index        == src._index        &&
    _sortpos      == src._sortpos      &&
    _wcCurId      == src._wcCurId      &&
    _items.size() == src._items.size() &&
    std::equal( _items.begin(), _items.end(), src._items.begin() );
}

const Uuid& Project::getId() const
{
  return _id;
}

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

long Project::getIndex() const
{
  return _index;
}

const sc::String& Project::getRepositoryUrl() const
{
  return getItemSource(TrunkId);
}

const sc::String& Project::getRepositoryName() const
{
  return getItemName(TrunkId);
}

const sc::String& Project::getBranchesUrl() const
{
  return getItemSource(BranchesId);
}

const sc::String& Project::getBranchesName() const
{
  return getItemName(BranchesId);
}

const sc::String& Project::getTagsUrl() const
{
  return getItemSource(TagsId);
}

const sc::String& Project::getTagsName() const
{
  return getItemName(TagsId);
}

const sc::String& Project::getCurWorkingCopyPath() const
{
  return getItemSource(_wcCurId);
}

const sc::String& Project::getCurWorkingCopyName() const
{
  return getItemName(_wcCurId);
}

long Project::getCurWorkingCopyId() const
{
  return _wcCurId;
}

const sc::String& Project::getWorkingCopyPath( long id ) const
{
  return getItemSource(id);
}

const sc::String& Project::getWorkingCopyName( long id ) const
{
  return getItemName(id);
}

void Project::setName( const sc::String& name )
{
  _name = name;
}

void Project::setIndex( long index )
{
  _index = index;
}

void Project::setRepositoryUrl( const sc::String& url )
{
  setItemSource(TrunkId,url);
}

void Project::setRepositoryName( const sc::String& name )
{
  setItemName(TrunkId,name);
}

void Project::setBranchesUrl( const sc::String& url )
{
  setItemSource(BranchesId,url);
}

void Project::setBranchesName( const sc::String& name )
{
  setItemName(BranchesId,name);
}

void Project::setTagsUrl( const sc::String& url )
{
  setItemSource(TagsId,url);
}

void Project::setTagsName( const sc::String& name )
{
  setItemName(TagsId,name);
}

const Project::Item& Project::getTrunkItem() const
{
  return getItem(TrunkId);
}

const Project::Item& Project::getBranchesItem() const
{
  return getItem(BranchesId);
}

const Project::Item& Project::getTagsItem() const
{
  return getItem(TagsId);
}

void Project::setTrunk( const Project::Item& item )
{
  Project::Item i = item;

  i.setId(TrunkId);
  i.setType(Project::Item_Trunk);
  setItem(i);
}

void Project::setBranches( const Project::Item& item )
{
  Project::Item i = item;

  i.setId(BranchesId);
  i.setType(Project::Item_Branches);

  setItem(i);
}

void Project::setTags( const Project::Item& item )
{
  Project::Item i = item;

  i.setId(TagsId);
  i.setType(Project::Item_Tags);

  setItem(i);
}

void Project::setCurWorkingCopy( long id )
{
  _wcCurId = id;
}

long Project::getSortPos() const
{
  return _sortpos;
}

void Project::setSortPos( long pos )
{
  _sortpos = pos;
}

bool Project::isCurrentWorkingCopy( const Item& item ) const
{
  return item.getId() == _wcCurId;
}

const Project::Item& Project::createWorkingCopyItem()
{
  Item newItem( getNextId(), Item_WorkingCopy, sc::NullString, sc::NullString );

  long cntWc   = 0;
  long sortPos = UnusedId;

  for( MapIdItems::const_iterator it = _items.begin(); it != _items.end(); it++ )
  {
    if( (*it).second.getType() == Item_WorkingCopy )
    {
      cntWc++;
    }

    if( (*it).second.getSortPos() > sortPos )
    {
      sortPos = (*it).second.getSortPos();
    }
  }

  if( cntWc == 0 )
  {
    setCurWorkingCopy(newItem.getId());
  }

  newItem.setSortPos(sortPos+1);
  setItem(newItem);

  return getItem(newItem.getId());
}

const Project::Item& Project::createRepositoryItem()
{
  Item newItem( getNextId(), Item_Repository, sc::NullString, sc::NullString );

  long sortPos = UnusedId;

  for( MapIdItems::const_iterator it = _items.begin(); it != _items.end(); it++ )
  {
    if( (*it).second.getSortPos() > sortPos )
    {
      sortPos = (*it).second.getSortPos();
    }
  }

  newItem.setSortPos(sortPos+1);
  setItem(newItem);

  return getItem(newItem.getId());
}

void Project::delItem( long id )
{
  if( _wcCurId == id )
  {
    _wcCurId = UnusedId;
  }

  long sortPos = UnusedId;

  MapIdItems::iterator itDel = _items.find(id);
  if( itDel != _items.end() )
  {
    sortPos = (*itDel).second.getSortPos();
    _items.erase(itDel);
  }

  // adjust sort positions
  for( MapIdItems::iterator it = _items.begin(); it != _items.end(); it++ )
  {
    Item& item = (*it).second;

    if( item.getSortPos() > sortPos )
    {
      item.setSortPos(item.getSortPos()-1);
    }
  }

  // we deleted the current wc?
  if( _wcCurId == UnusedId )
  {
    // then choose a new one..
    for( MapIdItems::iterator it = _items.begin(); it != _items.end(); it++ )
    {
      Item& item = (*it).second;

      if( item.isWorkingCopy() )
      {
        _wcCurId = item.getId();
        break;
      }
    }
  }
}

void Project::getItems( Items& items ) const
{
  for( MapIdItems::const_iterator it = _items.begin(); it != _items.end(); it++ )
  {
    items.push_back( (*it).second );
  }
}

void Project::setItem( const Item& item )
{
  MapIdItems::iterator it = _items.find(item.getId());
  
  // new item
  if( it == _items.end() )
  {
    _items.insert( MapIdItems::value_type(item.getId(),item) );
  }
  // existing item
  else
  {
    (*it).second = item;
  }
}

const Project::Item& Project::getItem( long id ) const
{
  MapIdItems::const_iterator it = _items.find(id);

  if( it == _items.end() )
  {
    return NullItem;
  }

  return (*it).second;
}

const sc::String& Project::getItemName(long id) const
{
  MapIdItems::const_iterator it = _items.find(id);

  if( it == _items.end() )
  {
    return sc::NullString;
  }

  return (*it).second.getName();
}

const sc::String& Project::getItemSource(long id) const
{
  MapIdItems::const_iterator it = _items.find(id);

  if( it == _items.end() )
  {
    return sc::NullString;
  }

  return (*it).second.getSource();
}

void Project::setItemName( long id, const sc::String& name )
{
  MapIdItems::iterator it = _items.find(id);

  if( it == _items.end() )
  {
    return;
  }

  (*it).second.setName(name);
}

void Project::setItemSource( long id, const sc::String& source )
{
  MapIdItems::iterator it = _items.find(id);

  if( it == _items.end() )
  {
    return;
  }

  (*it).second.setSource(source);
}

void Project::preset()
{
  Item item;
  item.setName( sc::String("trunk") );
  item.setSortPos(TrunkId);
  setTrunk(item);

  item.setName( sc::String("branches") );
  item.setSortPos(BranchesId);
  setBranches(item);

  item.setName( sc::String("tags") );
  item.setSortPos(TagsId);
  setTags(item);
}

void Project::moveItem( long fromId, long toId )
{
  MapIdItems::iterator itFrom = _items.find(fromId);
  MapIdItems::iterator itTo   = _items.find(toId);

  if( itFrom == _items.end() || itTo == _items.end() )
  {
    return;
  }

  Item& itemFrom = (*itFrom).second;
  Item& itemTo   = (*itTo).second;
  long fromPos   = itemFrom.getSortPos();
  long toPos     = itemTo.getSortPos();

  bool up = itemFrom.getSortPos() > itemTo.getSortPos();

  if( up )
  { 
    for( MapIdItems::iterator it = _items.begin(); it != _items.end(); it++ )
    {
      long sortpos = (*it).second.getSortPos();

      if( sortpos >= toPos && sortpos < fromPos )
      {
        (*it).second.setSortPos( sortpos+1 );
      }
    }
    itemFrom.setSortPos(toPos);
  }
  else
  {
    for( MapIdItems::iterator it = _items.begin(); it != _items.end(); it++ )
    {
      long sortpos = (*it).second.getSortPos();

      if( sortpos > fromPos && sortpos <= toPos )
      {
        (*it).second.setSortPos( sortpos-1 );
      }
    }
    itemFrom.setSortPos(toPos);
  }
}

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

Project::Item::Item() : _id(UnusedId), _type(Item_None)
{
  init();
}

Project::Item::Item( long id, ItemType type, const sc::String& name,
  const sc::String& src )
: _id(id), _type(type), _name(name), _source(src)
{
  init();
}

Project::Item::Item( const Item& src )
{
  *this = src;
}

void Project::Item::init()
{
  _sortpos     = 0;
  _recursive   = true;
  _update      = false;
  _autoRefresh = false;
  _autoUpdate  = false;

  _rev = svn::RevisionNone;
  if( _type != Project::Item_WorkingCopy )
    _rev = svn::RevisionPtr(new svn::Revision(svn::RevHead));
}

void Project::Item::operator=( const Item& src )
{
  _id          = src._id;
  _type        = src._type;
  _name        = src._name;
  _source      = src._source;

  _sortpos     = src._sortpos;
  _recursive   = src._recursive;
  _update      = src._update;
  _autoRefresh = src._autoRefresh;
  _autoUpdate  = src._autoUpdate;
  _viewHeader  = src._viewHeader;
  _rev         = src._rev;
  _subItems    = src._subItems;
}

bool Project::Item::operator==( const Item& src ) const
{
  return 
    _id          == src._id          &&
    _type        == src._type        &&
    _name        == src._name        &&
    _source      == src._source      &&
    _sortpos     == src._sortpos     &&
    _recursive   == src._recursive   &&
    _update      == src._update      &&
    _autoRefresh == src._autoRefresh &&
    _autoUpdate  == src._autoUpdate  &&
    _viewHeader  == src._viewHeader  &&
    _rev->equals(src._rev.get()) &&
    std::equal( _subItems.begin(), _subItems.end(),
      src._subItems.begin() );
}

long Project::Item::getId() const
{
  return _id;
}

Project::ItemType Project::Item::getType() const
{
  return _type;
}

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

const sc::String& Project::Item::getSource() const
{
  return _source;
}

long Project::Item::getSortPos() const
{
  return _sortpos;
}

void Project::Item::setId( long id )
{
  _id = id;
}

void Project::Item::setType( ItemType type )
{
  _type = type;
}

void Project::Item::setName( const sc::String& name )
{
  _name = name;
}

void Project::Item::setSource( const sc::String& src )
{
  // fixme: use svn_dirent_uri.h since   1.6+
  //           svn_path.h       before  1.6

  sc::String newSrc = src;

  // make sure all wc paths use '/' and not '\' as path seperator. 

  QString path = QString::fromUtf8(newSrc);

  if (isWorkingCopy ()) {
    path = path.replace( '\\', '/' );
  } else {
    path = path.replace(":////", "://");
  }

  newSrc = sc::String(path.utf8());

  // svn delivers path without trailing "/". Make sure we also have
  // the path without "/". Otherwise a string compare on an equal path
  // (with exception of the trailing "/") will be false.

  if( newSrc.right(1) == sc::String("/") )
  {
    setSource( newSrc.left(newSrc.getCharCnt()-1) );
  }
  else
  {
    _source = newSrc;
  }
}

void Project::Item::setSortPos( long pos )
{
  _sortpos = pos;
}

bool Project::Item::isNull() const
{
  return _type == Item_None;
}

bool Project::Item::isWorkingCopy() const
{
  return _type == Item_WorkingCopy;
}

bool Project::Item::isRepository() const
{
  return _type == Item_Repository;
}

bool Project::Item::isTrunk() const
{
  return _type == Item_Trunk;
}

bool Project::Item::isBranches() const
{
  return _type == Item_Branches;
}

bool Project::Item::isTags() const
{
  return _type == Item_Tags;
}

bool Project::Item::getAutoUpdate() const
{
  return _autoUpdate;
}

void Project::Item::setAutoUpdate( bool on )
{
  _autoUpdate = on;
}

bool Project::Item::getAutoRefresh() const
{
  return _autoRefresh;
}

void Project::Item::setAutoRefresh( bool value )
{
  _autoRefresh = value;
}

bool Project::Item::getRecursive() const
{
  return _recursive;
}

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

bool Project::Item::getUpdate() const
{
  return _update;
}

void Project::Item::setUpdate( bool on )
{
  _update = on;
}

svn::RevisionPtr Project::Item::getRevision() const
{
  return _rev;
}

void Project::Item::setRevision( svn::RevisionPtr rev )
{
  _rev = rev;
}

void Project::Item::setViewHeader( const sc::String& header )
{
  _viewHeader = header;
}

const sc::String& Project::Item::getViewHeader() const
{
  return _viewHeader;
}

bool Project::Item::hasSubItems() const
{
  return ! _subItems.empty();
}

void Project::Item::getSubItems( SubItems& items ) const
{
  for( MapIdSubItems::const_iterator it = _subItems.begin();
    it != _subItems.end(); it++ )
  {
    items.push_back( (*it).second );
  }
}

void Project::Item::setSubItem( const SubItem& item )
{
  MapIdSubItems::iterator it = _subItems.find(item.getId());
  
  if( it == _subItems.end() )
    _subItems.insert( MapIdSubItems::value_type(item.getId(),item) );
  else
    (*it).second = item;
}

const Project::SubItem& Project::Item::getSubItem( long id ) const
{
  MapIdSubItems::const_iterator it = _subItems.find(id);

  if( it == _subItems.end() )
  {
    return NullSubItem;
  }

  return (*it).second;
}

const Project::SubItem& Project::Item::createSubItem()
{
  SubItem newItem( getNextId(), sc::NullString );
  
  setSubItem(newItem);
  return getSubItem(newItem.getId()); 
}

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

const long SubItemNullId = -1;

Project::SubItem::SubItem()
: _id(SubItemNullId)
{
}

Project::SubItem::SubItem( const SubItem& src )
{
  *this = src;
}

Project::SubItem::SubItem( long id, const sc::String& path )
: _id(id), _path(path), _type(SubItem_Flat)
{
}

void Project::SubItem::operator=( const SubItem& src )
{
  _id = src._id;
  _type = src._type;
  _path = src._path;
}

bool Project::SubItem::operator==( const SubItem& item ) const
{
  return _id == item._id
    && _type == item._type
    && _path == item._path;
}

long Project::SubItem::getId() const
{
  return _id;
}

const sc::String& Project::SubItem::getPath() const
{
  return _path;
}

void Project::SubItem::setPath( const sc::String& path )
{
  _path = path;
}

Project::SubItemType Project::SubItem::getType() const
{
  return _type;
}

void Project::SubItem::setType( SubItemType type )
{
  _type = type;
}

bool Project::SubItem::isNull() const
{
  return _id == SubItemNullId;
}
