/* ====================================================================
 * Copyright (c) 2003-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 "SplitLayout.h"

// qt
#include <QtGui/QCursor>
#include <QtGui/QFrame>
#include <QtCore/QEvent>
#include <QtGui/QMouseEvent>

// sys
#include <assert.h>

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

class SplitHandleStrategy
{
public:
  virtual ~SplitHandleStrategy(){}

  virtual void setCursor() = 0;
  virtual void setSizePolicy() = 0;

  virtual int getPos() = 0;
  virtual int getMousePos( QMouseEvent* ) = 0;

  virtual void setSize() = 0;
  virtual void setGeometry( int ) = 0;
};

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

class SplitHandle : public QFrame 
{
  typedef QFrame super;

public:
  enum Orientation
  {
    oHorizontal,
    oVertical
  };

  SplitHandle( QWidget *parent, SplitLayoutHandle* layout, Orientation o );
  ~SplitHandle();

  void reposition( int newPos );
  int getPos() const;

protected:
  void mouseMoveEvent( QMouseEvent* e );
  void mousePressEvent( QMouseEvent* e );
  void mouseReleaseEvent( QMouseEvent* e );
  //void mouseDoubleClickEvent( QMouseEvent* e );

private:
  SplitHandleStrategy* _strategy;
  SplitLayoutHandle*   _layout;
  int                  _lastPos;
  bool                 _move;
};


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

class HSplitHandleStrategy : public SplitHandleStrategy
{
public:
  HSplitHandleStrategy( SplitHandle* sh ) : _handle(sh)
  {
  }

  void setCursor()
  {
    _handle->setCursor( QCursor(Qt::SplitHCursor) );
  }

  void setSizePolicy()
  {
    _handle->setSizePolicy(
      QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Expanding) );
  }

  int getMousePos( QMouseEvent* e )
  {
    return e->globalX();
  }

  int getPos()
  {
    return _handle->x();
  }

  void setSize()
  {
    _handle->setFixedWidth( 5 );
  }

  void setGeometry( int newPos )
  {
    _handle->setGeometry( newPos, _handle->y(), _handle->width(),
      _handle->height() );
  }

private:
  SplitHandle* _handle;
};



class VSplitHandleStrategy : public SplitHandleStrategy
{
public:
  VSplitHandleStrategy( SplitHandle* sh ) : _handle(sh)
  {
  }

  void setCursor()
  {
    _handle->setCursor( QCursor(Qt::SplitVCursor) );
  }

  void setSizePolicy()
  {
    _handle->setSizePolicy(
      QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed) );
  }

  int getMousePos( QMouseEvent* e )
  {
    return e->globalY();
  }

  int getPos()
  {
    return _handle->y();
  }

  void setSize()
  {
    _handle->setFixedHeight( 5 );
  }

  void setGeometry( int newPos )
  {
    _handle->setGeometry( _handle->x(), newPos, _handle->width(),
      _handle->height() );
  }

private:
  SplitHandle* _handle;
};

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

SplitHandle::SplitHandle( QWidget *parent, SplitLayoutHandle* layout,
  Orientation o ) : QFrame(parent), _layout(layout), _move(false)
{
  if( o == oHorizontal )
  {
    _strategy = new HSplitHandleStrategy(this);
  }
  else
  {
    _strategy = new VSplitHandleStrategy(this);
  }

  _strategy->setCursor();
  _strategy->setSize();
  _strategy->setSizePolicy();
}

SplitHandle::~SplitHandle()
{
  delete _strategy;
}

void SplitHandle::mouseMoveEvent( QMouseEvent* e )
{
  int myPos    = _strategy->getPos();
  int mousePos = _strategy->getMousePos(e);
  int newPos   = _layout->calcMovePos( myPos, mousePos - _lastPos );

  _lastPos = mousePos; 
  _move    = true;

  reposition(newPos);
}

void SplitHandle::mousePressEvent( QMouseEvent* e )
{
  _lastPos = _strategy->getMousePos(e);
}

void SplitHandle::mouseReleaseEvent( QMouseEvent* e )
{
  if( _move )
  {
    _move = ! _move;
    return;
  }

  int newPos = _layout->calcJumpPos( _strategy->getPos() );
  reposition(newPos);
}

//void SplitHandle::mouseDoubleClickEvent( QMouseEvent* e )
//{
//  int newPos = _layout->calcJumpPos( _strategy->getPos() );
//  reposition(newPos);
//}

void SplitHandle::reposition( int newPos )
{
  if( newPos == _strategy->getPos() )
  {
    return;
  }

  _strategy->setGeometry( newPos );
  _layout->recalculate();
}

int SplitHandle::getPos() const
{
  return _strategy->getPos();
}

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

SplitLayout::SplitLayout( QWidget* parent, Pos p )
: QLayout(parent), _stretchOne(0), _stretchTwo(0), _hide(p),
  _initialized(false), _handlePos(-1)
{
  _items.resize(3);

  _items[pOne] = 0;
  _items[pTwo] = 0;
}

SplitLayout::~SplitLayout()
{
  if( _items[pOne] )
    delete _items[pOne];

  delete _items[pHandle];

  if( _items[pTwo] )
    delete _items[pTwo];
}

// SplitLayoutHandle
void SplitLayout::recalculate()
{
  //activate();
  //parentWidget()->update();
  update();
}

int SplitLayout::calcMovePos( int curPos, int diffPos )
{
  _items[pOne]->widget()->show();
  _items[pTwo]->widget()->show();

  int minPos = 0;
  int maxPos = getSize(geometry()) - getSize(_items[pHandle]->geometry());

  int newPos = curPos + diffPos;
  if( newPos < minPos )
  {
    _items[pOne]->widget()->hide();
    newPos = 0;
  }
  if( newPos > maxPos )
  {
    _items[pTwo]->widget()->hide();
    newPos = maxPos;
  }

  return newPos;
}

int SplitLayout::calcJumpPos( int curPos )
{
  int maxPos = getSize(geometry()) - getSize(_items[pHandle]->sizeHint());

  if( curPos == 0 || curPos == maxPos )
  {
    _items[pOne]->widget()->show();
    _items[pTwo]->widget()->show();

    if( _handlePos == 0 || _handlePos == maxPos )
    {
      int w = getSize(_lastRect) / (_stretchOne+_stretchTwo);
      return w * _stretchOne;
    }
    else
    {
      return _handlePos;
    }
  }
  else
  {
    _handlePos = getHandlePos();
    
    _items[_hide]->widget()->hide();

    if( _hide == pOne )
    {
      _items[pTwo]->widget()->show();
    }
    else
    {
      _items[pOne]->widget()->show();
    }

    return 0;
  }
}

// TODO common code with calcJumpPos
void SplitLayout::jumpPos( bool visible )
{
  int maxPos = getSize(geometry()) - getSize(_items[pHandle]->sizeHint());
  int w      =( getSize(_lastRect) / (_stretchOne+_stretchTwo) ) * _stretchOne;

  if( visible )
  {
    _items[pOne]->widget()->show();
    _items[pTwo]->widget()->show();

    ((SplitHandle*)(_items[pHandle]->widget()))->reposition(w);
  }
  else
  {
    if( _hide == pOne )
    {
      _items[pOne]->widget()->hide();
      _items[pTwo]->widget()->show();

      ((SplitHandle*)(_items[pHandle]->widget()))->reposition(0);
    }
    else
    {
      _items[pOne]->widget()->show();
      _items[pTwo]->widget()->hide();

      ((SplitHandle*)(_items[pHandle]->widget()))->reposition(maxPos);
    }
  }
}

void SplitLayout::addWidgetOne( QWidget* w, bool hide, int stretch )
{
  _items[pOne] = new QWidgetItem(w); 
  _stretchOne = stretch;

  if( hide )
  {
    _hide = pOne;
    w->hide();
  }
}

void SplitLayout::addWidgetTwo( QWidget* w, bool hide, int stretch )
{
  _items[pTwo] = new QWidgetItem(w); 
  _stretchTwo = stretch;

  if( hide )
  {
    _hide = pTwo;
    w->hide();
  }
}

void SplitLayout::enableHandle( bool enable )
{
  if( enable )
  {
    (_items[pHandle]->widget())->show();
  }
  else
  {
    (_items[pHandle]->widget())->hide();
  }
}

void SplitLayout::addItem( QLayoutItem *item )
{
  if( !_items[pOne] )
  {
    _items[pOne] = item;
    return;
  }
  if( !_items[pTwo] )
  {
    _items[pTwo] = item;
    return;
  }
  assert(false);
}

QLayoutItem* SplitLayout::itemAt( int index ) const
{
  if( index >= (int)_items.size() )
  {
    return NULL;
  }
  return _items[index];
}

QLayoutItem* SplitLayout::takeAt( int index )
{
  if( index >= (int)_items.size() )
  {
    return NULL;
  }
  return _items[index];
}

int SplitLayout::count() const
{
  return _items.size();
}

QSize SplitLayout::sizeHint() const
{
  QSize s(0,0);

  for( TLayoutItems::const_iterator it = _items.begin(); it != _items.end(); it++ )
  {
    adjustSize( s, (*it)->sizeHint() );
  }
  return s;
}

QSize SplitLayout::minimumSize() const
{
  QSize s(0,0);

  for( TLayoutItems::const_iterator it = _items.begin(); it != _items.end(); it++ )
  {
    adjustSize( s, (*it)->minimumSize() );
  }
  return s;
}

void SplitLayout::setGeometry( const QRect &newRect )
{
  QLayout::setGeometry(newRect);

  if( _items.empty() )
  {
    assert(false);
    return;
  }

  QLayoutItem* o = _items[pOne];
  QLayoutItem* m = _items[pHandle];
  QLayoutItem* t = _items[pTwo];

  int os = 0; // one size
  int ms = 0; // handle size
  int ts = 0; // two size

  int op = 0; // one pos
  int mp = 0; // handle pos
  int tp = 0; // two pos

  if( o->widget()->isHidden() )
  {
    os = 0;
    ms = getSize(m->sizeHint());
    ts = getSize(newRect) - ms;

    op = 0;
    mp = 0;
    tp = op + ms;
  }
  else if( t->widget()->isHidden() )
  {
    ms = getSize(m->sizeHint());
    os = getSize(newRect) - ms;
    ts = 0;

    op = 0;
    mp = os;
    tp = 0;
  }
  else
  {
    int ds = getSize(newRect) - getSize(_lastRect); // delta size

    // move handle
    if( ds == 0 )
    {
      os = getPos(m->geometry());
      ms = getSize(m->sizeHint());
      ts = getSize(newRect) - os - ms;

      op = 0;
      mp = getPos(m->geometry());
      tp = mp + ms;
    }
    // resize
    else
    {
      os = getPos(m->geometry());
      ms = getSize(m->sizeHint());
      ts = getSize(_lastRect) - os - ms;

      op = 0;
      mp = getPos(m->geometry());
      tp = mp + ms;

      int div = ds / (_stretchOne + _stretchTwo);
      int mod = ds % (_stretchOne + _stretchTwo);

      int mod_div = mod / 2;
      int mod_mod = mod % 1;

      int ls = _stretchOne * div + mod_div;
      int rs = _stretchTwo * div + mod_div;

      if( _stretchOne > _stretchTwo )
      {
        ls += mod_mod;
      }
      else
      {
        rs += mod_mod;
      }

      os += ls;
      ts += rs;

      if( os < 0 )
      {
        os = 0;
        o->widget()->hide();
      }
      if( ts < 0 )
      {
        ts = 0;
        t->widget()->hide();
      }

      op = 0;
      mp = os;
      tp = mp + ms;
    }
  }

  QRect orect = calcRect( op, os, newRect );
  o->setGeometry(orect);

  QRect mrect = calcRect( mp, ms, newRect );
  m->setGeometry(mrect);

  QRect trect = calcRect( tp, ts, newRect );
  t->setGeometry(trect);

  _lastRect = newRect;


  if( ! _initialized )
  {
    _initialized = true;
    setHandlePos(_handlePos);
  }
}

void SplitLayout::setHandlePos( int pos )
{
  // the earliest time we can set the handle position is after the
  // first layout, so defer the initialization if we haven't done it.

  if( _initialized )
  {
    // we have a handle position?
    if( pos != -1 )
    {
      ((SplitHandle*)_items[pHandle]->widget())->reposition(pos);
    }
  }
  else
  {
    _handlePos = pos;
  }
}

int SplitLayout::getHandlePos() const
{
  return ((SplitHandle*)_items[pHandle]->widget())->getPos();
}

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

HSplitLayout::HSplitLayout( QWidget* parent, Pos pos )
: SplitLayout(parent,pos)
{
  _items[pHandle] = new QWidgetItem(
      new SplitHandle(parent,this,SplitHandle::oHorizontal)  );
}

int HSplitLayout::getPos( const QRect& r ) const
{
  return r.x();
}

int HSplitLayout::getSize( const QRect& r ) const
{
  return r.width();
}

int HSplitLayout::getSize( const QSize& s ) const
{
  return s.width();
}

void HSplitLayout::adjustSize( QSize& out, const QSize& item ) const
{
  out.setWidth( out.width() + item.width() );
  out.setHeight( out.height() < item.height() ? item.height() : out.height() );
}

QRect HSplitLayout::calcRect( int pos, int size, const QRect& r ) const
{
  return QRect( pos, r.y(), size, r.height() );
}

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

VSplitLayout::VSplitLayout( QWidget* parent, Pos pos )
: SplitLayout(parent,pos)
{
  _items[pHandle] = new QWidgetItem(
      new SplitHandle(parent,this,SplitHandle::oVertical)  );
}

int VSplitLayout::getPos( const QRect& r ) const
{
  return r.y();
}

int VSplitLayout::getSize( const QRect& r ) const
{
  return r.height();
}

int VSplitLayout::getSize( const QSize& s ) const
{
  return s.height();
}

void VSplitLayout::adjustSize( QSize& out, const QSize& item ) const
{
  out.setHeight( out.height() + item.height() );
  out.setWidth( out.width() < item.width() ? item.width() : out.width() );
}

QRect VSplitLayout::calcRect( int pos, int size, const QRect& r ) const
{
  return QRect( r.x(), pos, r.width(), size );
}

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