/*
 * Copyright (C) 2015 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 <algorithm>
#include <tinyxml.h>
#include <utility>
#include <cmath>

#include <ignition/math/Angle.hh>
#include <ignition/math/Matrix3.hh>
#include <ignition/math/Vector2.hh>
#include <ignition/math/Vector3.hh>

#include <gazebo/common/Console.hh>
#include <gazebo/common/Assert.hh>

#include "SVGLoaderPrivate.hh"
#include "SVGLoader.hh"

using namespace gazebo;
using namespace common;


/////////////////////////////////////////////////
// This local helper function transforms a string to its lowecase equivalent
std::string lowercase(const std::string &_in)
{
  std::string out = _in;
  std::transform(out.begin(), out.end(), out.begin(), ::tolower);
  return out;
}

/////////////////////////////////////////////////
// This local helper function transforms a C string to its lowecase equivalent
std::string lowercase(const char *_in)
{
  std::string ins = _in;
  return lowercase(ins);
}

/////////////////////////////////////////////////
// Local helper function that splits a string according to the delimiting char
std::vector<std::string> &split(const std::string &_s,
                                char _delim,
                                std::vector<std::string> &_elems)
{
  std::stringstream ss(_s);
  std::string item;
  while (std::getline(ss, item, _delim))
  {
    _elems.push_back(item);
  }
  return _elems;
}

/////////////////////////////////////////////////
// This local helper function takes in a SVG transformation string
// and returns the corresponding transformation matrix
ignition::math::Matrix3d ParseTransformMatrixStr(
                                              const std::string &_transformStr)
{
  // check for transformation
  GZ_ASSERT(!_transformStr.empty(), "no data for ParseTransformMatrixStr");

  // _transfromStr should not have a closing paren and look like this
  // matrix(0,0.55669897,-0.55669897,0,194.55441,-149.50402
  // we're going to extract the transform type and numbers
  std::vector<std::string> tx;
  split(_transformStr, '(', tx);

  if (tx.size() < 2)
  {
    gzerr << "Invalid path transform: '" << &_transformStr << "'"
          << std::endl;
    return ignition::math::Matrix3d::Identity;
  }
  std::string transform = tx[0];
  std::vector<std::string> numbers;
  split(tx[1], ',', numbers);

  // how to unpack the values into 3x3 matrices
  // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
  if (transform.find("matrix") != std::string::npos)
  {
    if (numbers.size() != 6)
    {
      gzerr << "Unsupported matrix transform with "
            << numbers.size() << " parameters. Should be 6."
            << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double a = stod(numbers[0]);  // 00
    double b = stod(numbers[1]);  // 10
    double c = stod(numbers[2]);  // 01
    double d = stod(numbers[3]);  // 11
    double e = stod(numbers[4]);  // 02
    double f = stod(numbers[5]);  // 12
    ignition::math::Matrix3d m(a, c, e, b, d, f, 0, 0, 1);
    return m;
  }

  if (transform.find("skewX") != std::string::npos)
  {
    if (numbers.size() != 1)
    {
      gzerr << "Unsupported skewX transform. Needs 1 parameter only"
            << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double deg = stod(numbers[0]);
    ignition::math::Angle angle;
    angle.Degree(deg);
    // get the tangent of the angle
    double t = tan(angle.Radian());
    ignition::math::Matrix3d m(1, t, 0, 0, 1, 0, 0, 0, 1);
    return m;
  }

  if (transform.find("skewY") != std::string::npos)
  {
    if (numbers.size() != 1)
    {
      gzerr << "Unsupported skewY transform. Needs 1 parameter only"
            << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double deg = stod(numbers[0]);
    ignition::math::Angle angle;
    angle.Degree(deg);
    // get the tangent of the angle
    double t = tan(angle.Radian());
    ignition::math::Matrix3d m(1, 0, 0, t, 1, 0, 0, 0, 1);
    return m;
  }

  // scale(<x> [<y>])
  // if y is not provided, it is assumed to be equal to x.
  if (transform.find("scale") != std::string::npos)
  {
    if (numbers.size() == 0 || numbers.size() > 2)
    {
      gzerr << "Unsupported scale transform with more than 2 parameters"
            << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double x = stod(numbers[0]);
    double y = x;
    if (numbers.size() == 2)
    {
      y = stod(numbers[1]);
    }
    ignition::math::Matrix3d m(x, 0, 0, 0, y, 0, 0, 0, 1);
    return m;
  }
  // translate(<x> [<y>])
  // If y is not provided, it is assumed to be zero.
  if (transform.find("translate") != std::string::npos)
  {
    if (numbers.size() == 0 || numbers.size() > 2)
    {
      gzerr << "Unsupported translate transform with more than 2 parameters"
            << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double x = stod(numbers[0]);
    double y = 0;
    if (numbers.size() == 2)
    {
      y = stod(numbers[1]);
    }
    ignition::math::Matrix3d m(1, 0, x, 0, 1, y, 0, 0, 1);
    return m;
  }
  // rotate(<a> [<x> <y>]) angle in degrees, center x and y
  // if x, y are not supplied, rotation is about 0, 0
  if (transform.find("rotate") != std::string::npos)
  {
    if (numbers.size() ==0 || numbers.size() == 2 || numbers.size() > 3 )
    {
      gzerr << "Unsupported rotate transform. Only angle and optional x y"
            << " are supported" << std::endl;
      return ignition::math::Matrix3d::Identity;
    }
    double deg = stod(numbers[0]);
    ignition::math::Angle angle;
    angle.Degree(deg);
    double a = angle.Radian();
    double sina = sin(a);
    double cosa = cos(a);
    double x = 0;
    double y = 0;
    if (numbers.size() == 3)
    {
      x = stod(numbers[1]);
      y = stod(numbers[2]);
    }
    // we apply a translation to x, y, the rotation and the translation -x,-y
    ignition::math::Matrix3d transToXy(1, 0, x, 0, 1, y, 0, 0, 1);
    ignition::math::Matrix3d transFromXy(1, 0, -x, 0, 1, -y, 0, 0, 1);
    ignition::math::Matrix3d rotate(cosa, -sina, 0, sina, cosa, 0, 0, 0, 1);
    ignition::math::Matrix3d m = transToXy * rotate * transFromXy;
    return m;
  }
  // we have no business being here
  gzerr << "Unknown transformation: " << transform << std::endl;
  ignition::math::Matrix3d m = ignition::math::Matrix3d::Identity;
  return m;
}

/////////////////////////////////////////////////
// This local helper function interpolates a bezier curve at t (between 0 and 1)
ignition::math::Vector2d bezierInterpolate(double _t,
                                           const ignition::math::Vector2d &_p0,
                                           const ignition::math::Vector2d &_p1,
                                           const ignition::math::Vector2d &_p2,
                                           const ignition::math::Vector2d &_p3)
{
  double t_1 = 1.0 - _t;
  double t_1_2 = t_1 * t_1;
  double t_1_3 = t_1_2 * t_1;
  double t2 = _t * _t;
  double t3 = t2 * _t;

  ignition::math::Vector2d p;
  p.X(t_1_3 * _p0.X() + 3 * _t *  t_1_2 * _p1.X() + 3 * t2 * t_1 * _p2.X() +
      t3 * _p3.X());
  p.Y(t_1_3 * _p0.Y() + 3 * _t *  t_1_2 * _p1.Y() + 3 * t2 * t_1 * _p2.Y() +
      t3 * _p3.Y());
  return p;
}

/////////////////////////////////////////////////
// This helper function adds bezier interpolations to a list of points
void cubicBezier(const ignition::math::Vector2d &_p0,
                 const ignition::math::Vector2d &_p1,
                 const ignition::math::Vector2d &_p2,
                 const ignition::math::Vector2d &_p3,
                 double _step,
                 std::vector<ignition::math::Vector2d> &_points)
{
  // we don't start at t = 0, but t = step...
  // so we assume that the first point is there (from the last move)
  double t = _step;
  while (t < 1.0)
  {
    auto p = bezierInterpolate(t, _p0, _p1, _p2, _p3);
    _points.push_back(p);
    t += _step;
  }

  // however we close the loop with the last point (t = 1)
  _points.push_back(_p3);
}

/////////////////////////////////////////////////
// This helper function computes the square of a number
static double Sqr(float _x)
{
  return _x * _x;
}

/////////////////////////////////////////////////
// This helper function computes the angle between 2 vectors, using acos
static float VecAng(float _ux, float _uy, float _vx, float _vy)
{
  double ux = _ux;
  double uy = _uy;
  double vx = _vx;
  double vy = _vy;

  double uMag = sqrt(ux * ux + uy * uy);
  double vMag = sqrt(vx * vx + vy * vy);
  double r = (ux * vx + uy * vy) / (uMag * vMag);

  if (r < -1.0)
  {
    r = -1.0;
  }
  else if (r > 1.0)
  {
    r = 1.0;
  }

  double a = acos(r);
  if (ux * vy < uy * vx)
  {
    return -a;
  }
  else
  {
    return a;
  }
}

/////////////////////////////////////////////////
// This helper function adds arc interpolations to a list of points
void arcPath(const ignition::math::Vector2d &_p0,
             const double _rx,
             const double _ry,
             const double _rotxDeg,
             const size_t _largeArc,
             const size_t _sweepDirection,
             const ignition::math::Vector2d &_pEnd,
             const double _step,
             std::vector<ignition::math::Vector2d> &_points)
{
  // Ported from canvg (https://code.google.com/p/canvg/)
  double rx = _rx;
  double ry = _ry;
  double rotx = _rotxDeg / 180.0 * M_PI;

  double x1, y1, x2, y2, cx, cy, dx, dy, d;
  double x1p, y1p, cxp, cyp, s, sa, sb;
  double ux, uy, vx, vy, a1, da;
  double px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
  double sinrx, cosrx;
  double hda, kappa;

  x1 = _p0.X();
  y1 = _p0.Y();
  x2 = _pEnd.X();
  y2 = _pEnd.Y();

  dx = x1 - x2;
  dy = y1 - y2;
  d = sqrt(dx*dx + dy*dy);
  if (d < 1e-6 || rx < 1e-6 || ry < 1e-6)
  {
    // The arc degenerates to a line
    _points.push_back(_pEnd);
    return;
  }

  sinrx = sin(rotx);
  cosrx = cos(rotx);

  // Convert to center point parameterization.
  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  // 1) Compute x1', y1'
  x1p = cosrx * dx / 2.0 + sinrx * dy / 2.0;
  y1p = -sinrx * dx / 2.0 + cosrx * dy / 2.0;
  d = Sqr(x1p) / Sqr(rx) + Sqr(y1p) / Sqr(ry);
  if (d > 1)
  {
    d = sqrt(d);
    rx *= d;
    ry *= d;
  }
  // 2) Compute cx', cy'
  s = 0.0;
  sa = Sqr(rx) * Sqr(ry) - Sqr(rx) * Sqr(y1p) - Sqr(ry) * Sqr(x1p);
  sb = Sqr(rx) * Sqr(y1p) + Sqr(ry) * Sqr(x1p);
  if (sa < 0.0)
    sa = 0.0;
  if (sb > 0.0)
    s = sqrt(sa / sb);

  if (_largeArc == _sweepDirection)
  {
    s = -s;
  }

  cxp = s * rx * y1p / ry;
  cyp = s * -ry * x1p / rx;

  // 3) Compute cx,cy from cx',cy'
  cx = (x1 + x2) / 2.0 + cosrx * cxp - sinrx * cyp;
  cy = (y1 + y2) / 2.0 + sinrx * cxp + cosrx * cyp;

  // 4) Calculate theta1, and delta theta.
  ux = (x1p - cxp) / rx;
  uy = (y1p - cyp) / ry;
  vx = (-x1p - cxp) / rx;
  vy = (-y1p - cyp) / ry;
  // initial angle
  a1 = VecAng(1.0, 0.0, ux, uy);
  // delta angle
  da = VecAng(ux, uy, vx, vy);

  if (_largeArc)
  {
    // Choose large arc
    if (da > 0.0)
      da = da - 2 * M_PI;
    else
      da = 2 * M_PI + da;
  }

  // rounding errors for half circles
  if (M_PI - fabs(da) < 0.001)
  {
    if (_sweepDirection)
      da = M_PI;
    else
      da = -M_PI;
  }

  // Approximate the arc using cubic spline segments.
  t[0] = cosrx;
  t[1] = sinrx;
  t[2] = -sinrx;
  t[3] = cosrx;
  t[4] = cx;
  t[5] = cy;

  // Split arc into max 90 degree segments.
  // The loop assumes an iteration per end point
  // (including start and end), this +1.
  size_t ndivs = static_cast<int>(fabs(da) / (M_PI * 0.5) + 1.0);
  hda = (da / ndivs) / 2.0;
  kappa = fabs(4.0 / 3.0 * (1.0 - cos(hda)) / sin(hda));
  if (da < 0.0)
    kappa = -kappa;

  for (size_t i = 0; i <= ndivs; ++i)
  {
    double x, y, tanx, tany, a;
    a = a1 + da * (1.0 * i /ndivs);
    dx = cos(a);
    dy = sin(a);
    // position  xform point
    double pox = dx * rx;
    double poy = dy * ry;
    x = pox * t[0] + poy * t[2] + t[4];
    y = pox * t[1] + poy * t[3] + t[5];
    // tangent  xform vec
    double tx = -dy * rx * kappa;
    double ty = dx * ry * kappa;
    tanx = tx * t[0] + ty * t[2];
    tany = tx * t[1] + ty * t[3];

    if (i > 0)
    {
      ignition::math::Vector2d p0(px, py);
      ignition::math::Vector2d p1(px + ptanx, py + ptany);
      ignition::math::Vector2d p2(x - tanx, y - tany);
      ignition::math::Vector2d p3(x, y);
      cubicBezier(p0, p1, p2, p3, _step, _points);
    }
    px = x;
    py = y;
    ptanx = tanx;
    ptany = tany;
  }
}

/////////////////////////////////////////////////
SvgError::SvgError(const std::string &_what)
  : std::runtime_error(_what)
{
}

/////////////////////////////////////////////////
ignition::math::Vector2d SVGLoader::SubpathToPolyline(
                            const std::vector<SVGCommand> &_subpath,
                            ignition::math::Vector2d _last,
                            std::vector<ignition::math::Vector2d> &_polyline)
{
  GZ_ASSERT(_polyline.size() == 0, "polyline not empty");
  for (SVGCommand cmd: _subpath)
  {
    size_t i = 0;
    size_t count = cmd.numbers.size();

    switch (cmd.cmd)
    {
      case 'm':
      case 'l':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(cmd.numbers[i+0]);
          p.Y(cmd.numbers[i+1]);
          // m and l cmds are relative to the last point
          p.X() += _last.X();
          p.Y() += _last.Y();
          _polyline.push_back(p);
          _last = p;
          i += 2;
        }
        break;
      case 'M':
      case 'L':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(cmd.numbers[i+0]);
          p.Y(cmd.numbers[i+1]);
          _polyline.push_back(p);
          _last = p;
          i += 2;
        }
        break;
      case 'h':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(cmd.numbers[i+0]);
          p.Y(0.0);
          p.X() += _last.X();
          p.Y() = _last.Y();
          _polyline.push_back(p);
          _last = p;
          i += 1;
        }
        break;
      case 'H':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(cmd.numbers[i+0]);
          p.Y(_last.Y());
          _polyline.push_back(p);
          _last = p;
          i += 1;
        }
        break;
      case 'v':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(0.0);
          p.Y(cmd.numbers[i+0]);
          p.X() = _last.X();
          p.Y() += _last.Y();
          _polyline.push_back(p);
          _last = p;
          i += 1;
        }
        break;
      case 'V':
        while (i < count)
        {
          ignition::math::Vector2d p;
          p.X(_last.X());
          p.Y(cmd.numbers[i+0]);
          _polyline.push_back(p);
          _last = p;
          i += 1;
        }
        break;
      case 'C':
        while (i < count)
        {
          ignition::math::Vector2d p0 = _last;
          ignition::math::Vector2d p1, p2, p3;
          p1.X(cmd.numbers[i+0]);
          p1.Y(cmd.numbers[i+1]);
          p2.X(cmd.numbers[i+2]);
          p2.Y(cmd.numbers[i+3]);
          p3.X(cmd.numbers[i+4]);
          p3.Y(cmd.numbers[i+5]);
          cubicBezier(p0, p1, p2, p3, this->dataPtr->resolution, _polyline);
          _last = p3;
          i += 6;
        }
        break;
      case 'c':
        while (i < count)
        {
          ignition::math::Vector2d p0 = _last;
          ignition::math::Vector2d p1, p2, p3;
          p1.X(cmd.numbers[i+0] + _last.X());
          p1.Y(cmd.numbers[i+1] + _last.Y());
          p2.X(cmd.numbers[i+2] + _last.X());
          p2.Y(cmd.numbers[i+3] + _last.Y());
          p3.X(cmd.numbers[i+4] + _last.X());
          p3.Y(cmd.numbers[i+5] + _last.Y());
          cubicBezier(p0, p1, p2, p3, this->dataPtr->resolution, _polyline);
          _last = p3;
          i += 6;
        }
        break;
      case 'A':
        while (i < count)
        {
          ignition::math::Vector2d p0 = _last;
          double rx = cmd.numbers[i+0];
          double ry = cmd.numbers[i+1];
          double xRot = cmd.numbers[i+2];
          unsigned int arc(cmd.numbers[i+3]);
          unsigned int sweep(cmd.numbers[i+4]);
          ignition::math::Vector2d pEnd;
          pEnd.X(cmd.numbers[i+5]);
          pEnd.Y(cmd.numbers[i+6]);
          arcPath(p0, rx, ry, xRot, arc, sweep, pEnd,
                  this->dataPtr->resolution, _polyline);
          _last = pEnd;
          i += 7;
        }
        break;
      case 'a':
        while (i < count)
        {
          ignition::math::Vector2d p0 = _last;
          double rx = cmd.numbers[i+0];
          double ry = cmd.numbers[i+1];
          double xRot = cmd.numbers[i+2];
          unsigned int arc(cmd.numbers[i+3]);
          unsigned int sweep(cmd.numbers[i+4]);
          ignition::math::Vector2d pEnd;
          pEnd.X(cmd.numbers[i+5] + _last.X());
          pEnd.Y(cmd.numbers[i+6] + _last.Y());
          arcPath(p0, rx, ry, xRot, arc, sweep, pEnd,
                  this->dataPtr->resolution, _polyline);
          _last = pEnd;
          i += 7;
        }
      // Z and z indicate closed path.
      // just add the first point to the list
      case 'Z':
      case 'z':
        {
          auto &p = _polyline.front();
          if (_polyline.back().Distance(p) > 1e-5)
          {
            gzerr << "Zz" << _polyline.back().Distance(p) << std::endl;
            _polyline.push_back(p);
          }
          break;
        }
      default:
        gzerr << "Unexpected SVGCommand value: " << cmd.cmd << std::endl;
    }
  }
  return _last;
}

/////////////////////////////////////////////////
SVGLoader::SVGLoader(unsigned int _samples)
{
  this->dataPtr = new SVGLoaderPrivate();
  if (_samples == 0)
  {
    std::string m("The number of samples cannot be 0");
    SvgError e(m);
    throw e;
  }
  this->dataPtr->resolution = 1.0/_samples;
}

/////////////////////////////////////////////////
SVGLoader::~SVGLoader()
{
  delete(this->dataPtr);
}

/////////////////////////////////////////////////
void SVGLoader::SplitSubpaths(const std::vector<SVGCommand> &_cmds,
                              std::vector< std::vector<SVGCommand> > &_subpaths)
{
  if (_cmds.empty())
  {
    std::ostringstream os;
    os << "SVGPath has no commands";
    SvgError x(os.str());
    throw x;
  }

  for (SVGCommand cmd: _cmds)
  {
    if (tolower(cmd.cmd) == 'm')
    {
      // the path contains a subpath
      std::vector<SVGCommand> sub;
      _subpaths.push_back(sub);
    }
    // get a reference to the latest subpath
    std::vector<SVGCommand> &subpath = _subpaths.back();
    // give the cmd to the latest
    subpath.push_back(cmd);
  }
}

/////////////////////////////////////////////////
void SVGLoader::ExpandCommands(
                  const std::vector< std::vector<SVGCommand> > &_subpaths,
                  SVGPath &_path)
{
  for (std::vector<SVGCommand> compressedSubpath :_subpaths)
  {
    // add new subpath
    _path.subpaths.push_back(std::vector<SVGCommand>());
    // get a reference
    std::vector<SVGCommand> &subpath = _path.subpaths.back();
    // copy the cmds with repeating commands, grouping the numbers
    for (SVGCommand xCmd : compressedSubpath)
    {
      unsigned int numberCount = 0;
      if (tolower(xCmd.cmd) == 'a')
        numberCount = 7;
      if (tolower(xCmd.cmd) == 'c')
        numberCount = 6;
      if (tolower(xCmd.cmd) == 'm')
        numberCount = 2;
      if (tolower(xCmd.cmd) == 'l')
        numberCount = 2;
      if (tolower(xCmd.cmd) == 'v')
        numberCount = 1;
      if (tolower(xCmd.cmd) == 'h')
        numberCount = 1;
      if (tolower(xCmd.cmd) == 'z')
        subpath.push_back(xCmd);
      // group numbers together and repeat the command
      // for each group
      unsigned int n = 0;
      size_t size = xCmd.numbers.size();
      while (n < size)
      {
        subpath.push_back(SVGCommand());
        SVGCommand &cmd = subpath.back();
        cmd.cmd = xCmd.cmd;
        for (size_t i = 0; i < numberCount; ++i)
        {
          cmd.numbers.push_back(xCmd.numbers[i+n]);
        }
        n += numberCount;
      }
    }
  }
}

/////////////////////////////////////////////////
void SVGLoader::GetPathCommands(const std::vector<std::string> &_tokens,
                                  SVGPath &_path)
{
  std::vector <SVGCommand> cmds;
  std::string lookup = "aAcCmMqQlLvVhHzZ";
  char lastCmd = 'x';
  std::vector<double> numbers;

  for (std::string token: _tokens)
  {
    // new command?
    if (lookup.find(token[0]) == std::string::npos)
    {
      // its just numbers
      std::vector<std::string> numberStrs;
      split(token, ',', numberStrs);
      for (std::string numberStr : numberStrs)
      {
        double f = atof(numberStr.c_str());
        numbers.push_back(f);
       }
     }
     else
     {
      if (lastCmd != 'x')
      {
        SVGCommand c;
        c.cmd = lastCmd;
        c.numbers = numbers;
        cmds.push_back(c);
       }
       // its new command
       lastCmd = token[0];
       numbers.resize(0);
     }
  }
  // the last command
  if (lastCmd != 'x')
  {
    SVGCommand c;
    c.cmd = lastCmd;
    c.numbers = numbers;
    cmds.push_back(c);
  }
  // split the commands into sub_paths
  std::vector< std::vector< SVGCommand> > subpaths;
  this->SplitSubpaths(cmds, subpaths);
  this->ExpandCommands(subpaths, _path);
  // the starting point for the subpath
  // it is the end point of the previous one
  ignition::math::Vector2d p;
  for (std::vector<SVGCommand> subpath : subpaths)
  {
    _path.polylines.push_back(std::vector<ignition::math::Vector2d>());
    std::vector<ignition::math::Vector2d> &polyline = _path.polylines.back();
    p = this->SubpathToPolyline(subpath, p, polyline);
  }
  // if necessary, apply transform to p and polyline
  if (_path.transform != ignition::math::Matrix3d::Identity)
  {
    // we need to transform all the points in the path
    for (auto &polyline : _path.polylines)
    {
      for (auto  &polyPoint : polyline)
      {
        // make a 3d vector form the 2d point
        ignition::math::Vector3d point3(polyPoint.X(), polyPoint.Y(), 1);
        // matrix multiply to get the new point, then save new coords in place
        auto transformed = _path.transform * point3;
        polyPoint.X(transformed.X());
        polyPoint.Y(transformed.Y());
      }
    }
  }
}

/////////////////////////////////////////////////
void SVGLoader::GetPathAttribs(TiXmlElement *_pElement, SVGPath &_path)
{
  GZ_ASSERT(_pElement, "empty XML element where a path was expected");
  _path.transform = ignition::math::Matrix3d::Identity;
  TiXmlAttribute *pAttrib = _pElement->FirstAttribute();

  // this attribute contains a list of coordinates
  std::vector<std::string> tokens;
  while (pAttrib)
  {
    std::string name = lowercase(pAttrib->Name());
    std::string value = pAttrib->Value();
    if (name == "style")
    {
      _path.style = value;
    }
    else if (name == "id")
    {
      _path.id = value;
    }
    else if (name == "transform")
    {
      _path.transform = ParseTransformMatrixStr(value);
    }
    else if (name == "d")
    {
      // load in the path parameters
      split(value, ' ', tokens);
    }
    else
    {
      gzwarn << "Ignoring attribute \"" << name  << "\" in path"  << std::endl;
    }
    pAttrib = pAttrib->Next();
  }
  // Now that all attributes are loaded, we can compute the values
  this->GetPathCommands(tokens, _path);
}

/////////////////////////////////////////////////
void SVGLoader::GetSvgPaths(TiXmlNode *_pParent, std::vector<SVGPath> &_paths)
{
  if (!_pParent)
    return;

  TiXmlNode *pChild;
  int t = _pParent->Type();
  std::string name;
  if ( t == TiXmlNode::TINYXML_ELEMENT)
  {
    name = lowercase(_pParent->Value());
    if (name == "path")
    {
      TiXmlElement *element = _pParent->ToElement();
      SVGPath p;
      this->GetPathAttribs(element, p);
      _paths.push_back(p);
    }
    // skip defs node that can contain path
    // elements that are not actual paths.
    if (name == "defs")
    {
      return;
    }
  }

  for (pChild = _pParent->FirstChild();
       pChild != 0;
       pChild = pChild->NextSibling())
  {
    this->GetSvgPaths(pChild, _paths);
  }
}

/////////////////////////////////////////////////
bool SVGLoader::Parse(const std::string &_filename,
    std::vector<SVGPath> &_paths)
{
  try
  {
    // load the named file and dump its structure to STDOUT
    TiXmlDocument doc(_filename.c_str());
    if (!doc.LoadFile())
    {
      std::ostringstream os;
      gzerr << "Failed to load file " <<  _filename << std::endl;
      gzerr << os.str() << std::endl;
      return false;
    }
    this->GetSvgPaths(&doc, _paths);
    return true;
  }
  catch(SvgError &e)
  {
    gzerr << e.what() << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
void SVGLoader::DumpPaths(const std::vector<SVGPath> &_paths,
                          std::ostream &_out) const
{
  // this prints an html document that allows to debug
  // SVG parsing issues. The points are generated in
  // a loop between the header and footer.
  std::string header = R"***(
<!DOCTYPE html>
<html>

<script type="text/javascript">

)***";
  std::string footer = R"***(
</script>

<script>

var x0 = 0;
var y0 = 0;
var scale = 1.;

function xx(x)
{
  var r = x0 + scale * x;
  return r;
}

function yy(y)
{
  var r =  - (y0 + scale * (-y) );
  return r;
}

function drawPoint(ctx, x, y)
{
  ctx.beginPath();
  ctx.arc(x, y, 5, 0, 2 * Math.PI, true);
  ctx.strokeStyle= style;
  ctx.stroke();
}

function drawPath(ctx, path, style, x0, y0, scale, showCtrlPoints )
{
  console.log('drawPath ' + path.name);

  ctx.beginPath();
  for (var j = 0; j <  path.subpaths.length; ++j)
  {
    var points = path.subpaths[j];
    console.log(points.length + ' points in subpath, (' + style + ')');
    if (points.length < 2)
    {
      console.log("not enough points in subpath " + j);
      return;
    }
    ctx.moveTo(xx(points[0][0]), yy(points[0][1]));
    for (var i = 1; i < points.length; ++i)
    {
      var x= xx(points[i][0]);
      var y= yy(points[i][1]);
      ctx.lineTo(x, y);
    }
    ctx.strokeStyle= style;
    ctx.stroke();

    // draw points
    if (showCtrlPoints)
    {
      var styles = ["black", "orange", "grey"];
      for (var i = 0; i < points.length; ++i)
      {
        var x= xx(points[i][0]);
        var y= yy(points[i][1]);
        var m = " [" + points[i][0] + ", " + points[i][1];
        m += "]  [" + x + ", " + y + "]";
        console.log(m);
        ctx.beginPath();
        if (i == 0)
        {
          ctx.arc(x, y, 4, 0, 2 * Math.PI, true);
          ctx.strokeStyle = "red";
          ctx.fill();
        }
        else if (i == 1)
        {
          ctx.arc(x, y, 2, 0, 2 * Math.PI, true);
          ctx.strokeStyle= "red";
        }
        else
        {
          ctx.arc(x, y, 2, 0, 2 * Math.PI, true);
          ctx.strokeStyle= styles[i % styles.length ];
        }
        ctx.stroke();
       }
    }
  }
}


function draw(showCtrlPoints)
{
  var canvas = document.getElementById("myCanvas");
  var ctx = canvas.getContext("2d");
  var styles = ["red", "green", "blue"];

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  x0 = Number(document.getElementsByName("xoff_in")[0].value);
  y0 = Number(document.getElementsByName("yoff_in")[0].value);
  scale = Number(document.getElementsByName("scale_in")[0].value);

  for (var i =0; i < svg.length; ++i)
  {
    var path = svg[i];
    console.log("path: " + path.name);
    drawPath(ctx, path, styles[i%3], x0, y0, scale, showCtrlPoints);
  }
}

  console.log("number of paths: " + svg.length);

  document.addEventListener("DOMContentLoaded", function(event)
  {
    draw();
  });

</script>


<body>

  <div>

  Xoff: <input type="text" name="xoff_in" value="0"><br>
  Yoff: <input type="text" name="yoff_in" value="0"><br>
  Scale: <input type="text" name="scale_in" value="1.0"><br>

  <button onclick="draw(true);">Draw</button>
  </div>

  <canvas
    id="myCanvas"
    width="1024"
    height="768"
    style="border:1px solid #d3d3d3;">
    Your browser does not support the canvas element.
  </canvas>

</body>
</html>

)***";

  _out << header << std::endl;
  _out << "var svg = [];" << std::endl;
  for (SVGPath path : _paths)
  {
    _out << "svg.push({name:\"" << path.id;
    _out <<  "\", subpaths:[], style: \"";
    _out << path.style << "\"}); " << std::endl;
    _out << "svg[svg.length-1].subpaths = [";
    char psep = ' ';

    for (unsigned int i = 0; i < path.polylines.size(); ++i)
    {
      std::vector<ignition::math::Vector2d> poly = path.polylines[i];
      _out << psep <<  "[" << std::endl;
      psep = ',';
      char sep = ' ';
      for (ignition::math::Vector2d p : poly)
      {
        _out << " " << sep << " [" <<  p.X() << ", "
             << p.Y() << "]" <<std::endl;
        sep = ',';
      }
      _out << " ] " << std::endl;
    }
    _out << "];" << std::endl;
    _out << "\n\n";
  }
  _out << footer << std::endl;
}

/////////////////////////////////////////////////
bool Vector2dCompare(const ignition::math::Vector2d &_a,
                     const ignition::math::Vector2d &_b,
                     double _tol)
{
  double x = _a.X() - _b.X();
  double y = _a.Y() - _b.Y();
  // is squared distance smaller than squared tolerance?
  return (x*x + y*y < _tol * _tol);
}

/////////////////////////////////////////////////
void SVGLoader::PathsToClosedPolylines(
    const std::vector<common::SVGPath> &_paths,
    double _tol,
    std::vector< std::vector<ignition::math::Vector2d> > &_closedPolys,
    std::vector< std::vector<ignition::math::Vector2d> > &_openPolys)
{
  // first we extract all polyline into a vector of line segments
  std::list<std::pair<ignition::math::Vector2d,
    ignition::math::Vector2d> > segments;

  for (auto const &path : _paths)
  {
    for (auto const &poly : path.polylines)
    {
      ignition::math::Vector2d startPoint = poly[0];
      for (unsigned int i =1; i < poly.size(); ++i)
      {
        const ignition::math::Vector2d &endPoint = poly[i];
        double length = endPoint.Distance(startPoint);
        if (length < _tol)
        {
          gzmsg << "Ignoring short segment (length: "
                << length << ")" <<std::endl;
        }
        else
        {
          segments.push_back(std::make_pair(startPoint, endPoint));
          startPoint = endPoint;
        }
      }
    }
  }

  // then we remove segments until there are none left
  while (!segments.empty())
  {
    // start a new polyline, made from the 2 points of
    // the next available segment.
    std::vector<ignition::math::Vector2d> polyline;
    auto &s = segments.front();
    polyline.push_back(s.first);
    polyline.push_back(s.second);
    // remove the segment from the list
    segments.pop_front();
    // this flag will be false if the polyline has no
    // new segment
    bool segmentFound = true;
    // this flag is true when the polyline is closed
    bool loopClosed = false;
    while (segmentFound && !loopClosed)
    {
      // find the segment in the polyline
      segmentFound = false;
      for (auto it = segments.begin(); it != segments.end(); ++it)
      {
        auto seg = *it;
        ignition::math::Vector2d nextPoint;
        if (Vector2dCompare(polyline.back(), seg.first, _tol))
        {
          nextPoint = seg.second;
          segmentFound = true;
        }
        if (Vector2dCompare(polyline.back(), seg.second, _tol))
        {
          nextPoint = seg.first;
          segmentFound = true;
        }
        if (segmentFound)
        {
          // remove the segment from the list of all remaining segments
          segments.erase(it);
          // add the new point to the polyline
          polyline.push_back(nextPoint);
          // verify if the polyline is closed
          if (Vector2dCompare(nextPoint, polyline[0], _tol))
          {
            // the loop is closed, we don't need another segment
            loopClosed = true;
          }
          // the segment has been found
          // get out of the for loop.
          break;
        }
      }
    }
    // the new polyline is complete
    if (loopClosed)
    {
      _closedPolys.push_back(polyline);
    }
    else
    {
      gzmsg << "Line segments that are not part of a closed paths have"
         << " been found with the current minimum distance of " << _tol
         << " between 2 points."  << std::endl << std::endl;
      _openPolys.push_back(polyline);
    }
  }
}
