/* ====================================================================
 * Copyright (c) 2006-2007, 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 "LogGraphDialog.h"
#include "LogGraphNode.h"
#include "LogGraphCanvasView.h"
#include "LogGraphCanvasItems.h"
#include "LogGraphViewModel.h"
#include "ScModel.h"
#include "PostCmdResult.h"
#include "Settings.h"
#include "commands/CmdProgressCallback.h"
#include "commands/CmdResultCallback.h"
#include "commands/LogGraphParam.h"
#include "commands/LogGraphCmd.h"
#include "events/ScParamEvent.h"
#include "sublib/Gui.h"
#include "sublib/TargetId.h"
#include "sublib/Utility.h"
#include "sublib/settings/LayoutSettings.h"
#include "svn/Client.h"
#include "svn/Info.h"
#include "svn/InfoBaton.h"
#include "svn/LogBaton.h"
#include "svn/LogEntry.h"
#include "svn/Revision.h"
#include "svn/Path.h"

// qt
#include <QtCore/QEvent>
#include <QtGui/QLayout>
#include <QtGui/QPushButton>
#include <QtGui/QMatrix>
#include <QtGui/QPainter>
#include <QtGui/QToolTip>
#include <QtGui/QLabel>
#include <QtGui/QAction>
#include <QtGui/QWheelEvent>
#include <Qt3Support/Q3PopupMenu>
#include <Qt3Support/Q3Canvas>

// sys
#include <set>
#include <queue>
#include "util/max.h"



class LogId
{
public:
  LogId( svn::Revnumber rev, long id ) : _rev(rev), _id(id)
  {
  }

  long id() const
  {
    return _id;
  }

  svn::Revnumber rev() const
  {
    return _rev;
  }

private:
  svn::Revnumber _rev;
  long           _id;
};


class PathRev
{
public:
  PathRev(): _rev(-1)
  {
  }

  PathRev( const sc::String& name, svn::Revnumber rev )
  : _name(name), _rev(rev)
  {
  }
  
  bool operator<( const PathRev& o ) const
  {
    if( _name == o._name )
    {
      return _rev < o._rev;
    }
    else
    {
      return _name < o._name;
    }
  }
  
  sc::String     _name;
  svn::Revnumber _rev;
};


typedef std::map<svn::Revnumber, const svn::LogEntryPtr>  Entries;
typedef std::set<sc::String>                              Paths;

typedef std::multimap< sc::String, sc::String >           Parents;
typedef std::pair< Parents::iterator, Parents::iterator > ParentsPair;

// look like we just need a long and not a LogId...
typedef std::multimap< sc::String, LogId >                Log2Ids;
typedef std::pair< Log2Ids::iterator, Log2Ids::iterator > Log2IdsPair;

typedef std::multimap< long, LogGraphNodePtr >            Logs2;
typedef std::pair< Logs2::iterator, Logs2::iterator >     Logs2Pair;

typedef std::set<PathRev>                                 PathRevs;
typedef std::multimap< PathRev, PathRev >                 Links;
typedef std::pair< Links::iterator, Links::iterator >     LinksPair;



// layout types...

typedef struct _Block {
  _Block() : _min(LONG_MAX), _max(LONG_MIN)
  {
  }

  long getSize() const
  {
    return _max - _min + 1;
  }

  long getMin() const
  {
    return _min;
  }

  long getMax() const
  {
    return _max;
  }

  long _min;
  long _max;

} Block;

class Coord
{
public:
  long _pos;
  long _depth;

  bool operator< ( const Coord& c ) const
  {
    if( _pos == c._pos )
    {
      return _depth < c._depth;
    }
    else
    {
      return _pos < c._pos;
    }
  }
};

class IdRev
{
public:
  IdRev( long id, svn::Revnumber rev )
    : _id(id), _rev(rev)
  {
  }

  bool operator<( const IdRev& idrev ) const
  {
    if( _id == idrev.id() )
    {
      return _rev < idrev.rev();
    }
    else
    {
      return _id < idrev.id();
    }
  }

  long id() const
  {
    return _id;
  }

  svn::Revnumber rev() const
  {
    return _rev;
  }

private:
  long           _id;
  svn::Revnumber _rev;
};


typedef std::map<svn::Revnumber,long>                     Pos;
typedef std::map<long,Block>                              Size2;
typedef std::set<Coord>                                   Matrix;
typedef std::map<IdRev,long>                              Pos2;

typedef std::vector<LogGraphNodePtr>                      GraphNodes;



class LogGraphData
{
public:
  LogGraphData( const sc::String& startPath, svn::Revnumber maxRev, Entries& entries )
    : _maxRev(maxRev), _entries(entries), _log2id(0)
  {
    _startPath = svn::Path::getUriDecode(startPath);
  }

  ~LogGraphData()
  {
  }

  void extractGraph()
  {
    calcRealStartPath();
    addTracking(_initialPath);
    addParents(_initialPath);
    calcGraph();

    //print();
  }

  void layoutGraph()
  {
    calcPosTime();
    calcSizeTime();
    calcPosDepth( _initialPath, _initialRev, 0, 0 );
  }

  void paintGraph( Q3CanvasView* view )
  {
    _view = view;

    paint( _initialPath, _initialRev, PathRev(sc::String(),0), 0 );

    long maxDepth = 0;
    for( Pos2::iterator it = _posDepth.begin(); it != _posDepth.end(); it++ )
    {
      (*it).second > maxDepth ? maxDepth = (*it).second : maxDepth;
    }
    maxDepth++;

    _view->canvas()->resize(IndexWidth*(long)_posTime.size(),IndexHeight*maxDepth);
    _view->repaintContents();
  }

  /**
   * check if the given path is a move.
   */
  bool checkMove( const svn::LogEntry::Path& srcPath, const svn::LogEntryPtr log )
  {
    // no add, then it is not a move.
    if( ! srcPath.isAdded() )
    {
      return false;
    }

    // an add, then check if copyfrom is deleted.
    const svn::LogEntry::ChangedPaths& changed = log->getPaths();

    for( svn::LogEntry::ChangedPaths::const_iterator cit = changed.begin(); cit != changed.end(); cit++ )
    {
      const svn::LogEntry::Path& cpath = *cit;

      if( cpath.isDeleted() && cpath.getPath() == srcPath.getCopyFromPath() )
      {
        // yet it was deleted, then we have a move!
        return true;
      }
    }
    return false;
  }

  /**
   * check if the given srcPath is the delete of a move.
   */
  bool checkMoveDelete( const svn::LogEntry::Path& srcPath, const svn::LogEntryPtr log )
  {
    if( ! srcPath.isDeleted() )
    {
      return false;
    }

    // a delete, check if it is a copyfrom too.
    const svn::LogEntry::ChangedPaths& changed = log->getPaths();

    for( svn::LogEntry::ChangedPaths::const_iterator cit = changed.begin(); cit != changed.end(); cit++ )
    {
      const svn::LogEntry::Path& cpath = *cit;

      if( cpath.isAdded() && cpath.getCopyFromPath() == srcPath.getPath() )
      {
        // yes it is a copyfrom of an add, so we have a move!
        return true;
      }
    }
    return false;
  }

  /**
   * add the given id for the name/rev pair.
   */
  void addLogId( const sc::String& name, svn::Revnumber rev, long id )
  {
    LogId nid( rev, id );

    _log2ids.insert( Log2Ids::value_type(name,nid) );
  }

  /**
   * get a new id for the given name/rev pair.
   */
  long newLogId( const sc::String& name, svn::Revnumber rev )
  {
    addLogId( name, rev, ++_log2id );
    return _log2id;
  }

  /**
   * get the LogId for the given name/rev pair.
   */
  long getLogId( const sc::String& name, svn::Revnumber rev )
  {
    // \todo the rev stuff is unecessary???
    Log2IdsPair pair = _log2ids.equal_range(name);

    Log2Ids::reverse_iterator b(pair.second);
    Log2Ids::reverse_iterator e(pair.first);

    for( Log2Ids::reverse_iterator it = b; it != e; it++ )
    {
      const LogId& id = (*it).second;

      if( rev >= id.rev() )
      {
        return id.id();
      }
    }

    return 0;
  }

  /**
   * add path to the paths we "track".
   */
  void addTracking( const sc::String& path )
  {
    sc::String init = path;
    while( !init.isEmpty() )
    {
      _tracking.insert(init);
      init = parent(init);
    }
  }

  /**
   * remove the given path from the path we track.
   */
  void delTracking( const sc::String& path )
  {
    Paths::iterator start = _tracking.end();
    Paths::iterator stop  = _tracking.end();

    for( Paths::iterator tit = _tracking.begin(); tit != _tracking.end(); tit++ )
    {
      if( isParentOrSelf(path,*tit) )
      {
        if( start == _tracking.end() )
        {
          start = tit;
          stop  = tit;
        }
        else
        {
          stop = tit;
        }
      }
    }
    
    if( stop != _tracking.end() )
    {
      stop++;
    }

    _tracking.erase( start, stop );
  }

  /**
   * extract the parent of the given source.
   */
  sc::String parent( const sc::String& src )
  {
    const char* c = src;
    c += src.getByteCnt()-1;
    while( *c != '/' )
      c--;
    long cnt = c - (const char*)src;
    return sc::String(src,cnt);
  }

  /**
   * add each possible parent/child combination of path to the
   * "parent" paths.
   */
  void addParents( const sc::String& path )
  {
    sc::String init = path;
    sc::String child;
    while( !init.isEmpty() )
    {
      _parents.insert( Parents::value_type(init,child) );

      sc::String newinit = parent(init);
      child = path.right( path.getCharCnt()-newinit.getCharCnt() );
      init  = newinit;
    }
  }

  /**
   * remove each possible parent/child combination of path from the
   * "parent" paths.
   */
  void delParents( const sc::String& path )
  {
    sc::String init = path;
    sc::String child;
    while( !init.isEmpty() )
    {
      ParentsPair pair = _parents.equal_range(init);
      for( Parents::iterator it = pair.first; it != pair.second; it++ )
      {
        if( (*it).second == child )
        {
          _parents.erase(it);
          break;
        }
      }

      sc::String newinit = parent(init);
      child = path.right( path.getCharCnt()-newinit.getCharCnt() );
      init  = newinit;
    }
  }


  /**
   * test if @a pos is @a self or a parent of @a self.
   */
  bool isParentOrSelf( const sc::String& pos, const sc::String& self )
  {
    //return pos == self.left(pos.getCharCnt());

    // beginning of self matches pos?
    bool equals = (pos == self.left(pos.getCharCnt()));

    // yes..
    if( equals )
    {
      // than it is self..
      if( self.getCharCnt() == pos.getCharCnt() )
      {
        return true;
      }
      // or a parent if the next char of self is a path seperator.
      // "foobar" is not self or parent of "foobar2"!
      else // self.getCharCnt() > pos.getCharCnt()
      {
        // beware of multibyte characters!
        return self.mid(pos.getCharCnt(),1) == "/";
      }
    }
    else
    {
      return false;
    }
  }

  /**
   * find out what the original name of _startPath was. It may have been
   * moved, copied or renamed.
   */
  void calcRealStartPath()
  {
    // extract the inital name and revision. Honor parent copies and moves.

    sc::String path = _startPath;

    for( svn::Revnumber rev = _maxRev; rev > 0; rev-- )
    {
      const svn::LogEntryPtr             log     = _entries[rev];
      const svn::LogEntry::ChangedPaths& changed = log->getPaths();

      for( svn::LogEntry::ChangedPaths::const_iterator cit = changed.begin(); cit != changed.end(); cit++ )
      {
        const svn::LogEntry::Path& cpath = *cit;

        if( cpath.isAdded() && isParentOrSelf(cpath.getPath(),path) )
        {
          if( cpath.isCopied() )
          {
            // extract previous name and check this rev again, the copyfrom path
            // may have been added in this rev too. 
            sc::String child = path.right(path.getCharCnt()-cpath.getPath().getCharCnt());
            path = cpath.getCopyFromPath();
            path += child;
            rev++;
          }
          else if( cpath.getPath() == path )
          {
            // found!
            _initialPath = path;
            _initialRev  = rev;
            return;
          }
        }
      }
    }

    // should never be reached, if the path is in the repository we always
    // finish at the return above. Unless it si run on the repository root
    // itself.
    // set sensible default values so we do not crash and simply display
    // nothing.
    _initialPath = _startPath;
    _initialRev  = 1;
  }

  void calcGraph()
  {
    for( svn::Revnumber rev = _initialRev; rev <= _maxRev; rev++ )
    {
      Paths _delTracking;

      const svn::LogEntryPtr             log     = _entries[rev];
      const svn::LogEntry::ChangedPaths& changed = log->getPaths();

      // check each changed path/coypfrom if we track it.
      for( svn::LogEntry::ChangedPaths::const_iterator cit = changed.begin(); cit != changed.end(); cit++ )
      {
        const svn::LogEntry::Path& cpath = *cit;

        // path handling:

        // we track this one?
        Paths::iterator tit = _tracking.find(cpath.getPath());
        if( tit != _tracking.end() )
        {
          // ..then add a Node to its line if it is an exact match, ie. no parent.
          ParentsPair pair = _parents.equal_range(cpath.getPath());
          for( Parents::iterator pit = pair.first; pit != pair.second; pit++ )
          {
            const sc::String& child = (*pit).second;
            sc::String name(cpath.getPath());
            name += child; 

            // the path itself is the changed path
            if( child.isEmpty() )
            {
              long id = getLogId(name,log->getRevnumber());
              if( id == 0 )
              {
                id = newLogId(name,log->getRevnumber());
              }

              // if this item was deleted by a move (ie. we also have an
              // add with this item as copfrom in the current rev) do not
              // add a new log, it gets added by the copy handling below.
              if( !checkMoveDelete(cpath,log) )
              {
                //todo add correct node type: added, modified, deleted.
                //  /** 'A'dd, 'D'elete, 'R'eplace, 'M'odify */
                LogGraphNodePtr node(new LogGraphNode( LogGraphNode::Modified, name, log ));
                _logs2.insert( Logs2::value_type(id,node) );
                _logs2in.insert( PathRev(name,log->getRevnumber()) );
              }
            }
          }

          // if it was deleted in this revision, remember it. We will
          // remove it from tracking & parents below.
          if( cpath.isDeleted() )
          {
            _delTracking.insert(*tit);
          }
        }


        // copyfrom handling:

        // if it was copied...
        if( cpath.isCopied() )
        {
          // ..check if we track its copyfrom.
          Paths::iterator tit = _tracking.find(cpath.getCopyFromPath());
          if( tit != _tracking.end() )
          {
            // we track it..

            // add each child of cpath to the paths we track with its new name.
            // if cpath is /trunk and it is moved or copied to /new and we are
            // tracking multiple childs of /trunk, e.g. /trunk/foo and /trunk/bar
            // we now have to track their new paths /new/foo and /new/bar too.

            // get all childs
            Paths childs;
            ParentsPair pair = _parents.equal_range(cpath.getCopyFromPath());
            for( Parents::iterator pit = pair.first; pit != pair.second; pit++ )
            {
              childs.insert((*pit).second);
            }

            // ..and it was moved.
            if( checkMove(cpath,log) )
            {
              for( Paths::iterator pit = childs.begin(); pit != childs.end(); pit++ )
              {
                const sc::String& child = (*pit);
                sc::String src(cpath.getCopyFromPath());
                src += child; 
                sc::String name(cpath.getPath());
                name += child;

                addTracking(name);
                addParents(name);

                long id = getLogId(src,cpath.getCopyFromRev());
                addLogId(name,log->getRevnumber(),id);

                LogGraphNodePtr node( new LogGraphNode( LogGraphNode::Moved, name, log ) );
                _logs2.insert( Logs2::value_type(id,node) );
                _logs2in.insert( PathRev(name,log->getRevnumber()) );
              }
            }
            // and it was NOT moved.
            else 
            {
              for( Paths::iterator pit = childs.begin(); pit != childs.end(); pit++ )
              {
                sc::String child = (*pit);

                sc::String src(cpath.getCopyFromPath());
                src += child;
                sc::String name(cpath.getPath());
                name += child;

                addTracking(name);
                addParents(name);

                // always new???
                long id;// = getLogId(name,log->getRevnumber());
                //if( id == 0 )
                {
                  id = newLogId(name,log->getRevnumber());
                }

                LogGraphNodePtr node( new LogGraphNode( LogGraphNode::ParentCopied, name, log ) );
                _logs2.insert( Logs2::value_type(id,node) );
                _logs2in.insert( PathRev(name,log->getRevnumber()) );

                PathRev pr1( src,  cpath.getCopyFromRev() );
                PathRev pr2( name, log->getRevnumber() );
                _copy2.insert( Links::value_type(pr1,pr2) );

                // source missing?
                if( _logs2in.find(pr1) == _logs2in.end() )
                {
                  long sid = getLogId(src,cpath.getCopyFromRev());
                  if( sid == 0 )
                  {
                    sid = newLogId(src,cpath.getCopyFromRev());
                    //assert(false);
                  }

                  LogGraphNodePtr node(
                    new LogGraphNode( LogGraphNode::CopyFrom, src, _entries[cpath.getCopyFromRev()] ) );
                  _logs2.insert( Logs2::value_type(sid,node) );
                  _logs2in.insert(pr1);
                }
              }
            }
          }
        }
      }

      // remove the paths we no longer track.
      for( Paths::iterator it = _delTracking.begin(); it != _delTracking.end(); it++ )
      {
        delTracking(*it);

        Paths paths;
        ParentsPair pair = _parents.equal_range(*it);
        for( Parents::iterator pit = pair.first; pit != pair.second; pit++ )
        {
          sc::String del(*it);
          del += (*pit).second;

          paths.insert(del);
        }
        for( Paths::iterator vit = paths.begin(); vit != paths.end(); vit++ )
        {
          delParents(*vit);
        }
      }
    }
  }

  void print()
  {
    for( Paths::iterator it = _tracking.begin(); it != _tracking.end(); it++ )
    {
      printf( "track: %s\n", (const char*)(*it) );
    }

    for( Parents::iterator it = _parents.begin(); it != _parents.end(); it++ )
    {
      printf( "parent: %s -> %s\n", (const char*)(*it).first, (const char*)(*it).second );
    }

    for( Log2Ids::iterator it = _log2ids.begin(); it != _log2ids.end(); it++ )
    {
      printf( "logids: %s -> %ld\n", (const char*)(*it).first, (*it).second.id() );
    }

    for( Logs2::iterator it = _logs2.begin(); it != _logs2.end(); it++ )
    {
      sc::String             src = (*it).second->getName();
      const svn::LogEntryPtr log = (*it).second->getLog();

      printf( "log:%ld %s:%ld\n", (*it).first, (const char*)src, log->getRevnumber() );
    }

    for( Links::iterator it = _copy2.begin(); it != _copy2.end(); it++ )
    {
      const PathRev& src = (*it).first;
      const PathRev& dst = (*it).second;

      printf( "copy:  %s:%ld  to  %s:%ld\n", 
        (const char*)src._name, src._rev,
        (const char*)dst._name, dst._rev );
    }
  }

private:
  // in
  sc::String       _startPath;   ///< the path we build the tree for

  svn::Revnumber   _maxRev;      ///< the highest rev in _entries
  Entries          _entries;     ///< all log messages

  // calc
  sc::String       _initialPath; ///< the earliest name of _startPath
  svn::Revnumber   _initialRev;  ///< the rev _initialPath was added

  Paths            _tracking;    ///< the paths we track
  Parents          _parents;     ///< the parents of the paths we track

  long             _log2id;      ///< id factory for _log2ids
  PathRevs         _logs2in;     ///< what's already added to _logs2

  // out
  Log2Ids          _log2ids;     ///< the line id for a path, if path was
                                 ///< renamed its old and new name are
                                 ///< mapped to the same id.

  Logs2            _logs2;       ///< maps the line ids to the Nodes on 
                                 ///< the line

  Links            _copy2;       ///< copied path/rev to path/rev


// layout code follows.....
// \todo move to own class..??
public:

  /**
   * calc the position of each revision we want to draw. For each rev
   * we increase the position by one.
   *
   * \todo rename to calcRevPos...
   */
  void calcPosTime()
  {
    // extract all revision for time index positioning
    for( Logs2::iterator it = _logs2.begin(); it != _logs2.end(); it++ )
    {
      const svn::LogEntryPtr log = (*it).second->getLog();
      _posTime[log->getRevnumber()] = 0;
    }

    // set time position index
    long cnt = 0;
    for( Pos::iterator it = _posTime.begin(); it != _posTime.end(); it++ )
    {
      (*it).second = cnt;
      cnt++;
    }
  }

  /**
   * calculate the min and max pos for each log "line".
   *
   * \todo rename to better name...
   */
  void calcSizeTime()
  {
    // extract min/max time index position for each path
    for( Logs2::iterator it = _logs2.begin(); it != _logs2.end(); it++ )
    {
      long                   src = (*it).first;
      const svn::LogEntryPtr log = (*it).second->getLog();

      long index  = _posTime[log->getRevnumber()];
      Block& size = _size2Time[src];

      if( index < size._min )
        size._min = index;
      if( index > size._max )
        size._max = index;
    }
  }

  /**
   * check if there is space for the Block at depth.
   */
  bool checkDepth( const Block& size, long startIdx, long depth )
  {
    for( long idx = /*size._min*/startIdx; idx <= size._max; idx++ )
    {
      Coord c;
      c._depth = depth;
      c._pos   = idx;

      Matrix::iterator it = _matrix.find(c);

      // already in use?
      if( it != _matrix.end() )
      {
        return false;
      }
    }
    return true;
  }

  /**
   * claculate the depth for the given Block.
   */
  long calcDepth( const Block& size, long startIdx, long minDepth )
  {
    long depth = minDepth;

    while( true )
    {
      bool free = checkDepth( size, startIdx, depth );

      if( free )
      {
        break;
      }

      depth++;
    }

    return depth;
  }


  /**
   * mark used positions in the layout grid.
   */
  void setMatrix( const Block& size, long depth, long startIdx, long startDepth )
  {
    // mark connector line
    for( long idx = startDepth; idx < depth; idx++ )
    {
      Coord c;
      c._depth = idx;
      c._pos   = startIdx;

      _matrix.insert(c);
    }

    // mark revision line
    for( long idx = startIdx/*size._min*/; idx <= size._max; idx++ )
    {
      Coord c;
      c._depth = depth;
      c._pos   = idx;

      _matrix.insert(c);
    }
  }

  /**
   * set the depth for the given line id.
   */
  void setPosDepth( long id, long depth )
  {
    Logs2Pair lPair = _logs2.equal_range( id );

    for( Logs2::iterator itLog = lPair.first; itLog != lPair.second; itLog++ )
    {
      IdRev idrev( id, (*itLog).second->getLog()->getRevnumber() );
      _posDepth[idrev] = depth;
    }
  }


  // predicate for sortNodes
  class BackSorter : public std::binary_function<LogGraphNodePtr,LogGraphNodePtr, bool>
  {
  public:
    bool operator() (LogGraphNodePtr lh, LogGraphNodePtr rh)
    {
      return lh->getLog()->getRevnumber() > rh->getLog()->getRevnumber();
    }
  };

  void sortNodes( GraphNodes& nodes, Logs2Pair pair )
  {
    for( Logs2::iterator itLog = pair.first; itLog != pair.second; itLog++ )
    {
      nodes.push_back((*itLog).second);
    }

    std::sort( nodes.begin(), nodes.end(), BackSorter() );
  }

  void calcPosDepth( const sc::String& url, svn::Revnumber rev, long startIdx, long startDepth )
  {
    long id = getLogId(url,rev);

    const Block& size = _size2Time[id];
    long  depth       = calcDepth( size, startIdx, startDepth );

    setMatrix( size, depth, startIdx, startDepth );
    setPosDepth( id, depth );

    Logs2Pair lPair = _logs2.equal_range( id );

    // sort nodes by reversed revisions, since they were not strictly added in
    // increasing order to _logs2.
    GraphNodes nodes;
    sortNodes(nodes,lPair);

    for( GraphNodes::iterator itLog = nodes.begin(); itLog != nodes.end(); itLog++ )
    {
      PathRev rev( (*itLog)->getName(), (*itLog)->getLog()->getRevnumber() );
      LinksPair lkPair = _copy2.equal_range(rev);

      Links::reverse_iterator rbl(lkPair.second);
      Links::reverse_iterator rbe(lkPair.first);

      for( Links::reverse_iterator itLink = rbl; itLink != rbe; itLink++ )
      {
        calcPosDepth( (*itLink).second._name, (*itLink).second._rev, _posTime[rev._rev], depth );
      }
    }
  }

private:
  Pos        _posTime;    ///< revision number to index
  Size2      _size2Time;  ///< line id to time size
  Pos2       _posDepth;   ///< url / revision number to depth index

  Matrix     _matrix;




public:
  static const long IndexWidth   = 120;  // default 120, min 70
  static const long IndexHeight  = 65;   // default 80,  min 60
  static const long IndexMargin  = 5;

  static const long PathBoxHeight = IndexHeight - 2*IndexMargin;

  static const long PathBoxMargin     = 5;
  static const long PathBoxTextHeight = 20;

  static const long RevBoxHeight = IndexHeight - 2*IndexMargin - 2*PathBoxMargin - PathBoxTextHeight;
  static const long RevBoxWidth  = IndexWidth  - 2*IndexMargin - 2*PathBoxMargin;

  void paint( const sc::String& url, long rev, const PathRev& src, long pathDepth )
  {
    static QColor colors[] = {
      QColor(230,230,230),
      QColor(200,200,230),
      QColor(230,200,200),
      QColor(200,200,200),
      QColor(180,180,180),
      QColor(160,160,160),
      QColor(140,140,140),
      QColor(120,120,120),
      QColor(100,100,100)
    };

    long id            = getLogId(url,rev);
    const Block& size  = _size2Time[id];
    long         depth = _posDepth[IdRev(id,rev)];
    long         cidx  = pathDepth % 8;

    LogGraphCanvasPath* box = new LogGraphCanvasPath( _view->canvas(), QString::fromUtf8(url) );
    box->setX( IndexWidth*size.getMin() + IndexMargin );
    box->setY( IndexHeight*depth        + IndexMargin );
    box->setSize( IndexWidth*size.getSize()-2*IndexMargin, PathBoxHeight );
    box->setBrush(colors[cidx]);
    box->setPen(colors[cidx].dark(120));
    box->show();

    Logs2Pair lPair = _logs2.equal_range( id );

    bool drawn = false;
    for( Logs2::iterator itLog = lPair.first; itLog != lPair.second; itLog++ )
    {
      svn::Revnumber num = (*itLog).second->getLog()->getRevnumber();
      long           pos = _posTime[num];

      box->addNewName( QString::fromUtf8((*itLog).second->getName()) );

      Q3CanvasRectangle* rev = new LogGraphCanvasRev(_view->canvas(),(*itLog).second);
      rev->setX( IndexWidth*pos    + IndexMargin + PathBoxMargin );
      rev->setY( IndexHeight*depth + IndexMargin + PathBoxMargin + PathBoxTextHeight );
      rev->setSize( RevBoxWidth, RevBoxHeight );
      rev->setBrush(colors[cidx].dark(105));
      rev->setPen(colors[cidx].dark(115));
      rev->show();

      // draw connection
      if( !src._name.isEmpty() && !drawn )
      {
        drawn = true;
        QColor lineColor(150,150,150);

        long id = getLogId(src._name, src._rev);
        long prevDepth = _posDepth[IdRev(id,src._rev)];
        long prevPos   = _posTime[src._rev];

        if( prevDepth == depth )
        {
          Q3CanvasLine* l1 = new Q3CanvasLine(_view->canvas());
          int x1 = IndexWidth*prevPos    + IndexMargin + PathBoxMargin + RevBoxWidth;
          int y1 = IndexHeight*prevDepth + IndexMargin + PathBoxMargin + PathBoxTextHeight + RevBoxHeight/2;
          l1->setPoints( x1, y1, (int)rev->x(), y1 );
          l1->setPen(lineColor);
          l1->show();
        }
        else if( prevDepth < depth )
        {
          Q3CanvasLine* l1 = new Q3CanvasLine(_view->canvas());
          int x1 = IndexWidth*prevPos    + IndexMargin + PathBoxMargin + RevBoxWidth/2;
          int y1 = IndexHeight*prevDepth + IndexMargin + PathBoxMargin + PathBoxTextHeight + RevBoxHeight;
          int y2 = (int)rev->y()+ RevBoxHeight/2;
          l1->setPoints( x1, y1, x1, y2 );
          l1->setPen(lineColor);
          l1->show();
          Q3CanvasLine* l2 = new Q3CanvasLine(_view->canvas());
          l2->setPoints( x1, y2, (int)rev->x(), y2 );
          l2->setPen(lineColor);
          l2->show();
        }
        else
        {
          Q3CanvasLine* l1 = new Q3CanvasLine(_view->canvas());
          int x1 = IndexWidth*prevPos    + IndexMargin + PathBoxMargin + RevBoxWidth;
          int y1 = IndexHeight*prevDepth + IndexMargin + PathBoxMargin + PathBoxTextHeight + RevBoxHeight/2;
          int x2 = (int)rev->x() + RevBoxWidth/2;
          int y2 = (int)rev->y() + RevBoxHeight;
          l1->setPoints( x1, y1, x2, y1 );
          l1->setPen(lineColor);
          l1->show();
          Q3CanvasLine* l2 = new Q3CanvasLine(_view->canvas());
          l2->setPoints( x2, y1, x2, y2 );
          l2->setPen(lineColor);
          l2->show();
        }
      }
      //

      PathRev prev( (*itLog).second->getName(), (*itLog).second->getLog()->getRevnumber() );
      LinksPair lkPair = _copy2.equal_range(prev);

      for( Links::iterator itLink = lkPair.first; itLink != lkPair.second; itLink++ )
      {
        paint( (*itLink).second._name, (*itLink).second._rev, prev, pathDepth+1 );
      }
    }
  }

private:
  Q3CanvasView* _view;
};


class LogGraphLayout
{
public:
  LogGraphLayout()
  {
  }
};


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

Qt::WFlags LogGraphDialogFlags = Qt::WStyle_Customize | Qt::WType_TopLevel
  | Qt::WStyle_MinMax  | Qt::WStyle_NormalBorder | Qt::WStyle_Title
  | Qt::WStyle_SysMenu | Qt::WDestructiveClose;

LogGraphDialog::LogGraphDialog( LogGraphViewModel* model, QWidget *parent )
: super( parent, 0, LogGraphDialogFlags ), _model(model)
{
  setName( "LogGraphDialog" );
  setCaption( QString(_q("subcommander:log graph (%1)")).arg(
    QString::fromUtf8(_model->getName()) ) );

  QVBoxLayout *vbl = new QVBoxLayout(this,5,8);
  vbl->setSpacing(10);
  {
    _view = new LogGraphCanvasView(this);
    _view->setCanvas( new Q3Canvas(100,100) );
    vbl->addWidget(_view);
    connect( _view, SIGNAL(diffRequested()), SLOT(diff()) );
    connect( _view, SIGNAL(selectionChanged(bool)), SLOT(diffable(bool)) );

    QHBoxLayout* hu = new QHBoxLayout;
    vbl->addLayout(hu);
    {
      _zoom = new QLabel(this);
      _zoom->setText("1.00");
      hu->addWidget(_zoom);

      _diff = new QPushButton(this);
      _diff->setText("d&iff");
      _diff->setEnabled(false);
      hu->addWidget(_diff);

      // eats extra space, so the buttons keep their size
      hu->addStretch(1); 

      _done = new QPushButton(this);
      _done->setText( _q("&Done") );
      hu->addWidget(_done);

      hu->addSpacing(getSizeGripSpacing());
      
      connect( _diff, SIGNAL(clicked()), SLOT(diff()) );
      connect( _done, SIGNAL(clicked()), SLOT(close()) );
    }
  }

  connect( 
    _model, SIGNAL(graph()),
    this, SLOT(graph()) );

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

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

  delete _view->canvas();
  delete _model;
}


void LogGraphDialog::wheelEvent( QWheelEvent* e )
{
  static double scale = 1.0;
  static double factor = 0.05;

  int delta = e->delta();
  if( delta > 0 )
  {
    // up
    if( scale + factor <= 3.0 )
    {
      scale += factor;
      QWMatrix m = _view->worldMatrix();
      m.reset();
      m.scale( scale, scale );
      _view->setWorldMatrix( m );

      _zoom->setText( QString("%1").arg(scale,4,'f',2) );
    }
  }
  else
  {
    // down
    if( scale - factor > 0 )
    {
      scale -= factor;
      QWMatrix m = _view->worldMatrix();
      m.reset();
      m.scale( scale, scale );
      _view->setWorldMatrix( m );

      _zoom->setText( QString("%1").arg(scale,4,'f',2) );
    }
  }
  e->accept();
}

void LogGraphDialog::run()
{
  _model->log();
}

void LogGraphDialog::diff()
{
  emit showDiff( _model->createDiffViewModel() );
}

void LogGraphDialog::diffable( bool b )
{
  const LogGraphCanvasItem* item1 = _view->getSelection1();
  const LogGraphCanvasItem* item2 = _view->getSelection2();

  LogGraphSelection selection;

  if( item1 )
  {
    LogGraphSelectionItem item;

    QString p = _view->getRoot() + item1->getPath();
    item._path = sc::String(p.utf8());
    item._rev  = item1->getRev();
    selection.push_back( item );
  }

  if( item2 )
  {
    LogGraphSelectionItem item;

    QString p = _view->getRoot() + item2->getPath();
    item._path = sc::String(p.utf8());
    item._rev  = item2->getRev();
    selection.push_back( item );
  }

  _model->setSelection( selection );
  _diff->setEnabled(b);
}

void LogGraphDialog::graph()
{
  QString root = QString::fromUtf8(_model->getRootPath());
  _view->setRoot(root);

  Entries entries;

  const svn::LogEntries& logs = _model->getLogEntries();
  for( svn::LogEntries::const_iterator it = logs.begin(); it != logs.end(); it++ )
  {
    svn::LogEntryPtr log = *it;
    entries.insert( Entries::value_type(log->getRevnumber(),log) );  
  }

  LogGraphData logGraphData( _model->getStartPath(), _model->getMaxRev(), entries );
  logGraphData.extractGraph();
  logGraphData.layoutGraph();
  logGraphData.paintGraph(_view);
}
