/*
 * Copyright (C) 2012 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <list>

#include <ignition/math/Matrix3.hh>
#include <ignition/math/Matrix4.hh>
#include <ignition/math/Vector3.hh>

#include "gazebo/common/Skeleton.hh"
#include "gazebo/common/SkeletonAnimation.hh"

using namespace gazebo;
using namespace common;

//////////////////////////////////////////////////
Skeleton::Skeleton()
{
}

//////////////////////////////////////////////////
Skeleton::Skeleton(SkeletonNode *_root)
{
  this->root = _root;
  this->BuildNodeMap();
}

//////////////////////////////////////////////////
Skeleton::~Skeleton()
{
  delete this->root;
}

//////////////////////////////////////////////////
void Skeleton::SetRootNode(SkeletonNode* _node)
{
  this->root = _node;
  this->BuildNodeMap();
}

//////////////////////////////////////////////////
SkeletonNode* Skeleton::GetRootNode()
{
  return this->root;
}

//////////////////////////////////////////////////
const SkeletonNode* Skeleton::GetRootNode() const
{
  return this->root;
}

//////////////////////////////////////////////////
SkeletonNode* Skeleton::GetNodeByName(std::string _name)
{
  for (std::map<unsigned int, SkeletonNode*>::iterator iter =
      this->nodes.begin(); iter != this->nodes.end(); ++iter)
    if (iter->second->GetName() == _name)
      return iter->second;

  return nullptr;
}

//////////////////////////////////////////////////
SkeletonNode* Skeleton::GetNodeById(std::string _id)
{
  for (std::map<unsigned int, SkeletonNode*>::iterator iter =
      this->nodes.begin(); iter != this->nodes.end(); ++iter)
    if (iter->second->GetId() == _id)
      return iter->second;

  return nullptr;
}

//////////////////////////////////////////////////
SkeletonNode* Skeleton::GetNodeByHandle(unsigned int _handle)
{
  return this->nodes[_handle];
}

//////////////////////////////////////////////////
unsigned int Skeleton::GetNumNodes()
{
  return this->nodes.size();
}

//////////////////////////////////////////////////
unsigned int Skeleton::GetNumJoints()
{
  unsigned int c = 0;
  for (std::map<unsigned int, SkeletonNode*>::iterator iter =
      this->nodes.begin(); iter != this->nodes.end(); ++iter)
    if (iter->second->IsJoint())
      c++;

  return c;
}


//////////////////////////////////////////////////
void Skeleton::Scale(double _scale)
{
  //  scale skeleton structure
  for (NodeMap::iterator iter = this->nodes.begin();
        iter != this->nodes.end(); ++iter)
  {
    SkeletonNode *node = iter->second;
    ignition::math::Matrix4d trans(node->Transform());
    ignition::math::Vector3d pos(trans.Translation());
    trans.SetTranslation(pos * _scale);
    node->SetTransform(trans, false);
  }

  //  update the nodes' model transforms
  this->root->UpdateChildrenTransforms();

  //  scale the animation data
  for (unsigned int i = 0; i < this->anims.size(); ++i)
    this->anims[i]->Scale(_scale);
}

//////////////////////////////////////////////////
void Skeleton::BuildNodeMap()
{
  std::list<SkeletonNode*> toVisit;
  toVisit.push_front(this->root);

  unsigned int handle = 0;

  while (!toVisit.empty())
  {
    SkeletonNode *node = toVisit.front();
    toVisit.pop_front();

    for (int i = (node->GetChildCount() - 1); i >= 0; i--)
      toVisit.push_front(node->GetChild(i));

    node->SetHandle(handle);
    this->nodes[handle] = node;
    handle++;
  }
}

//////////////////////////////////////////////////
void Skeleton::SetBindShapeTransform(const ignition::math::Matrix4d &_trans)
{
  this->bindShapeTransform = _trans;
}

//////////////////////////////////////////////////
ignition::math::Matrix4d Skeleton::BindShapeTransform()
{
  return this->bindShapeTransform;
}

//////////////////////////////////////////////////
void Skeleton::PrintTransforms()
{
  for (std::map<unsigned int, SkeletonNode*>::iterator iter =
      this->nodes.begin(); iter != this->nodes.end(); ++iter)
  {
    SkeletonNode *node = iter->second;
    std::cerr << "---------------\n" << node->GetName() << "\n";

    for (unsigned int i = 0; i < node->GetNumRawTrans(); ++i)
    {
      NodeTransform nt = node->GetRawTransform(i);
      std::cerr << "\t" << nt.GetSID();
      if (nt.GetType() == NodeTransform::MATRIX)
        std::cerr << " MATRIX\n";
      else
        if (nt.GetType() == NodeTransform::TRANSLATE)
          std::cerr << " TRANSLATE\n";
        else
          if (nt.GetType() == NodeTransform::ROTATE)
            std::cerr << " ROTATE\n";
          else
            std::cerr << " SCALE\n";
      std::cerr << nt() << "\n+++++++++++\n";
    }

    std::cerr << node->ModelTransform() << "\n";

    if (node->IsJoint())
      std::cerr << node->InverseBindTransform() << "\n";
  }
}

//////////////////////////////////////////////////
NodeMap Skeleton::GetNodes()
{
  return this->nodes;
}

//////////////////////////////////////////////////
void Skeleton::SetNumVertAttached(unsigned int _vertices)
{
  this->rawNW.clear();
  this->rawNW.resize(_vertices);
}

//////////////////////////////////////////////////
void Skeleton::AddVertNodeWeight(unsigned int _vertex, std::string _node,
                       double _weight)
{
  this->rawNW[_vertex].push_back(std::make_pair(_node, _weight));
}

//////////////////////////////////////////////////
unsigned int Skeleton::GetNumVertNodeWeights(unsigned int _vertex)
{
  if (_vertex >= this->rawNW.size())
    return 0;
  return this->rawNW[_vertex].size();
}

//////////////////////////////////////////////////
std::pair<std::string, double> Skeleton::GetVertNodeWeight(unsigned int _v,
                                     unsigned int _i)
{
  return this->rawNW[_v][_i];
}

//////////////////////////////////////////////////
unsigned int Skeleton::GetNumAnimations()
{
  return this->anims.size();
}

SkeletonAnimation *Skeleton::GetAnimation(const unsigned int _i)
{
  if (_i >= this->anims.size())
    return nullptr;

  return this->anims[_i];
}

//////////////////////////////////////////////////
void Skeleton::AddAnimation(SkeletonAnimation *_anim)
{
  this->anims.push_back(_anim);
}


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


//////////////////////////////////////////////////
SkeletonNode::SkeletonNode(SkeletonNode* _parent)
{
  this->parent = _parent;

  if (this->parent)
    this->parent->AddChild(this);
}

//////////////////////////////////////////////////
SkeletonNode::SkeletonNode(SkeletonNode* _parent, std::string _name,
                std::string _id, SkeletonNodeType _type)
{
  this->parent = _parent;

  if (this->parent)
    this->parent->AddChild(this);

  this->name = _name;
  this->id = _id;
  this->type = _type;
}

//////////////////////////////////////////////////
SkeletonNode::~SkeletonNode()
{
  this->children.clear();
}

//////////////////////////////////////////////////
void SkeletonNode::SetName(std::string _name)
{
  this->name = _name;
}

//////////////////////////////////////////////////
std::string SkeletonNode::GetName()
{
  return this->name;
}

//////////////////////////////////////////////////
std::string SkeletonNode::Name() const
{
  return this->name;
}

//////////////////////////////////////////////////
void SkeletonNode::SetId(std::string _id)
{
  this->id = _id;
}

//////////////////////////////////////////////////
std::string SkeletonNode::GetId()
{
  return this->id;
}

//////////////////////////////////////////////////
void SkeletonNode::SetType(SkeletonNodeType _type)
{
  this->type = _type;
}

//////////////////////////////////////////////////
bool SkeletonNode::IsJoint()
{
  if (this->type == JOINT)
    return true;
  else
    return false;
}

//////////////////////////////////////////////////
void SkeletonNode::SetTransform(const ignition::math::Matrix4d &_trans,
    bool _updateChildren)
{
  this->transform = _trans;

  if (this->parent == nullptr)
    this->modelTransform = _trans;
  else
    this->modelTransform = this->parent->ModelTransform() * _trans;

  /// propagate the change to the children nodes
  if (_updateChildren)
    this->UpdateChildrenTransforms();
}

//////////////////////////////////////////////////
void SkeletonNode::SetInitialTransform(const ignition::math::Matrix4d &_trans)
{
  this->initialTransform = _trans;
  this->SetTransform(_trans);
}

//////////////////////////////////////////////////
void SkeletonNode::Reset(bool resetChildren)
{
  this->SetTransform(this->initialTransform);

  if (resetChildren)
    for (unsigned int i = 0; i < this->GetChildCount(); ++i)
      this->GetChild(i)->Reset(true);
}

//////////////////////////////////////////////////
void SkeletonNode::UpdateChildrenTransforms()
{
  std::list<SkeletonNode*> toVisit;
  for (unsigned int i = 0; i < this->children.size(); ++i)
    toVisit.push_back(this->children[i]);

  while (!toVisit.empty())
  {
    SkeletonNode *node = toVisit.front();
    toVisit.pop_front();

    for (int i = (node->GetChildCount() - 1); i >= 0; i--)
      toVisit.push_front(node->GetChild(i));

    node->modelTransform = node->GetParent()->modelTransform * node->transform;
  }
}

//////////////////////////////////////////////////
ignition::math::Matrix4d SkeletonNode::Transform()
{
  return this->transform;
}

//////////////////////////////////////////////////
void SkeletonNode::SetModelTransform(
    const ignition::math::Matrix4d &_trans, const bool _updateChildren)
{
  this->modelTransform = _trans;

  if (this->parent == nullptr)
  {
    this->transform = _trans;
  }
  else
  {
    ignition::math::Matrix4d invParentTrans =
      this->parent->ModelTransform().Inverse();
    this->transform = invParentTrans * this->modelTransform;
  }

  if (_updateChildren)
    this->UpdateChildrenTransforms();
}

//////////////////////////////////////////////////
ignition::math::Matrix4d SkeletonNode::ModelTransform() const
{
  return this->modelTransform;
}

//////////////////////////////////////////////////
void SkeletonNode::SetParent(SkeletonNode* _parent)
{
  this->parent = _parent;
}

//////////////////////////////////////////////////
SkeletonNode* SkeletonNode::GetParent()
{
  return this->parent;
}

//////////////////////////////////////////////////
bool SkeletonNode::IsRootNode()
{
  if (!this->parent)
    return true;
  else
    return false;
}

//////////////////////////////////////////////////
void SkeletonNode::AddChild(SkeletonNode* _child)
{
  this->children.push_back(_child);
}

//////////////////////////////////////////////////
unsigned int SkeletonNode::GetChildCount()
{
  return this->children.size();
}

//////////////////////////////////////////////////
SkeletonNode* SkeletonNode::GetChild(unsigned int _index)
{
  return this->children[_index];
}

//////////////////////////////////////////////////
SkeletonNode* SkeletonNode::GetChildByName(std::string _name)
{
  for (unsigned int i = 0; i < this->children.size(); ++i)
    if (this->children[i]->GetName() == _name)
      return this->children[i];

  return nullptr;
}

//////////////////////////////////////////////////
SkeletonNode* SkeletonNode::GetChildById(std::string _id)
{
  for (unsigned int i = 0; i < this->children.size(); ++i)
    if (this->children[i]->GetId() == _id)
      return this->children[i];

  return nullptr;
}

//////////////////////////////////////////////////
void SkeletonNode::SetHandle(unsigned int _handle)
{
  this->handle = _handle;
}

//////////////////////////////////////////////////
unsigned int SkeletonNode::GetHandle()
{
  return this->handle;
}

//////////////////////////////////////////////////
void SkeletonNode::SetInverseBindTransform(
    const ignition::math::Matrix4d &_invBM)
{
  this->invBindTransform = _invBM;
}

//////////////////////////////////////////////////
ignition::math::Matrix4d SkeletonNode::InverseBindTransform()
{
  return this->invBindTransform;
}

//////////////////////////////////////////////////
bool SkeletonNode::HasInvBindTransform()
{
  return this->invBindTransform != ignition::math::Matrix4d::Zero;
}

//////////////////////////////////////////////////
std::vector<NodeTransform> SkeletonNode::GetRawTransforms()
{
  return this->rawTransforms;
}

//////////////////////////////////////////////////
unsigned int SkeletonNode::GetNumRawTrans()
{
  return this->rawTransforms.size();
}

//////////////////////////////////////////////////
NodeTransform SkeletonNode::GetRawTransform(unsigned int _i)
{
  return this->rawTransforms[_i];
}

//////////////////////////////////////////////////
void SkeletonNode::AddRawTransform(NodeTransform _t)
{
  this->rawTransforms.push_back(_t);
}

//////////////////////////////////////////////////
std::vector<NodeTransform> SkeletonNode::GetTransforms()
{
  return this->rawTransforms;
}


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


//////////////////////////////////////////////////
NodeTransform::NodeTransform(TransformType _type)
{
  this->sid = "_default_";
  this->type = _type;
  this->transform =
    ignition::math::Matrix4d(ignition::math::Matrix4d::Identity);
}

//////////////////////////////////////////////////
NodeTransform::NodeTransform(const ignition::math::Matrix4d &_mat,
    const std::string &_sid, TransformType _type)
{
  this->sid = _sid;
  this->type = _type;
  this->transform = _mat;
}

//////////////////////////////////////////////////
NodeTransform::~NodeTransform()
{
}

//////////////////////////////////////////////////
void NodeTransform::Set(const ignition::math::Matrix4d &_mat)
{
  this->transform = _mat;
}

//////////////////////////////////////////////////
void NodeTransform::SetType(TransformType _type)
{
  this->type = _type;
}

//////////////////////////////////////////////////
void NodeTransform::SetSID(std::string _sid)
{
  this->sid = _sid;
}

//////////////////////////////////////////////////
ignition::math::Matrix4d NodeTransform::GetTransform() const
{
  return this->transform;
}

//////////////////////////////////////////////////
NodeTransform::TransformType NodeTransform::GetType()
{
  return this->type;
}

//////////////////////////////////////////////////
std::string NodeTransform::GetSID()
{
  return this->sid;
}

//////////////////////////////////////////////////
void NodeTransform::SetComponent(unsigned int _idx, double _value)
{
  this->source[_idx] = _value;
}

//////////////////////////////////////////////////
void NodeTransform::SetSourceValues(const ignition::math::Matrix4d &_mat)
{
  this->source.resize(16);
  unsigned int idx = 0;
  for (unsigned int i = 0; i < 4; ++i)
  {
    for (unsigned int j = 0; j < 4; ++j)
    {
      this->source[idx] = _mat(i, j);
      idx++;
    }
  }
}

//////////////////////////////////////////////////
void NodeTransform::SetSourceValues(const ignition::math::Vector3d &_vec)
{
  this->source.resize(3);
  this->source[0] = _vec.X();
  this->source[1] = _vec.Y();
  this->source[2] = _vec.Z();
}

//////////////////////////////////////////////////
void NodeTransform::SetSourceValues(
    const ignition::math::Vector3d &_axis, const double _angle)
{
  this->source.resize(4);
  this->source[0] = _axis.X();
  this->source[1] = _axis.Y();
  this->source[2] = _axis.Z();
  this->source[3] = _angle;
}

//////////////////////////////////////////////////
void NodeTransform::RecalculateMatrix()
{
  if (this->type == MATRIX)
  {
    this->transform.Set(this->source[0], this->source[1], this->source[2],
                        this->source[3], this->source[4], this->source[5],
                        this->source[6], this->source[7], this->source[8],
                        this->source[9], this->source[10], this->source[11],
                        this->source[12], this->source[13], this->source[14],
                        this->source[15]);
  }
  else
    if (this->type == TRANSLATE)
    {
      this->transform.SetTranslation(
          ignition::math::Vector3d(this->source[0],
            this->source[1], this->source[2]));
    }
    else
      if (this->type == ROTATE)
      {
        ignition::math::Matrix3d mat;
        mat.Axis(ignition::math::Vector3d(
              this->source[0], this->source[1], this->source[2]),
            IGN_DTOR(this->source[3]));
        this->transform = mat;
      }
      else
      {
        this->transform.Scale(ignition::math::Vector3d(
              this->source[0], this->source[1], this->source[2]));
      }
}

//////////////////////////////////////////////////
ignition::math::Matrix4d NodeTransform::operator()()
{
  return this->transform;
}

//////////////////////////////////////////////////
ignition::math::Matrix4d NodeTransform::operator*(NodeTransform _t)
{
  ignition::math::Matrix4d m;

  m = this->transform * _t();

  return m;
}

//////////////////////////////////////////////////
ignition::math::Matrix4d NodeTransform::operator*(
    const ignition::math::Matrix4d &_m)
{
  ignition::math::Matrix4d m;

  m = this->transform * _m;

  return m;
}

//////////////////////////////////////////////////
void NodeTransform::PrintSource()
{
  std::cerr << this->sid;
  for (unsigned int i = 0; i < this->source.size(); ++i)
    std::cerr << " " << this->source[i];
  std::cerr << "\n";
}
