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

// Elements
#include "elements/elementtreeiterator.h"
#include "elements/vectorelement.h"

#include "elements/documentelement.h"
#include "elements/animateelement.h"
#include "elements/animatedlengthattr.h"
#include "elements/animatedcolorattr.h"
#include "elements/lengthattribute.h"
#include "elements/fillattribute.h"
#include "elements/strokeattribute.h"
#include "elements/colorattribute.h"
#include "elements/vectorpath.h"
#include "elements/groupelement.h"
#include "elements/defselement.h"
#include "elements/useelement.h"
#include "elements/transformlist.h"
#include "elements/animatedtransformlist.h"
#include "elements/urireference.h"
#include "elements/animatednumber.h"
#include "elements/number.h"
#include "elements/lineargradient.h"
#include "elements/radialgradient.h"
#include "elements/stopelement.h"
#include "elements/enumeration.h"
#include "elements/animatedenum.h"
#include "elements/timeattribute.h"
#include "elements/matrix.h"

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

// Qt
#include <qcolor.h>
// KDE
#include <kdebug.h>

VSetAnimValues::VSetAnimValues()
    : Visitor()
{}


VSetAnimValues::~VSetAnimValues()
{}

bool VSetAnimValues::visit( DocumentElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;
  element.width().animVal().setValue( element.width().baseVal().value() );
  element.height().animVal().setValue( element.height().baseVal().value() );
  element.x().animVal().setValue( element.x().baseVal().value() );
  element.y().animVal().setValue( element.y().baseVal().value() );
  return true;
}

bool VSetAnimValues::visit( VectorElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  // Set Transformations
  element.transformList().animVal() = element.transformList().baseVal();

  // Set Fill Style
  element.fill().color().animVal() = element.fill().color().baseVal();

  // Set Stroke Style
  element.stroke().color().animVal() = element.stroke().color().baseVal();

  // Set other style attrs
  element.fill().fillOpacity().animVal() = element.fill().fillOpacity().baseVal();
  element.stroke().strokeOpacity().animVal() = element.stroke().strokeOpacity().baseVal();
  element.stroke().strokeWidth().animVal() = element.stroke().strokeWidth().baseVal();

  VectorPath &vpath = element.vectorPath();
  // Set dirty if changed
  if ( vpath.getX1().animVal() != vpath.getX1().baseVal() ||
       vpath.getY1().animVal() != vpath.getY1().baseVal() ||
       vpath.getX2().animVal() != vpath.getX2().baseVal() ||
       vpath.getY2().animVal() != vpath.getY2().baseVal() ||
       vpath.getX3().animVal() != vpath.getX3().baseVal() ||
       vpath.getY3().animVal() != vpath.getY3().baseVal() )
    element.setDirty(true);

  // Set Length Elements
  vpath.getX1().animVal() = vpath.getX1().baseVal();
  vpath.getY1().animVal() = vpath.getY1().baseVal();
  vpath.getX2().animVal() = vpath.getX2().baseVal();
  vpath.getY2().animVal() = vpath.getY2().baseVal();
  vpath.getX3().animVal() = vpath.getX3().baseVal();
  vpath.getY3().animVal() = vpath.getY3().baseVal();
  return true;
}

bool VSetAnimValues::visit( AnimateTransformElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  if ( element.ref().getRefElement() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateTransformElement) - Missing Target Element!!!" << endl;
    return false;
  }
  if ( element.targetAttribute() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateTransformElement) - Missing Target Attribute!!!" << endl;
    return false;
  }

  AnimatedTransformList* animAttr =
    static_cast<AnimatedTransformList*>(element.targetAttribute()); // TODO: is this safe?
  Transform* animTransform = new Transform();

  /**
   *  Check if the Animation applies to the current point of time
   */
  double animTime = getAnimationTime( element );
  if (animTime<=-1)
    return true;  // Animation does not apply

  // kdDebug() << "VSetAnimValues::visit(AnimateTransformElement) - Animation applies" << endl;

  // Set Element contect dirty
  element.ref().getRefElement()->setDirty(true);

  // How far has this animation progressed?
  double progress = animTime - element.getStartTime().value();

  /**
   * Do we have a fromAttr? else create an empty one
   */
  Transform* fromAttr = 0;
  Transform* fakeFromAttr = 0;
  if ( element.fromAttribute().isValid() )
    fromAttr = &element.fromAttribute();
  else
  {
    fakeFromAttr = new Transform();
    fromAttr = fakeFromAttr;
  }

  /**
   *  To animation?
   */
  if ( element.toAttribute().isValid() )
  {
    float stepSize = 0;
    float stepSizeX = 0;
    float stepSizeY = 0;
    switch( element.toAttribute().transformMetaType() )
    {
    case Transform::TRANSFORM_ROTATE:
      stepSize = (element.toAttribute().getRotateAngle() -
                  fromAttr->getRotateAngle()) /
                 element.getSimpleDuration().value();

      animTransform->setRotate( fromAttr->getRotateAngle() + progress * stepSize,
                                fromAttr->getRotateCX(),
                                fromAttr->getRotateCY());
      break;
    case Transform::TRANSFORM_TRANSLATE:
      stepSizeX = (element.toAttribute().getTranslateX() -
                   fromAttr->getTranslateX()) /
                  element.getSimpleDuration().value();
      stepSizeY = (element.toAttribute().getTranslateY() -
                   fromAttr->getTranslateY()) /
                  element.getSimpleDuration().value();
      animTransform->setTranslate( fromAttr->getTranslateX() + progress * stepSizeX,
                                   fromAttr->getTranslateY() + progress * stepSizeY);
      break;
    case Transform::TRANSFORM_SCALE:
      stepSizeX = (element.toAttribute().getScaleX() -
                   fromAttr->getScaleX()) /
                  element.getSimpleDuration().value();
      stepSizeY = (element.toAttribute().getScaleY() -
                   fromAttr->getScaleY()) /
                  element.getSimpleDuration().value();
      animTransform->setScale( fromAttr->getScaleX() + progress * stepSizeX,
                               fromAttr->getScaleY() + progress * stepSizeY);
      break;
    case Transform::TRANSFORM_SKEWX:
      stepSize = (element.toAttribute().getSkewX() -
                  fromAttr->getSkewX()) /
                 element.getSimpleDuration().value();
      animTransform->setSkewX( fromAttr->getSkewX() + progress * stepSize );
      break;
    case Transform::TRANSFORM_SKEWY:
      stepSize = (element.toAttribute().getSkewY() -
                  fromAttr->getSkewY()) /
                 element.getSimpleDuration().value();
      animTransform->setSkewY( fromAttr->getSkewY() + progress * stepSize );
      break;
    case Transform::TRANSFORM_MATRIX:
      {
        float stepSizeA = (element.toAttribute().matrix()->a() - fromAttr->matrix()->a() ) /
                          element.getSimpleDuration().value();
        float stepSizeB = (element.toAttribute().matrix()->b() - fromAttr->matrix()->b() ) /
                          element.getSimpleDuration().value();
        float stepSizeC = (element.toAttribute().matrix()->c() - fromAttr->matrix()->c() ) /
                          element.getSimpleDuration().value();
        float stepSizeD = (element.toAttribute().matrix()->d() - fromAttr->matrix()->d() ) /
                          element.getSimpleDuration().value();
        float stepSizeE = (element.toAttribute().matrix()->e() - fromAttr->matrix()->e() ) /
                          element.getSimpleDuration().value();
        float stepSizeF = (element.toAttribute().matrix()->f() - fromAttr->matrix()->f() ) /
                          element.getSimpleDuration().value();
        double a, b, c, d, e, f;
        a = fromAttr->matrix()->a() + progress * stepSizeA;
        b = fromAttr->matrix()->b() + progress * stepSizeB;
        c = fromAttr->matrix()->c() + progress * stepSizeC;
        d = fromAttr->matrix()->d() + progress * stepSizeD;
        e = fromAttr->matrix()->e() + progress * stepSizeE;
        f = fromAttr->matrix()->f() + progress * stepSizeF;
        Matrix m(a, b, c, d, e, f);
        animTransform->setMatrix(m);
        break;
      }
    default:
      break;
    }
  }
  /**
   *  By animation?
   */
  else if ( element.byAttribute().isValid() )
  {
    float value = 0;
    float valueX = 0;
    float valueY = 0;
    switch( fromAttr->transformMetaType() )
    {
    case Transform::TRANSFORM_ROTATE:
      value = ( element.byAttribute().getRotateAngle() + (
                  (progress / element.getSimpleDuration().value()) *
                  element.byAttribute().getRotateAngle() ) );
      // TODO: nonbinary modulo 360°
      animTransform->setRotate( value,
                                fromAttr->getRotateCX(),
                                fromAttr->getRotateCY());
      break;
    case Transform::TRANSFORM_TRANSLATE:
      valueX = ( fromAttr->getTranslateX() + (
                   (progress / element.getSimpleDuration().value()) *
                   element.byAttribute().getTranslateX() ) );
      valueY = ( fromAttr->getTranslateY() + (
                   (progress / element.getSimpleDuration().value()) *
                   element.byAttribute().getTranslateY() ) );
      animTransform->setTranslate( valueX, valueY );
      break;
    case Transform::TRANSFORM_SCALE:
      valueX = ( fromAttr->getScaleX() + (
                   (progress / element.getSimpleDuration().value()) *
                   element.byAttribute().getScaleX() ) );
      valueY = ( fromAttr->getScaleY() + (
                   (progress / element.getSimpleDuration().value()) *
                   element.byAttribute().getScaleY() ) );
      animTransform->setScale( valueX, valueY );
      break;
    case Transform::TRANSFORM_SKEWX:
      value = ( fromAttr->getSkewX() + (
                  (progress / element.getSimpleDuration().value()) *
                  element.byAttribute().getSkewX() ) );
      animTransform->setSkewX( value );
      break;
    case Transform::TRANSFORM_SKEWY:
      value = ( fromAttr->getSkewY() + (
                  (progress / element.getSimpleDuration().value()) *
                  element.byAttribute().getSkewY() ) );
      animTransform->setSkewY( value );
      break;
    case Transform::TRANSFORM_MATRIX:
      {
        float factor = progress / element.getSimpleDuration().value();
        double a, b, c, d, e, f;
        a = fromAttr->matrix()->a() + factor * element.byAttribute().matrix()->a();
        b = fromAttr->matrix()->b() + factor * element.byAttribute().matrix()->b();
        c = fromAttr->matrix()->c() + factor * element.byAttribute().matrix()->c();
        d = fromAttr->matrix()->d() + factor * element.byAttribute().matrix()->d();
        e = fromAttr->matrix()->e() + factor * element.byAttribute().matrix()->e();
        f = fromAttr->matrix()->f() + factor * element.byAttribute().matrix()->f();
        Matrix m(a, b, c, d, e, f);
        animTransform->setMatrix(m);
      }
      break;
    default:
      break;
    }
  }

  delete fakeFromAttr;
  // Set the animVal
  animAttr->animVal().append( animTransform );
  return true;
}


bool VSetAnimValues::visit( AnimateLengthElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  if ( element.ref().getRefElement() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateLengthElement) - Missing Target Element!!!" << endl;
    return false;
  }
  if ( element.targetAttribute() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateLengthElement) - Missing Target Attribute!!!" << endl;
    return false;
  }

  LengthAttribute& animAttr = element.targetAttribute()->animVal();

  /**
   *  Check if the Animation applies to the current point of time
   */
  double animTime = getAnimationTime( element );
  if (animTime<=-1)
    return true;  // Animation does not apply

  //kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) - Animation applies" << endl;
  //kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From: " << element.fromAttribute().valueInSpecifiedUnits() << " by: " << element.byAttribute().valueInSpecifiedUnits() <<    " to: " << element.toAttribute().valueInSpecifiedUnits() << endl;

  // Set Element contect dirty
  element.ref().getRefElement()->setDirty(true);

  // How far has this animation progressed?
  double progress = animTime - element.getStartTime().value();

  /**
   * Do we have a fromAttr? else create an empty one
   */
  LengthAttribute* fromAttr = 0;
  LengthAttribute* fakeFromAttr = 0;
  if ( element.fromAttribute().isValid() )
    fromAttr = &element.fromAttribute();
  else
  {
    fakeFromAttr = new LengthAttribute();
    fromAttr = fakeFromAttr;
    if ( element.isAdditive() )
      *fromAttr = animAttr;
  }

  /**
   *  To animation?
   */
  if ( element.toAttribute().isValid() )
  {
    float stepSize = (element.toAttribute().valueInSpecifiedUnits() -
                      fromAttr->valueInSpecifiedUnits()) /
                     element.getSimpleDuration().value();
    float newVal = fromAttr->valueInSpecifiedUnits() + progress * stepSize;
    // kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From->To " << newVal << endl;
    animAttr.setValueInSpecifiedUnits( newVal );
  }
  /**
   *  By animation?
   */
  else if ( ( element.fromAttribute().isValid() && element.byAttribute().isValid() &&
              (element.fromAttribute().unitType() == element.byAttribute().unitType()) ) )
  {
    float value = ( fromAttr->valueInSpecifiedUnits() + (
                      (progress / element.getSimpleDuration().value()) *
                      element.byAttribute().valueInSpecifiedUnits() ) );
    // kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From->By " << value << endl;
    animAttr.setValueInSpecifiedUnits(value);
  }

  delete fakeFromAttr;
  return true;
}


bool VSetAnimValues::visit( AnimateNumberElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  if ( element.ref().getRefElement() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateNumberElement) - Missing Target Element!!!" << endl;
    return false;
  }
  if ( element.targetAttribute() == 0 )
  {
    kdWarning() << "VSetAnimValues::visit(AnimateNumberElement) - Missing Target Attribute!!!" << endl;
    return false;
  }

  Number& animAttr =
    static_cast<AnimatedNumber*>(element.targetAttribute())->animVal(); // TODO: is this safe?

  /**
   *  Check if the Animation applies to the current point of time
   */
  double animTime = getAnimationTime( element );
  if (animTime<=-1)
    return true;  // Animation does not apply

  kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) - Animation applies" << endl;
  kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From: " << element.fromAttribute().value() << " by: " << element.byAttribute().value() <<    " to: " << element.toAttribute().value() << endl;

  // Set Element contect dirty
  element.ref().getRefElement()->setDirty(true);

  // How far has this animation progressed?
  double progress = animTime - element.getStartTime().value();

  /**
   * Do we have a fromAttr? else create an empty one
   */
  Number* fromAttr = 0;
  Number* fakeFromAttr = 0;
  if ( element.fromAttribute().isValid() )
    fromAttr = &element.fromAttribute();
  else
  {
    fakeFromAttr = new Number();
    fromAttr = fakeFromAttr;
  }

  /**
   *  To animation?
   */
  if ( element.toAttribute().isValid() )
  {
    float stepSize = (element.toAttribute().value() -
                      fromAttr->value()) /
                     element.getSimpleDuration().value();
    float newVal = fromAttr->value() + progress * stepSize;
    // kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From->To " << newVal << endl;
    animAttr.setValue( newVal );
  }
  /**
   *  By animation?
   */
  else if ( ( element.fromAttribute().isValid() && element.byAttribute().isValid() &&
              (element.fromAttribute().numType() == element.byAttribute().numType() ) ) )
  {
    float value = ( fromAttr->value() + (
                      (progress / element.getSimpleDuration().value()) *
                      element.byAttribute().value() ) );
    // kdDebug() << "VSetAnimValues::visit(AnimateLengthElement) -   From->By " << value << endl;
    animAttr.setValue(value);
  }

  delete fakeFromAttr;
  return true;
}

/*
void VSetAnimValues::animateColor( AnimateElement& element )
{
  // Calculate the animVal
  double currentTime = Kast::self()->timeServer().getTime();
  int animR, animG, animB;
  int fromR, fromG, fromB;
  int toR, toG, toB;
 
  AnimatedColor* animAttr = static_cast<AnimatedColor*>(element.targetAttribute());
  ColorAttribute* to = static_cast<ColorAttribute*>(element.toAttribute());
  ColorAttribute* from = static_cast<ColorAttribute*>(element.fromAttribute());
 
  if ( element.getStartTime() <= currentTime &&
       (element.getStartTime() + element.getSimpleDuration().value()) >= currentTime )
  {
    if ( !from || !to )
      return; // TODO: Implement by here
    double progress = currentTime - element.getStartTime();
    from->qColor().getRgb( &fromR, &fromG, &fromB );
    to->qColor().getRgb( &toR, &toG, &toB );
 
    float stepSizeR = (toR - fromR) / element.getSimpleDuration().value();
    float stepSizeG = (toG - fromG) / element.getSimpleDuration().value();
    float stepSizeB = (toB - fromB) / element.getSimpleDuration().value();
 
    animR = fromR + progress * stepSizeR;
    animG = fromG + progress * stepSizeG;
    animB = fromB + progress * stepSizeB;
    kdDebug() << "ANIMATING: New RGB: " << animR << ":" << animG << ":" << animB << endl;
  }
  else
  {
    from->qColor().getRgb( &animR, &animG, &animB );
  }
 
  // Set the animVal
  animAttr->animVal().qColor().setRgb( animR, animG, animB );
}*/

bool VSetAnimValues::visit( GroupElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  element.transformList().animVal() = element.transformList().baseVal();
  return true;
}

bool VSetAnimValues::visit( DefsElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  return visit(static_cast<GroupElement&>(element));
}

bool VSetAnimValues::visit( UseElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  //   kdDebug() << "VSetAnimValues::visit( UseElement& element )" << endl;
  visit(static_cast<GroupElement&>(element));

  if ( element.getState() == Element::hidden || element.getState() == Element::hidden_locked )
    return false;

  /* if ( element.getFirstChild() )
  {
    ElementNode* node = element.getFirstChild();
    while( node )
    {
      ElementNode* delNodeNext = node->getNext();
      element.removeChild( node );
      node->deleteSubTree();
      node = delNodeNext;
    }
  } */

  //   kdDebug() << "VSetAnimValues::visit( UseElement& element ) - Appending clones..." << endl;
  if( !element.ref().getRefElement() )
  {
    kdWarning() << "VSetAnimValues::visit( UseElement& element ) - Invalid Ref!!!" << endl;
    return false;
  }
  if ( !element.getFirstChild() )
    element.appendChild( element.ref().getRefElement()->cloneSubTree() );

  return true;
}

/**
 * Returns the point of time in the animation that should be rendered
 * and -1 if Animation does not apply or -2 if anim does not apply but FILL_REMOVE is set
 */
double VSetAnimValues::getAnimationTime( AnimateElement& element )
{
  if ( element.getState() == Element::deleted )
    return false;

  double currentTime = Kast::self()->timeServer().getTime();

  if ( element.getStartTime().value() <= currentTime &&
       (element.getStartTime().value() + element.getSimpleDuration().value()) >= currentTime )
  {
    // We are between start and start + duration
    return currentTime;
  }
  else
  {
    if ( element.getFill() == AnimateElement::FILL_FREEZE &&
         (element.getStartTime().value() + element.getSimpleDuration().value()) < currentTime )
      return (element.getStartTime().value() + element.getSimpleDuration().value());  // We are after the animation but fill=freeze
    else
      return -2;  // Means FILL_REMOVE
  }

  return -1; // Means Animation does not apply
}

bool VSetAnimValues::visit( LinearGradient & element )
{
  if ( element.getState() == Element::deleted )
    return false;

  processGradient(element);
  element.getX1().animVal() = element.getX1().baseVal();
  element.getX2().animVal() = element.getX2().baseVal();
  element.getY1().animVal() = element.getY1().baseVal();
  element.getY2().animVal() = element.getY2().baseVal();
  return true;
}

bool VSetAnimValues::visit( RadialGradient & element )
{
  if ( element.getState() == Element::deleted )
    return false;

  processGradient(element);
  element.getCX().animVal() = element.getCX().baseVal();
  element.getCY().animVal() = element.getCY().baseVal();
  element.getR().animVal() = element.getR().baseVal();
  element.getFX().animVal() = element.getFX().baseVal();
  element.getFY().animVal() = element.getFY().baseVal();
  return true;
}

bool VSetAnimValues::visit( StopElement & element )
{
  if ( element.getState() == Element::deleted )
    return false;

  element.getStopColor().animVal() = element.getStopColor().baseVal();
  element.getStopOpacity().animVal() = element.getStopOpacity().baseVal();
  element.getOffset().animVal() = element.getOffset().baseVal();
  return true;
}

bool VSetAnimValues::processGradient( Gradient & element )
{
  if ( element.getState() == Element::deleted )
    return false;

  element.gradientTransformList().animVal() = element.gradientTransformList().baseVal();
  element.gradientUnits().animVal() = element.gradientUnits().baseVal();
  element.spreadMethod().animVal() = element.spreadMethod().baseVal();
  return true;
}
