/*
 * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup freestyle
 * \brief Fredo's stroke shaders
 */

#include "AdvancedStrokeShaders.h"
#include "StrokeIterators.h"

#include "../system/PseudoNoise.h"
#include "../system/RandGen.h"

namespace Freestyle {

/////////////////////////////////////////
//
//  CALLIGRAPHICS SHADER
//
/////////////////////////////////////////

CalligraphicShader::CalligraphicShader(real iMinThickness,
                                       real iMaxThickness,
                                       const Vec2f &iOrientation,
                                       bool clamp)
{
  _minThickness = iMinThickness;
  _maxThickness = iMaxThickness;
  _orientation = iOrientation;
  _orientation.normalize();
  _clamp = clamp;
}

int CalligraphicShader::shade(Stroke &ioStroke) const
{
  Interface0DIterator v;
  Functions0D::VertexOrientation2DF0D fun;
  StrokeVertex *sv;
  for (v = ioStroke.verticesBegin(); !v.isEnd(); ++v) {
    real thickness;
    if (fun(v) < 0) {
      return -1;
    }

    Vec2f vertexOri(fun.result);
    Vec2r ori2d(-vertexOri[1], vertexOri[0]);
    ori2d.normalizeSafe();
    real scal = ori2d * _orientation;
    sv = dynamic_cast<StrokeVertex *>(&(*v));
    if (_clamp && (scal < 0)) {
      scal = 0.0;
      sv->attribute().setColor(1, 1, 1);
    }
    else {
      scal = fabs(scal);
      sv->attribute().setColor(0, 0, 0);
    }
    thickness = _minThickness + scal * (_maxThickness - _minThickness);
    if (thickness < 0.0) {
      thickness = 0.0;
    }
    sv->attribute().setThickness(thickness / 2.0, thickness / 2.0);
  }

  return 0;
}

/////////////////////////////////////////
//
//  SPATIAL NOISE SHADER
//
/////////////////////////////////////////

static const unsigned NB_VALUE_NOISE = 512;

SpatialNoiseShader::SpatialNoiseShader(
    float iAmount, float ixScale, int nbOctave, bool smooth, bool pureRandom)
{
  _amount = iAmount;
  if (ixScale == 0) {
    _xScale = 0;
  }
  else {
    _xScale = 1.0 / ixScale / real(NB_VALUE_NOISE);
  }
  _nbOctave = nbOctave;
  _smooth = smooth;
  _pureRandom = pureRandom;
}

int SpatialNoiseShader::shade(Stroke &ioStroke) const
{
  Interface0DIterator v, v2;
  v = ioStroke.verticesBegin();
  Vec2r p(v->getProjectedX(), v->getProjectedY());
  v2 = v;
  ++v2;
  Vec2r p0(v2->getProjectedX(), v2->getProjectedY());
  p0 = p + 2 * (p - p0);
  StrokeVertex *sv;
  sv = dynamic_cast<StrokeVertex *>(&(*v));
  real initU = sv->strokeLength() * real(NB_VALUE_NOISE);
  if (_pureRandom) {
    initU += RandGen::drand48() * real(NB_VALUE_NOISE);
  }

  Functions0D::VertexOrientation2DF0D fun;
  while (!v.isEnd()) {
    sv = dynamic_cast<StrokeVertex *>(&(*v));
    Vec2r p(sv->getPoint());
    if (fun(v) < 0) {
      return -1;
    }
    Vec2r vertexOri(fun.result);
    Vec2r ori2d(vertexOri[0], vertexOri[1]);
    ori2d = Vec2r(p - p0);
    ori2d.normalizeSafe();

    PseudoNoise mynoise;
    real bruit;

    if (_smooth) {
      bruit = mynoise.turbulenceSmooth(_xScale * sv->curvilinearAbscissa() + initU, _nbOctave);
    }
    else {
      bruit = mynoise.turbulenceLinear(_xScale * sv->curvilinearAbscissa() + initU, _nbOctave);
    }

    Vec2r noise(-ori2d[1] * _amount * bruit, ori2d[0] * _amount * bruit);

    sv->setPoint(p[0] + noise[0], p[1] + noise[1]);
    p0 = p;

    ++v;
  }

  ioStroke.UpdateLength();

  return 0;
}

/////////////////////////////////////////
//
//  SMOOTHING SHADER
//
/////////////////////////////////////////

SmoothingShader::SmoothingShader(int iNbIteration,
                                 real iFactorPoint,
                                 real ifactorCurvature,
                                 real iFactorCurvatureDifference,
                                 real iAnisoPoint,
                                 real iAnisoNormal,
                                 real iAnisoCurvature,
                                 real iCarricatureFactor)
{
  _nbIterations = iNbIteration;
  _factorCurvature = ifactorCurvature;
  _factorCurvatureDifference = iFactorCurvatureDifference;
  _anisoNormal = iAnisoNormal;
  _anisoCurvature = iAnisoCurvature;
  _carricatureFactor = iCarricatureFactor;
  _factorPoint = iFactorPoint;
  _anisoPoint = iAnisoPoint;
}

int SmoothingShader::shade(Stroke &ioStroke) const
{
  // cerr << " Smoothing a stroke  " << endl;

  Smoother smoother(ioStroke);
  smoother.smooth(_nbIterations,
                  _factorPoint,
                  _factorCurvature,
                  _factorCurvatureDifference,
                  _anisoPoint,
                  _anisoNormal,
                  _anisoCurvature,
                  _carricatureFactor);
  return 0;
}

// SMOOTHER
////////////////////////////

Smoother::Smoother(Stroke &ioStroke)
{
  _stroke = &ioStroke;

  _nbVertices = ioStroke.vertices_size();
  _vertex = new Vec2r[_nbVertices];
  _curvature = new real[_nbVertices];
  _normal = new Vec2r[_nbVertices];
  StrokeInternal::StrokeVertexIterator v, vend;
  int i = 0;
  for (v = ioStroke.strokeVerticesBegin(), vend = ioStroke.strokeVerticesEnd(); v != vend;
       ++v, ++i) {
    _vertex[i] = (v)->getPoint();
  }
  Vec2r vec_tmp(_vertex[0] - _vertex[_nbVertices - 1]);
  _isClosedCurve = (vec_tmp.norm() < M_EPSILON);

  _safeTest = (_nbVertices > 4);
}

Smoother::~Smoother()
{
  delete[] _vertex;
  delete[] _curvature;
  delete[] _normal;
}

void Smoother::smooth(int nbIteration,
                      real iFactorPoint,
                      real ifactorCurvature,
                      real iFactorCurvatureDifference,
                      real iAnisoPoint,
                      real iAnisoNormal,
                      real iAnisoCurvature,
                      real iCarricatureFactor)
{
  _factorCurvature = ifactorCurvature;
  _factorCurvatureDifference = iFactorCurvatureDifference;
  _anisoNormal = iAnisoNormal;
  _anisoCurvature = iAnisoCurvature;
  _carricatureFactor = iCarricatureFactor;
  _factorPoint = iFactorPoint;
  _anisoPoint = iAnisoPoint;

  for (int i = 0; i < nbIteration; ++i) {
    iteration();
  }
  copyVertices();
}

static real edgeStopping(real x, real sigma)
{
  if (sigma == 0.0) {
    return 1.0;
  }
  return exp(-x * x / (sigma * sigma));
}

void Smoother::iteration()
{
  computeCurvature();
  for (int i = 1; i < (_nbVertices - 1); ++i) {
    real motionNormal = _factorCurvature * _curvature[i] *
                        edgeStopping(_curvature[i], _anisoNormal);

    real diffC1 = _curvature[i] - _curvature[i - 1];
    real diffC2 = _curvature[i] - _curvature[i + 1];
    real motionCurvature = edgeStopping(diffC1, _anisoCurvature) * diffC1 +
                           edgeStopping(diffC2, _anisoCurvature) *
                               diffC2;  //_factorCurvatureDifference;
    motionCurvature *= _factorCurvatureDifference;
    // motionCurvature = _factorCurvatureDifference * (diffC1 + diffC2);
    if (_safeTest) {
      _vertex[i] = Vec2r(_vertex[i] + (motionNormal + motionCurvature) * _normal[i]);
    }
    Vec2r v1(_vertex[i - 1] - _vertex[i]);
    Vec2r v2(_vertex[i + 1] - _vertex[i]);
    real d1 = v1.norm();
    real d2 = v2.norm();
    _vertex[i] = Vec2r(
        _vertex[i] + _factorPoint * edgeStopping(d2, _anisoPoint) * (_vertex[i - 1] - _vertex[i]) +
        _factorPoint * edgeStopping(d1, _anisoPoint) * (_vertex[i + 1] - _vertex[i]));
  }

  if (_isClosedCurve) {
    real motionNormal = _factorCurvature * _curvature[0] *
                        edgeStopping(_curvature[0], _anisoNormal);

    real diffC1 = _curvature[0] - _curvature[_nbVertices - 2];
    real diffC2 = _curvature[0] - _curvature[1];
    real motionCurvature = edgeStopping(diffC1, _anisoCurvature) * diffC1 +
                           edgeStopping(diffC2, _anisoCurvature) *
                               diffC2;  //_factorCurvatureDifference;
    motionCurvature *= _factorCurvatureDifference;
    // motionCurvature = _factorCurvatureDifference * (diffC1 + diffC2);
    _vertex[0] = Vec2r(_vertex[0] + (motionNormal + motionCurvature) * _normal[0]);
    _vertex[_nbVertices - 1] = _vertex[0];
  }
}

void Smoother::computeCurvature()
{
  int i;
  Vec2r BA, BC, normalCurvature;
  for (i = 1; i < (_nbVertices - 1); ++i) {
    BA = _vertex[i - 1] - _vertex[i];
    BC = _vertex[i + 1] - _vertex[i];
    real lba = BA.norm(), lbc = BC.norm();
    BA.normalizeSafe();
    BC.normalizeSafe();
    normalCurvature = BA + BC;

    _normal[i] = Vec2r(-(BC - BA)[1], (BC - BA)[0]);
    _normal[i].normalizeSafe();

    _curvature[i] = normalCurvature * _normal[i];
    if (lba + lbc > M_EPSILON) {
      _curvature[i] /= (0.5 * lba + lbc);
    }
  }
  _curvature[0] = _curvature[1];
  _curvature[_nbVertices - 1] = _curvature[_nbVertices - 2];
  Vec2r di(_vertex[1] - _vertex[0]);
  _normal[0] = Vec2r(-di[1], di[0]);
  _normal[0].normalizeSafe();
  di = _vertex[_nbVertices - 1] - _vertex[_nbVertices - 2];
  _normal[_nbVertices - 1] = Vec2r(-di[1], di[0]);
  _normal[_nbVertices - 1].normalizeSafe();

  if (_isClosedCurve) {
    BA = _vertex[_nbVertices - 2] - _vertex[0];
    BC = _vertex[1] - _vertex[0];
    real lba = BA.norm(), lbc = BC.norm();
    BA.normalizeSafe();
    BC.normalizeSafe();
    normalCurvature = BA + BC;

    _normal[i] = Vec2r(-(BC - BA)[1], (BC - BA)[0]);
    _normal[i].normalizeSafe();

    _curvature[i] = normalCurvature * _normal[i];
    if (lba + lbc > M_EPSILON) {
      _curvature[i] /= (0.5 * lba + lbc);
    }

    _normal[_nbVertices - 1] = _normal[0];
    _curvature[_nbVertices - 1] = _curvature[0];
  }
}

void Smoother::copyVertices()
{
  int i = 0;
  StrokeInternal::StrokeVertexIterator v, vend;
  for (v = _stroke->strokeVerticesBegin(), vend = _stroke->strokeVerticesEnd(); v != vend; ++v) {
    const Vec2r p0((v)->getPoint());
    const Vec2r p1(_vertex[i]);
    Vec2r p(p0 + _carricatureFactor * (p1 - p0));

    (v)->setPoint(p[0], p[1]);
    ++i;
  }
  _stroke->UpdateLength();
}

} /* namespace Freestyle */
