/* ====================================================================
 * Copyright (c) 2006-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 "BookmarkViewModel.h"
#include "Bookmark.h"
#include "ScModel.h"
#include "Project.h"

// qt
#include <QtCore/QUrl>
#include <Qt3Support/Q3StrList>

const ID NoBookmark = 0;

///////////////////////////////////////////////////////////////////////////////
// helper

class findProjectBookmark
{
public:
  findProjectBookmark( const Project* prj ) : _prj(prj) {}

  bool operator()( Bookmarks::value_type& val )
  {
    return val.second->isProject() && val.second->getProject()->getId() == _prj->getId();
  }

private:
  const Project* _prj;
};

class findProjectChildBookmark
{
public:
  findProjectChildBookmark( ID pBmId ) : _pid(pBmId) {}

  bool operator()( Bookmarks::value_type& val )
  {
    return val.second->getParentId() == _pid;
  }

private:
  ID _pid;
};

class findProjectItemBookmark
{
public:
  findProjectItemBookmark( ID pBmId, long id ) : _pid(pBmId), _id(id) {}

  bool operator()( Bookmarks::value_type& val )
  {
    return
      val.second->getParentId()  == _pid &&
      val.second->getPrjItemId() == _id;
  }

private:
  ID   _pid;
  long _id;
};

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

BookmarkViewModel::BookmarkViewModel( ScModel* model )
: _model(model), _current(NoBookmark), _currentMenu(NoBookmark)
{
}

BookmarkViewModel::~BookmarkViewModel()
{
  for( Bookmarks::iterator it = _bookmarks.begin(); it != _bookmarks.end(); it++ )
  {
    delete (*it).second;
  }
}

Bookmark* BookmarkViewModel::createBookmarks( Project* prj )
{
  Project::Items items;
  prj->getItems(items);

  Bookmark* prjBook = new Bookmark( prj );
  _bookmarks.insert( Bookmarks::value_type(prjBook->getId(),prjBook) );
  emit bookmarkAdded(prjBook);

  for( Project::Items::iterator it = items.begin(); it != items.end(); it++ )
  {
    Project::Item& item = *it;

    Bookmark* itemBook = new Bookmark( prj, item.getId(), prjBook->getId() );
    _bookmarks.insert( Bookmarks::value_type(itemBook->getId(),itemBook) );
    emit bookmarkAdded(itemBook);
  }

  return prjBook;
}

void BookmarkViewModel::initBookmarks()
{
  const Projects& projects = _model->getProjects();

  for( Projects::const_iterator it = projects.begin(); it != projects.end(); it++ )
  {
    createBookmarks(*it);
  }
}

void BookmarkViewModel::updateBookmarks()
{
  const Projects& projects = _model->getProjects();

  for( Projects::const_iterator it = projects.begin(); it != projects.end(); it++ )
  {
    updateBookmarks(*it);
  }
}

void BookmarkViewModel::updateBookmarks( Project* prj )
{
  _model->saveProject(prj);

  Bookmarks::iterator it = std::find_if( _bookmarks.begin(), _bookmarks.end(),
    findProjectBookmark(prj) );

  if( it != _bookmarks.end() )
  {
    ID prjId = (*it).second->getId();

    Project::Items items;
    prj->getItems(items);

    // handle existing and new items
    for( Project::Items::iterator iti = items.begin(); iti != items.end(); iti++ )
    {
      Project::Item& item = *iti;

      Bookmarks::iterator itm = std::find_if( _bookmarks.begin(), _bookmarks.end(),
        findProjectItemBookmark( prjId, item.getId() ) );

      // found => existing item
      if( itm != _bookmarks.end() )
      {
        emit bookmarkModified( (*itm).second );
      }
      // not found => new item
      else
      {
        Bookmark* itemBook = new Bookmark( prj, item.getId(), prjId );
        _bookmarks.insert( Bookmarks::value_type(itemBook->getId(),itemBook) );
        emit bookmarkAdded(itemBook);
      }
    }

    // handle deleted items
    for( Bookmarks::iterator it = _bookmarks.begin(); it != _bookmarks.end(); /*nop*/)
    {
      Bookmarks::iterator itnext = _bookmarks.end();

      if( (*it).second->getParentId() == prjId )
      {
        // bookmark is part of the project?
        const Project::Item& item = prj->getItem( (*it).second->getPrjItemId() );

        // no, then remove the bookmark
        if( item.isNull() )
        {
          Bookmarks::iterator itdel = it;
          itnext = ++it;

          emit bookmarkRemoved((*itdel).second);

          delete (*itdel).second;
          _bookmarks.erase(itdel);
        }
      }

      if( itnext != _bookmarks.end() )
      {
        it = itnext;
      }
      else if( it != _bookmarks.end() )
      {
        it++;
      }
    }
  }
  else
  {
    assert(false);
  }
}

void BookmarkViewModel::removeBookmarks( Bookmark* bmPrj )
{
  // remove child bookmarks
  for( Bookmarks::iterator it = _bookmarks.begin(); it != _bookmarks.end(); /*nop*/ )
  {
    Bookmarks::iterator itthis = it;
    it++;

    if( (*itthis).second->getParentId() == bmPrj->getId() )
    {
      delete (*itthis).second;
      _bookmarks.erase(itthis);
    }
  }

  // remove parent bookmark
  for( Bookmarks::iterator it = _bookmarks.begin(); it != _bookmarks.end(); it++ )
  {
    if( (*it).second->getId() == bmPrj->getId() )
    {
      delete (*it).second;
      _bookmarks.erase(it);
      break;
    }
  }
}

Bookmark* BookmarkViewModel::createProject()
{
  Project* prj = _model->createProject();
  _model->addProject(prj);

  Bookmark* prjBook = createBookmarks(prj);
  return prjBook;
}

Bookmark* BookmarkViewModel::getBookmark()
{
  return getBookmark(_current);
}

Bookmark* BookmarkViewModel::getBookmarkMenu()
{
  return getBookmark(_currentMenu);
}

Bookmark* BookmarkViewModel::getBookmark( ID id )
{
  Bookmarks::iterator it = _bookmarks.find(id);
  if( it == _bookmarks.end() )
  {
    return NULL;
  }
  return (*it).second;
}

void BookmarkViewModel::setBookmark( Bookmark* bm )
{
  _current     = bm->getId();
  _currentMenu = bm->getId();

  emit bookmarkSelected(bm);
}

void BookmarkViewModel::setBookmarkMenu( Bookmark* bm )
{
  _currentMenu = bm->getId();
}

void BookmarkViewModel::createNewProjectSimple()
{
  Bookmark* bm = createProject();
  emit showRenameBookmark(bm);
}

void BookmarkViewModel::createNewProject()
{
  Project* prj = _model->createProject();

  bool changed = false;
  emit showNewProjectBookmark( prj, changed );

  if( ! changed )
  {
    delete prj;
    return;
  }

  _model->addProject(prj);
  _model->saveProject(prj);

  createBookmarks(prj);
}

void BookmarkViewModel::addBookmarkRepository()
{
  Bookmark* bm  = getBookmarkMenu();
  Project*  prj = bm->getProject();

  Project::Item item = prj->createRepositoryItem();

  item.setName( sc::String(_q("<new repository>")) );
  item.setSource( sc::String(_q("<repository url>")) );
  prj->setItem(item);

  updateBookmarks(prj);
  _model->saveProject(prj);
}

void BookmarkViewModel::addBookmarkWorkingCopy()
{
  Bookmark* bm  = getBookmarkMenu();
  Project*  prj = bm->getProject();

  Project::Item item = prj->createWorkingCopyItem();

  item.setName( sc::String(_q("<new working copy>")) );
  item.setSource( sc::String(_q("<working copy path>")) );
  prj->setItem(item);

  updateBookmarks(prj);
  _model->saveProject(prj);
}

void BookmarkViewModel::setCurrent()
{
  Bookmark* bm = getBookmark(_currentMenu);
  assert(bm!=NULL);

  bm->setCurrent();
  _model->saveProject(bm->getProject());

  emit bookmarkCurrent(bm);
}

void BookmarkViewModel::editBookmark( Bookmark* bm )
{
  bool changed = false;
  emit showEditBookmark( bm, changed );

  if( ! changed )
    return;

  _model->saveProject(bm->getProject());
  emit bookmarkModified(bm);
}

void BookmarkViewModel::editBookmark( EditBookmarkType type )
{
  Bookmark* bmPrj = getBookmark(_currentMenu);

  Project::Item item;
  switch(type)
  {
  case Trunk:
    item = bmPrj->getProject()->getTrunkItem();
    break;
  case Branches:
    item = bmPrj->getProject()->getBranchesItem();
    break;
  case Tags:
    item = bmPrj->getProject()->getTagsItem();
    break;
  default:
    assert(false);
  }

  Bookmarks::iterator it = std::find_if( _bookmarks.begin(), _bookmarks.end(),
    findProjectItemBookmark( bmPrj->getId(), item.getId() ) );

  if( it == _bookmarks.end() )
    assert(false);

  Bookmark* bm = (*it).second;
  editBookmark(bm); 
}

void BookmarkViewModel::editBookmark()
{
  editBookmark(getBookmark(_currentMenu));
}

void BookmarkViewModel::editBookmarkTrunk()
{
  editBookmark(Trunk);
}

void BookmarkViewModel::editBookmarkBranches()
{
  editBookmark(Branches);
}

void BookmarkViewModel::editBookmarkTags()
{
  editBookmark(Tags);
}

void BookmarkViewModel::removeBookmark()
{
  Bookmark* bm = getBookmark(_currentMenu);
  assert(bm!=NULL);

  if( bm->isProject() )
  {
    bool changed = false;
    emit showRemoveBookmark(bm,changed);

    if( !changed )
      return;

    Project* prj = bm->getProject();

    emit bookmarkRemoved(bm);
    removeBookmarks(bm);

    _model->removeProject( prj );
  }
  else
  {
    Project* prj = bm->getProject();
    prj->delItem( bm->getPrjItemId() );
    updateBookmarks(prj);
  }
}

void BookmarkViewModel::renameBookmark( Bookmark* bm, const sc::String& newName )
{
  assert(bm);

  bm->setName(newName);
  _model->saveProject(bm->getProject());
  emit bookmarkModified(bm);
}

void BookmarkViewModel::moveBookmark( Bookmark* src, Bookmark* dst )
{
  assert(src);
  assert(dst);

  // either project on project..
  if( src->isProject() && dst->isProject() )
  {
    _model->reorderProjects( src->getProject(), dst->getProject() );
  }
  // .. or item on item.
  else
  {
    src->getProject()->moveItem( src->getPrjItemId(), dst->getPrjItemId() );
  }

  emit bookmarksModified();
  _model->saveProjects();
}

void BookmarkViewModel::toggleRecursive( bool on )
{
  Bookmark* bm = getBookmark(_current);
  bm->setRecursive(on);
}

void BookmarkViewModel::toggleUpdate( bool on )
{
  Bookmark* bm = getBookmark(_current);
  bm->setUpdate(on);
}

void BookmarkViewModel::toggleAutoUpdate( bool on )
{
}

void BookmarkViewModel::toggleAutoRefresh( bool on )
{
  Bookmark* bm = getBookmark(_current);
  bm->setAutoRefresh(on);

  // emit changed???
  _model->saveProject(bm->getProject());
}

void BookmarkViewModel::running( Bookmark* bm )
{
  bm->setRunning(true);
}

void BookmarkViewModel::finished( Bookmark* bm )
{
  bm->setRunning(false);
}

void BookmarkViewModel::updateChildBookmark( Bookmark* bmPrj, long itemId )
{
  Bookmarks::iterator it = std::find_if( _bookmarks.begin(), _bookmarks.end(),
    findProjectItemBookmark( bmPrj->getId(), itemId ) );

  if( it == _bookmarks.end() )
    assert(false);

  emit bookmarkModified((*it).second);
  _model->saveProject(bmPrj->getProject());
}

void BookmarkViewModel::setTrunkUrl( Bookmark* bm, const QStringList& list )
{
  assert(bm);
  bm->getProject()->setRepositoryUrl( sc::String(((*list.begin()).utf8())) );
  updateChildBookmark( bm, bm->getProject()->getTrunkItem().getId() );
}

void BookmarkViewModel::setBranchesUrl( Bookmark* bm, const QStringList& list )
{
  assert(bm);
  bm->getProject()->setBranchesUrl( sc::String(((*list.begin()).utf8())) );
  updateChildBookmark( bm, bm->getProject()->getBranchesItem().getId() );
}

void BookmarkViewModel::setTagsUrl( Bookmark* bm, const QStringList& list )
{
  assert(bm);
  bm->getProject()->setBranchesUrl( sc::String(((*list.begin()).utf8())) );
  updateChildBookmark( bm, bm->getProject()->getTagsItem().getId() );
}

void BookmarkViewModel::addRepositories( Bookmark* bm, const QStringList& list )
{
  assert(bm);

  for( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
  {
    QString url = *it;

    Project::Item item = bm->getProject()->createRepositoryItem();
    QString name = url.section( "/", -1 );
    if( name.isEmpty() )
    {
      name = _q("new repository");
    }
    item.setName  ( sc::String(name.utf8()) );
    item.setSource( sc::String(url.utf8()) );
    bm->getProject()->setItem(item);
  }

  updateBookmarks(bm->getProject());
  _model->saveProject(bm->getProject());
}

void BookmarkViewModel::addWorkingCopies( Bookmark* bm, const QStringList& list )
{
  assert(bm);

  for( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
  {
    QUrl url(*it);
    QString path = url.toLocalFile();

    Project::Item item = bm->getProject()->createWorkingCopyItem();
    QString name = path.section( "/", -1 );
    if( name.isEmpty() )
    {
      name = _q("new wc");
    }
    item.setName  ( sc::String(name.utf8()) );
    item.setSource( sc::String(path.utf8()) );
    bm->getProject()->setItem(item);
  }

  updateBookmarks(bm->getProject());
  _model->saveProject(bm->getProject());
}
