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

#include <math.h>

// Elements
#include "kastconstants.h"
#include "matrix.h"
#include "lengthattribute.h"
#include "angle.h"
#include "veclib.h"
#include "kastconstants.h"

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

// KDE
#include <kdebug.h>

Transform::Transform()
    : Attribute(ATTR_TRANSFORM),
    m_transformMetaType(TRANSFORM_NULL)
{
  m_rotateAngle = m_rotateCX = m_rotateCY = m_skew = 0.0;
  m_matrix = new Matrix();
}

Transform::~Transform()
{
  delete m_matrix;
}

Transform::Transform(const Transform &other)
    :  Attribute(ATTR_TRANSFORM), m_matrix(0)
{
  m_rotateAngle = m_rotateCX = m_rotateCY = 0.0;
  m_matrix = new Matrix();
  (*this) = other;
}

Transform &Transform::operator=(const Transform &other)
{
  Attribute::operator=(other);
  m_matrix->copy( *other.matrix() );
  m_transformMetaType = other.transformMetaType();
  m_rotateAngle = other.getRotateAngle();
  m_rotateCX = other.getRotateCX();
  m_rotateCY = other.getRotateCY();
  m_skew = other.getSkewX();
  return *this;
}

Attribute* Transform::clone()
{
  Transform* retElem = new Transform();
  *retElem = *this;
  return ( retElem );
}

/**
 * Warning: Resets metaType to TRANSFORM_MATRIX
 */
void Transform::setMatrix(const Matrix &matrix)
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->copy( matrix );
  setValid();
}

void Transform::multiply( const Matrix& secondMatrix )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->multiply( secondMatrix );
  setValid();
}

void Transform::postMultiply( const Matrix& secondMatrix )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postMultiply( secondMatrix );
  setValid();
}

void Transform::postTranslate( const double & x, const double & y )
{
  // FIXME: postTranslate does not have to make m_transformMetaType = TRANSFORM_MATRIX
  //        if the existing transforms are also translations... neither do postRotate, etc
  // FIXME: Everything quite inconsistant here. setTranslate deletes all existing transformations
  //        while postTranslate applies further transformations to the existing ones...
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postTranslate(x, y);
  setValid();
}

void Transform::postScale( const double & scaleFactorX, const double &scaleFactorY )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postScale(scaleFactorX, scaleFactorY);
  setValid();
}

void Transform::postRotate( const double & angle )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postRotate(angle);
  setValid();
}

void Transform::postSkewX( const double & angle )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postSkewX(angle);
  setValid();
}

void Transform::postSkewY( const double & angle )
{
  m_transformMetaType = TRANSFORM_MATRIX;
  m_matrix->postSkewY(angle);
  setValid();
}

void Transform::setTranslate(double tx, double ty)
{
  m_transformMetaType = TRANSFORM_TRANSLATE;
  m_matrix->reset();
  m_matrix->translate(tx, ty);
  setValid();
}

void Transform::setTranslate( LengthAttribute &tx, LengthAttribute &ty )
{
  m_transformMetaType = TRANSFORM_TRANSLATE;
  m_matrix->reset();
  m_matrix->translate(tx.value(), ty.value());  // TODO: we will need to preserve the value's unit types somewhere!
  setValid();
}

void Transform::setScale(double sx, double sy)
{
  m_transformMetaType = TRANSFORM_SCALE;
  m_matrix->reset();
  m_matrix->scaleNonUniform(sx, sy);
  setValid();
}

void Transform::setScale( LengthAttribute &sx, LengthAttribute &sy )
{
  m_transformMetaType = TRANSFORM_SCALE;
  m_matrix->reset();
  m_matrix->scaleNonUniform(sx.value(), sy.value());
  setValid();
}

void Transform::setRotate(double angle, double cx, double cy)
{
  m_transformMetaType = TRANSFORM_ROTATE;
  m_rotateAngle = angle;
  m_rotateCX = cx;
  m_rotateCY = cy;
  m_matrix->reset();
  m_matrix->translate(cx, cy);
  m_matrix->rotate(angle);
  m_matrix->translate(-cx, -cy);
  setValid();
}

void Transform::setRotate( Angle &angle, LengthAttribute &cx, LengthAttribute &cy )
{
  m_transformMetaType = TRANSFORM_ROTATE;
  m_rotateAngle = angle.degValue();
  m_rotateCX = cx.value();
  m_rotateCY = cy.value();
  m_matrix->reset();
  m_matrix->translate(cx.value(), cy.value());
  m_matrix->rotate(angle.degValue());
  m_matrix->translate(-cx.value(), -cy.value());
  setValid();
}

void Transform::setSkewX(double angle)
{
  m_skew = angle;
  m_transformMetaType = TRANSFORM_SKEWX;
  m_matrix->reset();
  m_matrix->skewX(angle);
  setValid();
}

void Transform::setSkewX( Angle& angle )
{
  m_skew = angle.degValue();
  m_transformMetaType = TRANSFORM_SKEWX;
  m_matrix->reset();
  m_matrix->skewX(angle.degValue());
  setValid();
}

void Transform::setSkewY(double angle )
{
  m_skew = angle;
  m_transformMetaType = TRANSFORM_SKEWY;
  m_matrix->reset();
  m_matrix->skewY(angle);
  setValid();
}

void Transform::setSkewY( Angle& angle )
{
  m_skew = angle.degValue();
  m_transformMetaType = TRANSFORM_SKEWY;
  m_matrix->reset();
  m_matrix->skewY(angle.degValue());
  setValid();
}

const QString Transform::getReadableTransformMetaType() const
{
  switch(m_transformMetaType)
  {
  case TRANSFORM_NULL:
    return "none";
    break;
  case TRANSFORM_TRANSLATE:
    return "translate";
    break;
  case TRANSFORM_SCALE:
    return "scale";
    break;
  case TRANSFORM_ROTATE:
    return "rotate";
    break;
  case TRANSFORM_SKEWX:
    return "skewX";
    break;
  case TRANSFORM_SKEWY:
    return "skewY";
    break;
  case TRANSFORM_MATRIX:
    return "matrix";
    break;
  default:
    return QString::null;
    break;
  }
}

const double Transform::getTranslateX( ) const
{
  return m_matrix->e();
}

const double Transform::getTranslateY( ) const
{
  return m_matrix->f();
}

const double Transform::getScaleX( ) const
{
  return m_matrix->a();
}

const double Transform::getScaleY( ) const
{
  return m_matrix->d();
}

void Transform::accept( AVisitor &a )
{
  a.visit(*this);
}

bool Transform::decompose( TransformList & tl ) const
{
  Matrix m(m_matrix->qmatrix());

  double detSing = (m.a()*m.d() - m.b()*m.c());
  if (ABS(detSing) < PRECISION_LIMIT)
    kdWarning() << "Transform::decompose() - singular matrix" << endl;

  /* Next take care of translation (easy). */
  double tx = 0.;
  double ty = 0.;
  double skew = 0.;
  double scaleX = 0.;
  double scaleY = 0.;

  if ( m.e() || m.f() )
  {
    tx = ABS(m.e()) < PRECISION_LIMIT ? 0 : m.e();
    ty = ABS(m.f()) < PRECISION_LIMIT ? 0 : m.f();
  }

  /* Compute X scale factor and normalize first row. */
  scaleX = sqrt( (m.a()*m.a()) + (m.b()*m.b()) );
  if (scaleX)
  {
    m.setA( m.a() / scaleX );
    m.setB( m.b() / scaleX );
  }
  scaleX = scaleX < PRECISION_LIMIT ? 0.0 : scaleX;

  /* Compute XY shear factor and make 2nd row orthogonal to 1st. */
  skew = (m.a()*m.c()) + (m.b()*m.d());
  m.setC( m.c() - (m.a() * skew));
  m.setD( m.d() - (m.b() * skew));


  /* Now, compute Y scale and normalize 2nd row. */
  scaleY = sqrt( (m.c()*m.c()) + (m.d()*m.d()) );
  if (scaleY)
  {
    m.setC( m.c() / scaleY );
    m.setD( m.d() / scaleY );
    skew /= scaleY;
  }

  scaleY = scaleY < PRECISION_LIMIT ? 0.0 : scaleY;
  //skew = ABS(skew) < PRECISION_LIMIT ? 0.0 : skew;
  //skew *= rad2deg;

  /* Get rotation */
  /* Check for a coordinate system flip. If det of upper-left 2x2
   * is -1, there is a reflection. If the rotation is < 180° negate
   * the y scale. If the rotation is > 180° then negate the x scale
   * and report a rotation between 0 and 180°. This dissymetry is
   * the result of computing (z) rotation from the first row (x component
   * of the axis system basis).
   */
  double det = (m.a()*m.d() - m.b()*m.c());

  double rotAngle = atan2(m.b(), m.a());
  //   if (rotAngle < 0)
  //   {
  //     rotAngle = (2 * (double) PI) + rotAngle;
  //   }

  rotAngle = ABS(rotAngle) < PRECISION_LIMIT ? 0 : rotAngle;
  if (rotAngle >= PI)
  {
    // rotAngle -= PI;  /*Why that, I'll have to check Graphic Gems ??? */
    if ( det < 0 )
    {
      scaleX *= -1;
    }
  }
  else if ( det < 0 )
  {
    scaleY *= -1;
  }
  rotAngle *= rad2deg;

  // Get skewX and skewY by undoing the other simple trans
  Matrix m2( m_matrix->qmatrix() );
  m2.postTranslate( -tx, -ty );
  m2.postRotate( -rotAngle );
  m2.scaleNonUniform( 1/scaleX, 1/scaleY );

  double skewY = atan(m2.b())* rad2deg;
  double skewX = atan(m2.c())* rad2deg;
  skewX = ABS(skewX) < PRECISION_LIMIT ? 0.0 : skewX;
  skewY = ABS(skewY) < PRECISION_LIMIT ? 0.0 : skewY;

  // Finally put it all together
  Transform* trans_translate = new Transform();
  trans_translate->setTranslate( tx, ty );
  tl.append( trans_translate );

  Transform* trans_rot = new Transform();
  trans_rot->setRotate(rotAngle, 0, 0);
  tl.append( trans_rot );

  Transform* trans_skewy = new Transform();
  trans_skewy->setSkewY( skewY );
  tl.append( trans_skewy );

  Transform* trans_skewx = new Transform();
  trans_skewx->setSkewX( skewX );
  tl.append( trans_skewx );


  Transform* trans_scale = new Transform();
  trans_scale->setScale( scaleX, scaleY );
  tl.append( trans_scale );

  return true;
}





TransformList::TransformList()
    : Attribute(ATTR_TRANSFORM), QPtrList<Transform>()
{
  setAutoDelete(true);  // We want the list to delete all it's childs on destruction
}

TransformList::TransformList(const TransformList &other)
    :  Attribute(ATTR_TRANSFORM), QPtrList<Transform>()
{
  (*this) = other;
}

TransformList::~TransformList()
{}

TransformList &TransformList::operator=(const TransformList &other)
{
  Attribute::operator=(other);
  // Clone the prtlist and all it's items
  clear();
  Transform *trans;
  TransformList& other2 = const_cast<TransformList&>(other);
  for ( trans = other2.first(); trans; trans = other2.next() )
  {
    append( new Transform( *trans ) );
  }

  return *this;
}

Attribute* TransformList::clone()
{
  TransformList* retElem = new TransformList();
  *retElem = *this;
  return ( retElem );
}

void TransformList::consolidate( Transform &trans )
{
  if(count()>0)
  {
    Matrix matrix;

    for(unsigned int i = 0; i < count(); i++)
      matrix.multiply(*at(i)->matrix());

    trans.setMatrix(matrix);
  }
}

/**
 * Overrides default isValid() method and delegate request to baseVal
 * @return 
 */
bool TransformList::isValid( ) const
{
  return !isEmpty();
}

void TransformList::accept( AVisitor &a )
{
  a.visit(*this);
}


