/***************************************************************************
 *   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 "escalecmd.h"

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

// Elements
#include "elements/elementnode.h"
#include "elements/elementselection.h"
#include "elements/vectorelement.h"
#include "elements/vectorpath.h"
#include "elements/lengthattribute.h"
#include "elements/animatedlengthattr.h"
#include "elements/matrix.h"
#include "elements/transformlist.h"
#include "elements/matrix.h"
#include "elements/animatedtransformlist.h"

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

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

EScaleCMD::EScaleCMD( double sx, double sy, int xorigin, int yorigin, int m_tx, int m_ty )
    : ECommand( i18n( "Scale Elements" ) ),
    m_sx(sx), m_sy(sy),
    m_xorigin(xorigin), m_yorigin(yorigin),
    m_tx(m_tx), m_ty(m_ty)
{
  kdDebug() << k_funcinfo << endl;
  m_selection = Kast::self()->elementDB().selection().clone();
}


EScaleCMD::EScaleCMD( ElementNode* element, double sx, double sy, int xorigin, int yorigin, int m_tx, int m_ty )
    : ECommand(i18n( "Scale Elements" )),
    m_sx(sx), m_sy(sy),
    m_xorigin(xorigin), m_yorigin(yorigin),
    m_tx(m_tx), m_ty(m_ty)
{
  m_selection = new ElementSelection();
  m_selection->append(*element, false);
}

EScaleCMD::~EScaleCMD()
{
  delete m_selection;
}


void EScaleCMD::execute()
{
  ECommand::execute();
  m_selection->elements().getFirst()->accept(*this);
}

void EScaleCMD::unexecute()
{
  ECommand::unexecute();
  Kast::self()->elementDB().selection() = *m_selection;
  m_selection->elements().getFirst()->accept(*this);
}

bool EScaleCMD::visit( VectorElement & element )
{
  if ( isExecuting() || isRedoing() )
    kdDebug() << "EScaleCMD::visit() (exec) - Scaling by factor " << m_sx << "," << m_sy << ", origin " << m_xorigin << "x" << m_yorigin << endl;
  else
    kdDebug() << "EScaleCMD::visit() (exec) - Undoing Scaling by factor " << m_sx << "," << m_sy << ", origin " << m_xorigin << "x" << m_yorigin << endl;


  unsigned int metaType = element.vectorPath().shapeMetaType();
  if (metaType == VectorPath::SHAPE_RECT ||
      metaType == VectorPath::SHAPE_RRECT )
  {
    if ( isExecuting() || isRedoing() )
    {
      // TODO: Rectangles or other basic shapes w/o skew or matrix Transformations (others too?)
      // should be converted to paths and be properly scaled then
      double x = element.vectorPath().getX().baseVal().value();
      double y = element.vectorPath().getY().baseVal().value();
      element.vectorPath().getX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, x + m_tx );
      element.vectorPath().getY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, y + m_ty );

      double w = element.vectorPath().getWidth().baseVal().value();
      double h = element.vectorPath().getHeight().baseVal().value();
      element.vectorPath().getWidth().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, w * m_sx );
      element.vectorPath().getHeight().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, h * m_sy );
    }
    else
    {
      double x = element.vectorPath().getX().baseVal().value();
      double y = element.vectorPath().getY().baseVal().value();
      element.vectorPath().getX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, x - m_tx );
      element.vectorPath().getY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, y - m_ty );

      double w = element.vectorPath().getWidth().baseVal().value();
      double h = element.vectorPath().getHeight().baseVal().value();
      element.vectorPath().getWidth().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, w / m_sx );
      element.vectorPath().getHeight().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, h / m_sy );
    }
  }
  else if (metaType == VectorPath::SHAPE_CIRCLE ||
           metaType == VectorPath::SHAPE_ELLIPSE )
  {
    if ( isExecuting() || isRedoing() )
    {
      double x = element.vectorPath().getX().baseVal().value();
      double y = element.vectorPath().getY().baseVal().value();
      element.vectorPath().getX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, x + m_tx );
      element.vectorPath().getY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, y + m_ty );

      // We always convert to ellipse on scaling
      element.vectorPath().setShapeMetaType( VectorPath::SHAPE_ELLIPSE );
      double rx = element.vectorPath().getRadiusX().baseVal().value();
      double ry = element.vectorPath().getRadiusY().baseVal().value();
      element.vectorPath().getRadiusX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, rx * m_sx );
      element.vectorPath().getRadiusY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, ry * m_sy );
    }
    else
    {
      double x = element.vectorPath().getX().baseVal().value();
      double y = element.vectorPath().getY().baseVal().value();
      element.vectorPath().getX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, x - m_tx );
      element.vectorPath().getY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, y - m_ty );

      double rx = element.vectorPath().getRadiusX().baseVal().value();
      double ry = element.vectorPath().getRadiusY().baseVal().value();
      element.vectorPath().getRadiusX().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, rx / m_sx );
      element.vectorPath().getRadiusY().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, ry / m_sy );
    }
  }
  else if ( metaType == VectorPath::SHAPE_LINE)
  {
    if ( isExecuting() || isRedoing() )
    {
      double x1 = element.vectorPath().getX1().baseVal().value();
      double x2 = element.vectorPath().getX2().baseVal().value();
      double y1 = element.vectorPath().getY1().baseVal().value();
      double y2 = element.vectorPath().getY2().baseVal().value();
      element.vectorPath().getX1().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, m_xorigin + m_tx + (x1 - m_xorigin) * m_sx );
      element.vectorPath().getX2().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, m_xorigin + m_tx  + (x2 - m_xorigin) * m_sx );
      element.vectorPath().getY1().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, m_yorigin + m_ty  + (y1 - m_yorigin) * m_sy );
      element.vectorPath().getY2().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, m_yorigin + m_ty  + (y2 - m_yorigin) * m_sy );
    }
    else
    {
      double x1 = element.vectorPath().getX1().baseVal().value();
      double x2 = element.vectorPath().getX2().baseVal().value();
      double y1 = element.vectorPath().getY1().baseVal().value();
      double y2 = element.vectorPath().getY2().baseVal().value();
      element.vectorPath().getX1().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, (x1 - m_xorigin - m_tx ) / m_sx + m_xorigin );
      element.vectorPath().getX2().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, (x2 - m_xorigin - m_tx ) / m_sx + m_xorigin );
      element.vectorPath().getY1().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, (y1 - m_yorigin - m_ty ) / m_sy + m_yorigin );
      element.vectorPath().getY2().baseVal().newValueSpecifiedUnits(
        LengthAttribute::SVG_LENGTHTYPE_PX, (y2 - m_yorigin - m_ty ) / m_sy + m_yorigin );
    }
  }
  else if ( metaType == VectorPath::SHAPE_POLYGON ||
            metaType == VectorPath::SHAPE_PATH)
  {
    Transform transform;
    Transformable* ve = m_selection->visualElements().getFirst();
    ve->transformList().baseVal().consolidate( transform );
    bool noTrans = ve->transformList().baseVal().isEmpty();
    const Matrix *m = transform.matrix();
    Matrix mi = transform.matrix()->inverse();

    KCPathDataList* pathDataList = element.vectorPath().kcPathData();
    KCPathDataList::iterator it;
    for ( it = pathDataList->begin(); it != pathDataList->end(); ++it )
    {
      if ( isExecuting() || isRedoing() )
      {
        // TODO: we need a mechanism that prevents information loss on scaling:
        // example: scale a path to a very small size and then to a big size again
        // maybe we should store a original path?
        // maybe a caching undo mechanism would also do the job for now
        if ( noTrans )
        {
          // Simple scaling ( no matrixes need to be applied )
          (*it).x1 = m_xorigin + m_tx + ( ((*it).x1 - m_xorigin) * m_sx );
          (*it).x2 = m_xorigin + m_tx + ( ((*it).x2 - m_xorigin) * m_sx );
          (*it).x3 = m_xorigin + m_tx + ( ((*it).x3 - m_xorigin) * m_sx );
          (*it).y1 = m_yorigin + m_ty + ( ((*it).y1 - m_yorigin) * m_sy );
          (*it).y2 = m_yorigin + m_ty + ( ((*it).y2 - m_yorigin) * m_sy );
          (*it).y3 = m_yorigin + m_ty + ( ((*it).y3 - m_yorigin) * m_sy );
        }
        else
        {
          // Apply transformation and inverse transformation to scale correctly
          int x1, y1;
          m->qmatrix().map((*it).x1, (*it).y1, &x1, &y1);
          x1 = m_xorigin + m_tx + ( (x1 - m_xorigin) * m_sx );
          y1 = m_yorigin + m_ty + ( (y1 - m_yorigin) * m_sy );
          mi.qmatrix().map(x1, y1, &(*it).x1, &(*it).y1);
          int x2, y2;
          m->qmatrix().map((*it).x2, (*it).y2, &x2, &y2);
          x2 = m_xorigin + m_tx + ( (x2 - m_xorigin) * m_sx );
          y2 = m_yorigin + m_ty + ( (y2 - m_yorigin) * m_sy );
          mi.qmatrix().map(x2, y2, &(*it).x2, &(*it).y2);
          int x3, y3;
          m->qmatrix().map((*it).x3, (*it).y3, &x3, &y3);
          x3 = m_xorigin + m_tx + ( (x3 - m_xorigin) * m_sx );
          y3 = m_yorigin + m_ty + ( (y3 - m_yorigin) * m_sy );
          mi.qmatrix().map(x3, y3, &(*it).x3, &(*it).y3);
        }
      }
      else
      {
        if ( noTrans )
        {
          // Simple scaling ( no matrixes need to be applied )
          (*it).x1 = ((*it).x1 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          (*it).x2 = ((*it).x2 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          (*it).x3 = ((*it).x3 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          (*it).y1 = ((*it).y1 - m_yorigin - m_ty ) / m_sy + m_yorigin;
          (*it).y2 = ((*it).y2 - m_yorigin - m_ty ) / m_sy + m_yorigin;
          (*it).y3 = ((*it).y3 - m_yorigin - m_ty ) / m_sy + m_yorigin;
        }
        else
        {
          // Apply transformation and inverse transformation to scale correctly
          int x1, y1;
          m->qmatrix().map((*it).x1, (*it).y1, &x1, &y1);
          x1 = ((*it).x1 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          y1 = ((*it).y1 - m_yorigin - m_ty ) / m_sy + m_yorigin;
          mi.qmatrix().map(x1, y1, &(*it).x1, &(*it).y1);
          int x2, y2;
          m->qmatrix().map((*it).x2, (*it).y2, &x2, &y2);
          x2 = ((*it).x2 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          y2 = ((*it).y2 - m_yorigin - m_ty ) / m_sy + m_yorigin;
          mi.qmatrix().map(x2, y2, &(*it).x2, &(*it).y2);
          int x3, y3;
          m->qmatrix().map((*it).x3, (*it).y3, &x3, &y3);
          x3 = ((*it).x3 - m_xorigin - m_tx ) / m_sx + m_xorigin;
          y3 = ((*it).y3 - m_yorigin - m_ty ) / m_sy + m_yorigin;
          mi.qmatrix().map(x3, y3, &(*it).x3, &(*it).y3);
        }
      }
    }

  }

  element.setDirty();
  Kast::self()->elementDB().entryModified(&element);
  return true;
}


