/*
 * Copyright (C) 2012-2016 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.
 *
*/
/**
 * File: MovableText.cpp
 *
 * description: This create create a billboarding object that display a text.
 *
 * @author  2003 by cTh see gavocanov@rambler.ru
 * @update  2006 by barraq see nospam@barraquand.com
 * @update  2007 by independentCreations see independentCreations@gmail.com
 */

#ifdef _WIN32
  // Ensure that windows types like LONGLONG are defined
  #include <windows.h>
#endif

#include <boost/thread/recursive_mutex.hpp>

#include "gazebo/common/common.hh"
#include "gazebo/common/Assert.hh"
#include "gazebo/math/gzmath.hh"
#include "gazebo/rendering/MovableText.hh"

#define POS_TEX_BINDING    0
#define COLOUR_BINDING     1

using namespace gazebo;
using namespace rendering;

//////////////////////////////////////////////////
MovableText::MovableText()
    : camera(NULL),
    renderWindow(NULL) ,
    font(NULL) ,
    viewportAspectCoef(0.75),
    spaceWidth(0) ,
    updateColors(true) ,
    vertAlign(V_BELOW) ,
    horizAlign(H_LEFT) ,
    onTop(false) ,
    baseline(0.0)
{
  this->renderOp.vertexData = NULL;

  this->dirty = true;
  this->mutex = new boost::recursive_mutex();
  this->aabb = new Ogre::AxisAlignedBox;
}

//////////////////////////////////////////////////
MovableText::~MovableText()
{
  delete this->renderOp.vertexData;
  delete this->mutex;
  delete this->aabb;
}

//////////////////////////////////////////////////
void MovableText::Load(const std::string &name_,
                        const std::string &text_,
                        const std::string &fontName_,
                        float charHeight_,
                        const common::Color &color_)
{
  {
    boost::recursive_mutex::scoped_lock lock(*this->mutex);

    this->text = text_;
    this->color = color_;
    this->fontName = fontName_;
    this->charHeight = charHeight_;
    this->mName = name_;

    if (this->mName == "")
      throw Ogre::Exception(Ogre::Exception::ERR_INVALIDPARAMS,
          "Trying to create MovableText without name",
          "MovableText::MovableText");

    if (this->text == "")
      throw Ogre::Exception(Ogre::Exception::ERR_INVALIDPARAMS,
          "Trying to create MovableText without text",
          "MovableText::MovableText");

    this->dirty = true;
  }

  this->SetFontName(this->fontName);

  this->_setupGeometry();
}

//////////////////////////////////////////////////
void MovableText::Update()
{
  if (this->dirty)
  {
    this->_setupGeometry();
    this->dirty = false;
  }
}

//////////////////////////////////////////////////
void MovableText::SetFontName(const std::string &newFontName)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if ((Ogre::MaterialManager::getSingletonPtr()->resourceExists(
          this->mName + "Material")))
  {
    Ogre::MaterialManager::getSingleton().remove(this->mName + "Material");
  }

  if (this->fontName != newFontName || this->material.isNull() || !this->font)
  {
    this->fontName = newFontName;

    this->font = (Ogre::Font*)Ogre::FontManager::getSingleton().getByName(
        this->fontName).getPointer();

    if (!this->font)
    {
      throw Ogre::Exception(Ogre::Exception::ERR_ITEM_NOT_FOUND,
                            "Could not find font " + fontName,
                            "MovableText::setFontName");
    }

    this->font->load();

    if (!this->material.isNull())
    {
      Ogre::MaterialManager::getSingletonPtr()->remove(
          this->material->getName());
      this->material.setNull();
    }

    this->material = this->font->getMaterial()->clone(this->mName + "Material");

    if (!this->material->isLoaded())
      this->material->load();

    this->material->setDepthCheckEnabled(!this->onTop);
    this->material->setDepthBias(!this->onTop, 0);
    this->material->setDepthWriteEnabled(this->onTop);
    this->material->setLightingEnabled(false);

    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetText(const std::string &newText)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if (this->text != newText)
  {
    this->text = newText;
    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetColor(const common::Color &newColor)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if (this->color != newColor)
  {
    this->color = newColor;
    this->updateColors = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetCharHeight(float _height)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if (!math::equal(this->charHeight, _height))
  {
    this->charHeight = _height;
    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetSpaceWidth(float _width)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if (!math::equal(this->spaceWidth, _width))
  {
    this->spaceWidth = _width;
    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetTextAlignment(const HorizAlign &h, const VertAlign &v)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  if (this->horizAlign != h)
  {
    this->horizAlign = h;
    this->needUpdate = true;
  }

  if (this->vertAlign != v)
  {
    this->vertAlign = v;
    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetBaseline(float _base)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  if (!math::equal(this->baseline, _base))
  {
    this->baseline = _base;
    this->needUpdate = true;
  }
}

//////////////////////////////////////////////////
void MovableText::SetShowOnTop(bool show)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  if (this->onTop != show && !this->material.isNull())
  {
    this->onTop = show;

    this->material->setDepthBias(!this->onTop, 0);
    this->material->setDepthCheckEnabled(!this->onTop);
    this->material->setDepthWriteEnabled(this->onTop);
  }
}

//////////////////////////////////////////////////
bool MovableText::GetShowOnTop() const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return this->onTop;
}

//////////////////////////////////////////////////
math::Box MovableText::GetAABB(void)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return math::Box(
      math::Vector3(this->aabb->getMinimum().x,
                    this->aabb->getMinimum().y,
                    this->aabb->getMinimum().z),
      math::Vector3(this->aabb->getMaximum().x,
                    this->aabb->getMaximum().y,
                    this->aabb->getMaximum().z));
}

//////////////////////////////////////////////////
void MovableText::_setupGeometry()
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  GZ_ASSERT(this->font, "font class member is null");
  GZ_ASSERT(!this->material.isNull(), "material class member is null");

  Ogre::VertexDeclaration *decl = NULL;
  Ogre::VertexBufferBinding *bind = NULL;
  Ogre::HardwareVertexBufferSharedPtr ptbuf;
  Ogre::HardwareVertexBufferSharedPtr cbuf;
  float *pVert = NULL;
  float largestWidth = 0;
  float left = 0;
  float top = 0;
  size_t offset = 0;
  float maxSquaredRadius = 0.0f;
  bool first = true;
  std::string::iterator i;
  bool newLine = true;
  float len = 0.0f;

  // for calculation of AABB
  Ogre::Vector3 min(0, 0, 0);
  Ogre::Vector3 max(0, 0, 0);
  Ogre::Vector3 currPos(0, 0, 0);

  unsigned int vertexCount = static_cast<unsigned int>(this->text.size() * 6);


  if (this->renderOp.vertexData)
  {
    // Removed this test as it causes problems when replacing a caption
    // of the same size: replacing "Hello" with "hello"
    // as well as when changing the text alignment
    // if (this->renderOp.vertexData->vertexCount != vertexCount)
    {
      delete this->renderOp.vertexData;
      this->renderOp.vertexData = NULL;
      this->updateColors = true;
    }
  }

  if (!this->renderOp.vertexData)
    this->renderOp.vertexData = new Ogre::VertexData();

  this->renderOp.indexData = 0;
  this->renderOp.vertexData->vertexStart = 0;
  this->renderOp.vertexData->vertexCount = vertexCount;
  this->renderOp.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
  this->renderOp.useIndexes = false;

  decl = this->renderOp.vertexData->vertexDeclaration;
  bind = this->renderOp.vertexData->vertexBufferBinding;

  // create/bind positions/tex.ccord. buffer
  if (!decl->findElementBySemantic(Ogre::VES_POSITION))
    decl->addElement(POS_TEX_BINDING, offset, Ogre::VET_FLOAT3,
                     Ogre::VES_POSITION);

  offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

  if (!decl->findElementBySemantic(Ogre::VES_TEXTURE_COORDINATES))
    decl->addElement(POS_TEX_BINDING, offset, Ogre::VET_FLOAT2,
                     Ogre::VES_TEXTURE_COORDINATES, 0);

  ptbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
            decl->getVertexSize(POS_TEX_BINDING),
            this->renderOp.vertexData->vertexCount,
            Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);

  bind->setBinding(POS_TEX_BINDING, ptbuf);

  // Colours - store these in a separate buffer because they change less often
  if (!decl->findElementBySemantic(Ogre::VES_DIFFUSE))
    decl->addElement(COLOUR_BINDING, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);

  cbuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
           decl->getVertexSize(COLOUR_BINDING),
           this->renderOp.vertexData->vertexCount,
           Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);

  bind->setBinding(COLOUR_BINDING, cbuf);

  pVert = static_cast<float*>(ptbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));

  // Derive space width from a capital A
  if (math::equal(this->spaceWidth, 0.0f))
    this->spaceWidth =
      this->font->getGlyphAspectRatio('A') * this->charHeight * 2.0;

  if (this->vertAlign == MovableText::V_ABOVE)
  {
    // Raise the first line of the caption
    top += this->charHeight;

    for (i = this->text.begin(); i != this->text.end(); ++i)
    {
      if (*i == '\n')
        top += this->charHeight * 2.0;
    }
  }

  for (i = this->text.begin(); i != this->text.end(); ++i)
  {
    if (newLine)
    {
      len = 0.0;
      for (std::string::iterator j = i; j != this->text.end(); ++j)
      {
        Ogre::Font::CodePoint character = *j;
        if (character == 0x000D  // CR
            || character == 0x0085)  // NEL
        {
          break;
        }
        else if (character == 0x0020)  // space
        {
          len += this->spaceWidth;
        }
        else
        {
          len += this->font->getGlyphAspectRatio(character) *
                 this->charHeight * 2.0 * this->viewportAspectCoef;
        }
      }

      newLine = false;
    }

    Ogre::Font::CodePoint character = (*i);

    if (character == 0x000D  // CR
        || character == 0x0085)  // NEL
    {
      top -= this->charHeight * 2.0;
      newLine = true;

      // Also reduce tri count
      this->renderOp.vertexData->vertexCount -= 6;
      continue;
    }
    else if (character == 0x0020)  // space
    {
      // Just leave a gap, no tris
      left += this->spaceWidth;

      // Also reduce tri count
      this->renderOp.vertexData->vertexCount -= 6;
      continue;
    }

    float horiz_height = this->font->getGlyphAspectRatio(character) *
                         this->viewportAspectCoef;

    const Ogre::Font::UVRect& uvRect = this->font->getGlyphTexCoords(character);

    // each vert is (x, y, z, u, v)
    //------------------------------------------------------------------------
    // First tri
    //
    // Upper left
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len/2.0);

    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.left;
    *pVert++ = uvRect.top;

    // Deal with bounds
    if (this->horizAlign == MovableText::H_LEFT)
      currPos = Ogre::Vector3(left, top, 0);
    else
      currPos = Ogre::Vector3(left - (len/2.0), top, 0);

    if (first)
    {
      min = max = currPos;
      maxSquaredRadius = currPos.squaredLength();
      first = false;
    }
    else
    {
      min.makeFloor(currPos);
      max.makeCeil(currPos);
      maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());
    }

    top -= this->charHeight * 2.0;

    // Bottom left
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len / 2.0);

    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.left;
    *pVert++ = uvRect.bottom;

    // Deal with bounds
    if (this->horizAlign == MovableText::H_LEFT)
      currPos = Ogre::Vector3(left, top, 0);
    else
      currPos = Ogre::Vector3(left - (len/2), top, 0);

    min.makeFloor(currPos);
    max.makeCeil(currPos);
    maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());


    top += this->charHeight * 2.0;
    left += horiz_height * this->charHeight * 2.0;

    // Top right
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len/2.0);

    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.right;
    *pVert++ = uvRect.top;

    // Deal with bounds
    if (this->horizAlign == MovableText::H_LEFT)
      currPos = Ogre::Vector3(left, top, 0);
    else
      currPos = Ogre::Vector3(left - (len/2), top, 0);

    min.makeFloor(currPos);
    max.makeFloor(currPos);
    maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());


    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    // Second tri
    //
    // Top right (again)
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len/2.0);
    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.right;
    *pVert++ = uvRect.top;

    // Deal with bounds
    currPos = Ogre::Vector3(left, top, 0);
    min.makeFloor(currPos);
    max.makeFloor(currPos);
    maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());


    top -= this->charHeight * 2.0;
    left -= horiz_height  * this->charHeight * 2.0;

    // Bottom left (again)
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len/2.0);
    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.left;
    *pVert++ = uvRect.bottom;

    // Deal with bounds
    currPos = Ogre::Vector3(left, top, 0);
    min.makeFloor(currPos);
    max.makeFloor(currPos);
    maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());


    left += horiz_height  * this->charHeight * 2.0;

    // Bottom right
    if (this->horizAlign == MovableText::H_LEFT)
      *pVert++ = left;
    else
      *pVert++ = left - (len/2.0);
    *pVert++ = top;
    *pVert++ = 0;
    *pVert++ = uvRect.right;
    *pVert++ = uvRect.bottom;

    // Deal with bounds
    currPos = Ogre::Vector3(left, top, 0);
    min.makeFloor(currPos);
    max.makeFloor(currPos);
    maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

    //-------------------------------------------------------------------------


    // Go back up with top
    top += this->charHeight * 2.0;

    float currentWidth = (left + 1.0)/2.0;
    if (currentWidth > largestWidth)
    {
      largestWidth = currentWidth;
    }
  }

  // Unlock vertex buffer
  ptbuf->unlock();



  // update AABB/Sphere radius
  this->aabb->setMinimum(min);
  this->aabb->setMaximum(max);
  this->radius = Ogre::Math::Sqrt(maxSquaredRadius);

  if (this->updateColors)
    this->_updateColors();

  this->needUpdate = false;
}

//////////////////////////////////////////////////
void MovableText::_updateColors(void)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  Ogre::RGBA clr;
  Ogre::HardwareVertexBufferSharedPtr vbuf;
  Ogre::RGBA *pDest = NULL;
  unsigned int i;

  GZ_ASSERT(this->font, "font class member is null");
  GZ_ASSERT(!this->material.isNull(), "material class member is null");

  // Convert to system-specific
  Ogre::ColourValue cv(this->color.r, this->color.g,
                       this->color.b, this->color.a);
  Ogre::Root::getSingleton().convertColourValue(cv, &clr);

  vbuf = this->renderOp.vertexData->vertexBufferBinding->getBuffer(
         COLOUR_BINDING);

  pDest = static_cast<Ogre::RGBA*>(
      vbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));

  for (i = 0; i < this->renderOp.vertexData->vertexCount; ++i)
  {
    *pDest++ = clr;
  }

  vbuf->unlock();
  this->updateColors = false;
}

//////////////////////////////////////////////////
const Ogre::Quaternion & MovableText::getWorldOrientation(void) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  GZ_ASSERT(this->camera, "camera class member is null");
  return const_cast<Ogre::Quaternion&>(this->camera->getDerivedOrientation());
  // return mParentNode->_getDerivedOrientation();
}

//////////////////////////////////////////////////
const Ogre::Vector3 & MovableText::getWorldPosition(void) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  GZ_ASSERT(mParentNode, "mParentNode class member is null");
  return mParentNode->_getDerivedPosition();
}

//////////////////////////////////////////////////
const Ogre::AxisAlignedBox &MovableText::getBoundingBox(void) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return *this->aabb;
}

//////////////////////////////////////////////////
const Ogre::String &MovableText::getMovableType() const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  static Ogre::String movType = "MovableText";
  return movType;
}

//////////////////////////////////////////////////
void MovableText::getWorldTransforms(Ogre::Matrix4 * xform) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  if (this->isVisible() && this->camera)
  {
    Ogre::Matrix3 rot3x3, scale3x3 = Ogre::Matrix3::IDENTITY;

    // store rotation in a matrix
    this->camera->getDerivedOrientation().ToRotationMatrix(rot3x3);
    // mParentNode->_getDerivedOrientation().ToRotationMatrix(rot3x3);

    // parent node position
    Ogre::Vector3 ppos = mParentNode->_getDerivedPosition() +
                         Ogre::Vector3::UNIT_Z * this->baseline;

    // apply scale
    scale3x3[0][0] = mParentNode->_getDerivedScale().x / 2;
    scale3x3[1][1] = mParentNode->_getDerivedScale().y / 2;
    scale3x3[2][2] = mParentNode->_getDerivedScale().z / 2;

    // apply all transforms to xform
    *xform = (rot3x3 * scale3x3);
    xform->setTrans(ppos);
  }
}

//////////////////////////////////////////////////
float MovableText::getBoundingRadius() const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return this->radius;
}

//////////////////////////////////////////////////
float MovableText::getSquaredViewDepth(const Ogre::Camera * /*cam_*/) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return 0;
}

//////////////////////////////////////////////////
void MovableText::getRenderOperation(Ogre::RenderOperation & op)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);

  if (this->isVisible())
  {
    if (this->needUpdate)
      this->_setupGeometry();
    if (this->updateColors)
      this->_updateColors();
    op = this->renderOp;
  }
}

//////////////////////////////////////////////////
const Ogre::MaterialPtr &MovableText::getMaterial(void) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  GZ_ASSERT(!this->material.isNull(), "material class member is null");
  return this->material;
}

//////////////////////////////////////////////////
const Ogre::LightList &MovableText::getLights(void) const
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return this->lightList;
}

//////////////////////////////////////////////////
void MovableText::_notifyCurrentCamera(Ogre::Camera *cam)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  this->camera = cam;
}

//////////////////////////////////////////////////
void MovableText::_updateRenderQueue(Ogre::RenderQueue* queue)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  if (this->isVisible())
  {
    if (this->needUpdate)
      this->_setupGeometry();

    if (this->updateColors)
      this->_updateColors();

    queue->addRenderable(this, mRenderQueueID,
                         OGRE_RENDERABLE_DEFAULT_PRIORITY);
  }
}

//////////////////////////////////////////////////
void MovableText::visitRenderables(Ogre::Renderable::Visitor* /*visitor*/,
                                 bool /*debug*/)
{
  boost::recursive_mutex::scoped_lock lock(*this->mutex);
  return;
}
