/***************************************************************************
 *   Copyright (C) 2004 by Hans Oischinger                                 *
 *   hans.oischinger@kdemail.net                                           *
 *                                                                         *
 *   This program 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.                                   *
 *                                                                         *
 *   This program 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 this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ***************************************************************************/
#include "selecttool.h"

// Core
#include "core/timeserver.h"
#include "core/direktorinstance.h"
#include "core/elementdatabase.h"
#include "core/documentwidget.h"

// Elements
#include "elements/vectorelement.h"
#include "elements/elementselection.h"
#include "elements/transformlist.h"
#include "elements/matrix.h"

// Visitors
#include "visitors/vconsolidatetransform.h"
#include "visitors/vdrawelements.h"

// Commands
#include "commands/emovecmd.h"
#include "commands/escalecmd.h"
#include "commands/etransformcmd.h"

// Qt
#include <qstring.h>
#include <qcursor.h>

// KDE
#include <klocale.h>
#include <kdebug.h>

// KCanvas Headers
#include <kcanvas/KCanvas.h>
#include <kcanvas/device/KRenderingDevice.h>

SelectTool::SelectTool()
    : GenericTool(),
    m_selectedHandle(0)
{
  m_currentMatrix = new Matrix();
}


SelectTool::~SelectTool()
{
  delete m_currentMatrix;
}

void SelectTool::activate( )
{
  if ( Kast::self()->isGUIReady() )
    Kast::self()->docWidget().setCursor( QCursor( Qt::arrowCursor ) );
}

QString SelectTool::name() const
{
  return i18n( "Select Tool" );
}

QString SelectTool::statusText() const
{
  return i18n( "Select" );
}

QString SelectTool::contextHelp() const
{
  QString s = i18n( "<qt><b>Selection tool:</b><br>" );
  s += i18n( "<i>Select something and hope that it works..." );
  return s;
}

void SelectTool::setCursor( ) const
{
  if ( Kast::self()->elementDB().selection().elements().isEmpty() ||
       Kast::self()->elementDB().selection().getState() != Element::normal )
  {
    Kast::self()->docWidget().setCursor( QCursor( Qt::arrowCursor ) );
    return;
  }

  switch( Kast::self()->elementDB().selection().handleNode( last() ) )
  {
  case ElementSelection::node_lt:
  case ElementSelection::node_rb:
    Kast::self()->docWidget().setCursor( QCursor( Qt::SizeFDiagCursor ) );
    break;
  case ElementSelection::node_rt:
  case ElementSelection::node_lb:
    Kast::self()->docWidget().setCursor( QCursor( Qt::SizeBDiagCursor ) );
    break;
  case ElementSelection::node_lm:
  case ElementSelection::node_rm:
    Kast::self()->docWidget().setCursor( QCursor( Qt::SizeHorCursor ) );
    break;
  case ElementSelection::node_mt:
  case ElementSelection::node_mb:
    Kast::self()->docWidget().setCursor( QCursor( Qt::SizeVerCursor ) );
    break;
  default:
    Kast::self()->docWidget().setCursor( QCursor( Qt::arrowCursor ) );
  }
}

void SelectTool::mouseButtonPress( )
{
  kdDebug() << k_funcinfo << endl;

  // Update the selection with item under pos()
  selectElementsUnderPoint(first());
  m_selbbox = Kast::self()->elementDB().selection().bbox();
}

void SelectTool::mouseButtonRelease( )
{
  kdDebug() << k_funcinfo << endl;

  Kast::self()->elementDB().selection().setState( Element::normal );
  Kast::self()->elementDB().selection().setDirty();
  Kast::self()->docWidget().updateElementSelection();
}

void SelectTool::mouseDragRelease()
{
  kdDebug() << k_funcinfo << endl;
  if( !Kast::self()->elementDB().selection().elements().isEmpty())
  {
    if ( m_selectedHandle != 0 )
    {
      fireScaleCMD();
      m_selectedHandle = 0; // Reset currently selected handle variable
    }
    else
      fireMoveCMD(last());
  }

  Kast::self()->elementDB().selection().setState( Element::normal );
  Kast::self()->elementDB().selection().setDirty();
  Kast::self()->docWidget().updateElementSelection();
}

void SelectTool::mouseDrag( )
{
  VectorElement* ve = 0;
  ve = Kast::self()->elementDB().selection().visualElements().getFirst();
  if (ve)
  {
    Transform* transform = new Transform();
    Matrix *ctm = VConsolidateTransform::calcCTM( *ve, false );
    if ( m_selectedHandle>0 )
    {
      int selboxNormX = (double)m_selbbox.x();
      int selboxNormY = (double)m_selbbox.y();
      int selboxNormW = (double)m_selbbox.width();
      int selboxNormH = (double)m_selbbox.height();
      int eventPosNormX = (double)last().x() * Kast::self()->docWidget().zoom();
      int eventPosNormY = (double)last().y() * Kast::self()->docWidget().zoom();

      // Translate selection to origin for proper scaling
      Transform tmp_transform;
      tmp_transform.setTranslate(-selboxNormX, -selboxNormY);

      // The scaleRefPoint has to be set differently for the left and top handles
      QPoint scaleRefPoint;
      if ( m_selectedHandle == ElementSelection::node_lt ||
           m_selectedHandle == ElementSelection::node_lm ||
           m_selectedHandle == ElementSelection::node_lb )
        scaleRefPoint.setX( selboxNormX + selboxNormW );
      else
        scaleRefPoint.setX( selboxNormX );

      if ( m_selectedHandle == ElementSelection::node_lt ||
           m_selectedHandle == ElementSelection::node_mt ||
           m_selectedHandle == ElementSelection::node_rt )
        scaleRefPoint.setY( selboxNormY + selboxNormH );
      else
        scaleRefPoint.setY( selboxNormY );

      // Calculate our scale factors
      m_scalex = 1;
      m_scaley = 1;
      m_tx = 0;
      m_ty = 0;
      if ( m_selectedHandle != ElementSelection::node_mt &&
           m_selectedHandle != ElementSelection::node_mb )
      {
        int scalexInPX = eventPosNormX - scaleRefPoint.x();
        if (  m_selectedHandle == ElementSelection::node_lt ||
              m_selectedHandle == ElementSelection::node_lm ||
              m_selectedHandle == ElementSelection::node_lb )
        {
          // shift to left & invert axis when scaling to left
          m_tx = eventPosNormX - selboxNormX;
          scalexInPX = -(scalexInPX);
        }
        m_scalex = (double)scalexInPX / (double)selboxNormW;
      }
      if ( m_selectedHandle != ElementSelection::node_lm &&
           m_selectedHandle != ElementSelection::node_rm )
      {
        int scaleyInPX = eventPosNormY - scaleRefPoint.y();
        if (  m_selectedHandle == ElementSelection::node_lt ||
              m_selectedHandle == ElementSelection::node_mt ||
              m_selectedHandle == ElementSelection::node_rt  )
        {
          // shift to top & invert axis when scaling to top
          m_ty = eventPosNormY - selboxNormY;
          scaleyInPX = -(scaleyInPX);
        }
        m_scaley = (double)scaleyInPX / (double)selboxNormH;
      }

      // Put it all together
      transform->setScale( m_scalex, m_scaley );  // Scale
      transform->multiply(*tmp_transform.matrix());  // Translate to 0.0
      transform->postMultiply(tmp_transform.matrix()->inverse()); // undo Translate
      if ( m_tx != 0 || m_ty != 0 )
        transform->postTranslate( m_tx, m_ty ); // Translate to calculated offset
      m_currentMatrix->copy( *transform->matrix() );
    }
    else
    {
      // Translate to the mouse coords
      transform->setTranslate((last().x() - first().x())*Kast::self()->docWidget().zoom(),
                              (last().y() - first().y())*Kast::self()->docWidget().zoom());
    }

    // Redraw the element and the selection rectangle
    KCanvas* canvas = Kast::self()->docWidget().getCanvas();
    KRenderingDevice* device = Kast::self()->docWidget().getDevice();
    ve->setDirty();
    VDrawElements *vDrawTree = new VDrawElements( canvas, device, false, false, transform );
    ve->accept( *vDrawTree );
    Kast::self()->elementDB().selection().setDirty();
    Kast::self()->elementDB().selection().accept( *vDrawTree );
    delete vDrawTree;
    delete transform;
    delete ctm;
//     ve->context()->invalidate();
//     canvas->drawDirtyChunks();
  }
}



void SelectTool::selectElementsUnderPoint( const QPoint & pos )
{
  kdDebug() << k_funcinfo << endl;

  // Hit detection
  QPtrList<ElementNode> newSelection;
  KCanvasItemList hits = Kast::self()->docWidget().getCanvas()->collisions(pos);
  KCanvasItemList::ConstIterator it = hits.begin();
  KCanvasItemList::ConstIterator end = hits.end();

  // Check for selection node hits
  if ( hits.count()<1 ||
       Kast::self()->elementDB().selection().elements().isEmpty() ||
       Kast::self()->elementDB().selection().getState() != Element::normal )
    m_selectedHandle = 0;
  else
    m_selectedHandle = Kast::self()->elementDB().selection().handleNode( pos );

  if (m_selectedHandle>0)
  {
    kdDebug() << "SelectTool::selectElementsUnderPoint() - Selection node selected" << endl;
    return;
  }

  // Set selection to hidden on mouspress (unhides on mouserelease)
  Kast::self()->elementDB().selection().setState( Element::hidden );
  Kast::self()->elementDB().selection().setDirty();

  // Check for element hits
  if ( hits.count()>0 )
  {
    kdDebug() << "SelectTool::selectElementsUnderPoint() - Detected CanvasItem under MousePos" << endl;
    for ( ; it != end; ++it )
    {
      if (!(*it)->userData())
        continue;
      ElementNode *current = static_cast<ElementNode*>((*it)->userData());
      if (current)
        newSelection.append( current );
    }
  }

  Kast::self()->elementDB().selection().clear(false);
  // Use this for multiselection:
  // Kast::self()->elementDB().selection().append( newSelection );
  if ( !newSelection.isEmpty() )
    Kast::self()->elementDB().selection().append(
      *newSelection.last(), false );
  Kast::self()->elementDB().selection().notifyChange();

  Kast::self()->docWidget().updateElementSelection();
  kdDebug() << "SelectTool::selectElementsUnderPoint() - Finished selecting item" << endl;
}

void SelectTool::fireMoveCMD( const QPoint& p )
{
  kdDebug() << k_funcinfo << endl;

  // Calculate in the zoomFactor
  int x1 = p.x()*Kast::self()->docWidget().zoom();
  int y1 = p.y()*Kast::self()->docWidget().zoom();
  int x2 = first().x()*Kast::self()->docWidget().zoom();
  int y2 = first().y()*Kast::self()->docWidget().zoom();

  if ( x1 != x2 || y1 != y2 ) // only apply if sth. changed
  {
    kdDebug() << "SelectTool::fireMoveCMD() - Dragging from (" << first().x() << "," << first().y() << ") to (" << p.x() << "," << p.y() << ")" << endl;

    if (Kast::self()->getRecordAnimation())
    {
      // Exec a transform command as we are applying an animation
      kdDebug() << "SelectTool::fireMoveCMD() - Recording animation" << endl;
      // Map coordinates on the screen to real coordinates
      Matrix *ctm = VConsolidateTransform::calcCTM(
                      *Kast::self()->elementDB().selection().elements().getFirst(), false );
      int curX1, curY1, curX2, curY2;
      Transform* transform = new Transform();
      ctm->qmatrix().invert().map(x2, y2, &curX1, &curY1);
      ctm->qmatrix().invert().map(x1, y1, &curX2, &curY2);
      transform->setTranslate(curX2 - curX1, curY2 - curY1);

      ETransformCMD *transfElement = new ETransformCMD( transform );
      Kast::self()->addCommand(transfElement);
      delete ctm;
    }
    else
    {
      kdDebug() << "SelectTool::fireMoveCMD() - Execing Move CMD" << endl;
      // Map coordinates on the screen to real coordinates
      Matrix *ctm = VConsolidateTransform::calcCTM(
                      *Kast::self()->elementDB().selection().elements().getFirst(), false );
      int curX1, curY1, curX2, curY2;
      ctm->qmatrix().invert().map(x2, y2, &curX1, &curY1);
      ctm->qmatrix().invert().map(x1, y1, &curX2, &curY2);

      // Exec a move command (no animation, manipulating baseVal)
      EMoveCMD *moveElement = new EMoveCMD( curX2 - curX1, curY2 - curY1 );
      Kast::self()->addCommand(moveElement);
      delete ctm;
    }
  }
}

void SelectTool::fireScaleCMD()
{
  kdDebug() << k_funcinfo << endl;

  if (m_scalex == 1 && m_scaley == 1)
    return; // only apply if sth. changed

  Matrix *ctm = VConsolidateTransform::calcCTM(
                  *Kast::self()->elementDB().selection().elements().getFirst(), false );

  // Calc the origin of the transformation
  int xorigin = m_selbbox.x();
  int yorigin = m_selbbox.y();

  if ( Kast::self()->elementDB().selection().visualElements().count() == 1 )
  {
    // Calculate and set the transformation matrix
    Transform *transform = new Transform();

    transform->setMatrix(*ctm); // Apply the ctm
    transform->postTranslate( -xorigin,  -yorigin); // Translate to 0,0 for proper scaling
    transform->postScale( m_scalex, m_scaley );  // Scale
    transform->postTranslate( xorigin,  yorigin); // undo translate to 0,0
    if ( m_tx != 0 || m_ty != 0 )
      transform->postTranslate( m_tx, m_ty ); // Translate to calculated offset
    transform->postMultiply(ctm->inverse());  // Undo ctm
    kdDebug() << transform->matrix()->a() << " "  << transform->matrix()->c() << " "  << transform->matrix()->e() << endl;
    kdDebug() << transform->matrix()->b() << " "  << transform->matrix()->d() << " "  << transform->matrix()->f() << endl;

    ETransformCMD *transfElement = 0;
    if ( !Kast::self()->getRecordAnimation() )
    {
      transfElement = new ETransformCMD( transform );
    }
    else
    {
      TransformList *tlist = new TransformList();
      transform->decompose( *tlist );
      transfElement = new ETransformCMD( tlist );

      TransformList *tlist2 = new TransformList( *tlist );
      Transform *narf = new Transform();
      tlist2->consolidate(*narf);
      kdDebug() << narf->matrix()->a() << " "  << narf->matrix()->c() << " "  << narf->matrix()->e() << endl;
      kdDebug() << narf->matrix()->b() << " "  << narf->matrix()->d() << " "  << narf->matrix()->f() << endl;

    }
    Kast::self()->addCommand(transfElement);
  }

}

