/* SPDX-FileCopyrightText: 2008-2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup freestyle
 * \brief Class gathering basic stroke shaders
 */

#include <fstream>

#include "AdvancedFunctions0D.h"
#include "AdvancedFunctions1D.h"
#include "BasicStrokeShaders.h"
#include "StrokeIO.h"
#include "StrokeIterators.h"
#include "StrokeRenderer.h"

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

#include "../view_map/Functions0D.h"
#include "../view_map/Functions1D.h"

#include "BKE_global.hh"

#include "BLI_sys_types.h"

#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"

namespace Freestyle::StrokeShaders {

//
//  Thickness modifiers
//
//////////////////////////////////////////////////////////

int ConstantThicknessShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v, vend;
  int i = 0;
  int size = stroke.strokeVerticesSize();
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    // XXX What's the use of i here? And is not the thickness always overridden by the last line of
    // the loop?
    if ((1 == i) || (size - 2 == i)) {
      v->attribute().setThickness(_thickness / 4.0, _thickness / 4.0);
    }
    if ((0 == i) || (size - 1 == i)) {
      v->attribute().setThickness(0, 0);
    }

    v->attribute().setThickness(_thickness / 2.0, _thickness / 2.0);
  }
  return 0;
}

int ConstantExternThicknessShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v, vend;
  int i = 0;
  int size = stroke.strokeVerticesSize();
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    // XXX What's the use of i here? And is not the thickness always overridden by the last line of
    // the loop?
    if ((1 == i) || (size - 2 == i)) {
      v->attribute().setThickness(_thickness / 2.0, 0);
    }
    if ((0 == i) || (size - 1 == i)) {
      v->attribute().setThickness(0, 0);
    }

    v->attribute().setThickness(_thickness, 0);
  }
  return 0;
}

int IncreasingThicknessShader::shade(Stroke &stroke) const
{
  int n = stroke.strokeVerticesSize() - 1, i;
  StrokeInternal::StrokeVertexIterator v, vend;
  for (i = 0, v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend;
       ++v, ++i)
  {
    float t;
    if (i < float(n) / 2.0f) {
      t = (1.0 - float(i) / float(n)) * _ThicknessMin + float(i) / float(n) * _ThicknessMax;
    }
    else {
      t = (1.0 - float(i) / float(n)) * _ThicknessMax + float(i) / float(n) * _ThicknessMin;
    }
    v->attribute().setThickness(t / 2.0, t / 2.0);
  }
  return 0;
}

int ConstrainedIncreasingThicknessShader::shade(Stroke &stroke) const
{
  float slength = stroke.getLength2D();
  float maxT = min(_ratio * slength, _ThicknessMax);
  int n = stroke.strokeVerticesSize() - 1, i;
  StrokeInternal::StrokeVertexIterator v, vend;
  for (i = 0, v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend;
       ++v, ++i)
  {
    // XXX Why not using an if/else here? Else, if last condition is true, everything else is
    // computed for nothing!
    float t;
    if (i < float(n) / 2.0f) {
      t = (1.0 - float(i) / float(n)) * _ThicknessMin + float(i) / float(n) * maxT;
    }
    else {
      t = (1.0 - float(i) / float(n)) * maxT + float(i) / float(n) * _ThicknessMin;
    }
    v->attribute().setThickness(t / 2.0, t / 2.0);
    if (i == n - 1) {
      v->attribute().setThickness(_ThicknessMin / 2.0, _ThicknessMin / 2.0);
    }
  }
  return 0;
}

int LengthDependingThicknessShader::shade(Stroke &stroke) const
{
  float step = (_maxThickness - _minThickness) / 3.0f;
  float l = stroke.getLength2D();
  float thickness = 0.0f;
  if (l > 300.0f) {
    thickness = _minThickness + 3.0f * step;
  }
  else if ((l < 300.0f) && (l > 100.0f)) {
    thickness = _minThickness + 2.0f * step;
  }
  else if ((l < 100.0f) && (l > 50.0f)) {
    thickness = _minThickness + 1.0f * step;
  }
  else {  // else if (l <  50.0f), tsst...
    thickness = _minThickness;
  }

  StrokeInternal::StrokeVertexIterator v, vend;
  int i = 0;
  int size = stroke.strokeVerticesSize();
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    // XXX What's the use of i here? And is not the thickness always overridden by the last line of
    // the loop?
    if ((1 == i) || (size - 2 == i)) {
      v->attribute().setThickness(thickness / 4.0, thickness / 4.0);
    }
    if ((0 == i) || (size - 1 == i)) {
      v->attribute().setThickness(0, 0);
    }

    v->attribute().setThickness(thickness / 2.0, thickness / 2.0);
  }
  return 0;
}

static const uint NB_VALUE_NOISE = 512;

ThicknessNoiseShader::ThicknessNoiseShader()
{
  _amplitude = 1.0f;
  _scale = 1.0f / 2.0f / float(NB_VALUE_NOISE);
}

ThicknessNoiseShader::ThicknessNoiseShader(float iAmplitude, float iPeriod)
{
  _amplitude = iAmplitude;
  _scale = 1.0f / iPeriod / float(NB_VALUE_NOISE);
}

int ThicknessNoiseShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v = stroke.strokeVerticesBegin(), vend;
  real initU1 = v->strokeLength() * real(NB_VALUE_NOISE) +
                RandGen::drand48() * real(NB_VALUE_NOISE);
  real initU2 = v->strokeLength() * real(NB_VALUE_NOISE) +
                RandGen::drand48() * real(NB_VALUE_NOISE);

  real bruit, bruit2;
  PseudoNoise mynoise, mynoise2;
  for (vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    bruit = mynoise.turbulenceSmooth(_scale * v->curvilinearAbscissa() + initU1,
                                     2);  // 2 : nbOctaves
    bruit2 = mynoise2.turbulenceSmooth(_scale * v->curvilinearAbscissa() + initU2,
                                       2);  // 2 : nbOctaves
    const float *originalThickness = v->attribute().getThickness();
    float r = bruit * _amplitude + originalThickness[0];
    float l = bruit2 * _amplitude + originalThickness[1];
    v->attribute().setThickness(r, l);
  }

  return 0;
}

//
//  Color shaders
//
///////////////////////////////////////////////////////////////////////////////

int ConstantColorShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v, vend;
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    v->attribute().setColor(_color[0], _color[1], _color[2]);
    v->attribute().setAlpha(_color[3]);
  }
  return 0;
}

int IncreasingColorShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v, vend;
  int n = stroke.strokeVerticesSize() - 1, yo;
  float newcolor[4];
  for (yo = 0, v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend;
       ++v, ++yo)
  {
    for (int i = 0; i < 4; ++i) {
      newcolor[i] = (1.0 - float(yo) / float(n)) * _colorMin[i] +
                    float(yo) / float(n) * _colorMax[i];
    }
    v->attribute().setColor(newcolor[0], newcolor[1], newcolor[2]);
    v->attribute().setAlpha(newcolor[3]);
  }
  return 0;
}

int MaterialColorShader::shade(Stroke &stroke) const
{
  Interface0DIterator v, vend;
  Functions0D::MaterialF0D fun;
  StrokeVertex *sv;
  for (v = stroke.verticesBegin(), vend = stroke.verticesEnd(); v != vend; ++v) {
    if (fun(v) < 0) {
      return -1;
    }
    const float *diffuse = fun.result.diffuse();
    sv = dynamic_cast<StrokeVertex *>(&(*v));
    sv->attribute().setColor(
        diffuse[0] * _coefficient, diffuse[1] * _coefficient, diffuse[2] * _coefficient);
    sv->attribute().setAlpha(diffuse[3]);
  }
  return 0;
}

ColorNoiseShader::ColorNoiseShader()
{
  _amplitude = 1.0f;
  _scale = 1.0f / 2.0f / float(NB_VALUE_NOISE);
}

ColorNoiseShader::ColorNoiseShader(float iAmplitude, float iPeriod)
{
  _amplitude = iAmplitude;
  _scale = 1.0f / iPeriod / float(NB_VALUE_NOISE);
}

int ColorNoiseShader::shade(Stroke &stroke) const
{
  StrokeInternal::StrokeVertexIterator v = stroke.strokeVerticesBegin(), vend;
  real initU = v->strokeLength() * real(NB_VALUE_NOISE) +
               RandGen::drand48() * real(NB_VALUE_NOISE);

  real bruit;
  PseudoNoise mynoise;
  for (vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    bruit = mynoise.turbulenceSmooth(_scale * v->curvilinearAbscissa() + initU,
                                     2);  // 2 : nbOctaves
    const float *originalColor = v->attribute().getColor();
    float r = bruit * _amplitude + originalColor[0];
    float g = bruit * _amplitude + originalColor[1];
    float b = bruit * _amplitude + originalColor[2];
    v->attribute().setColor(r, g, b);
  }
  return 0;
}

//
//  Texture Shaders
//
///////////////////////////////////////////////////////////////////////////////

int BlenderTextureShader::shade(Stroke &stroke) const
{
  if (_mtex) {
    return stroke.setMTex(_mtex);
  }
  if (_nodeTree) {
    stroke.setNodeTree(_nodeTree);
    return 0;
  }
  return -1;
}

int StrokeTextureStepShader::shade(Stroke &stroke) const
{
  stroke.setTextureStep(_step);
  return 0;
}

//
//  Geometry Shaders
//
///////////////////////////////////////////////////////////////////////////////

int BackboneStretcherShader::shade(Stroke &stroke) const
{
  float l = stroke.getLength2D();
  if (l <= 1.0e-6) {
    return 0;
  }

  StrokeInternal::StrokeVertexIterator v0 = stroke.strokeVerticesBegin();
  StrokeInternal::StrokeVertexIterator v1 = v0;
  ++v1;
  StrokeInternal::StrokeVertexIterator vn = stroke.strokeVerticesEnd();
  --vn;
  StrokeInternal::StrokeVertexIterator vn_1 = vn;
  --vn_1;

  Vec2d first((v0)->x(), (v0)->y());
  Vec2d last((vn)->x(), (vn)->y());

  Vec2d d1(first - Vec2d((v1)->x(), (v1)->y()));
  d1.normalize();
  Vec2d dn(last - Vec2d((vn_1)->x(), (vn_1)->y()));
  dn.normalize();

  Vec2d newFirst(first + _amount * d1);
  (v0)->setPoint(newFirst[0], newFirst[1]);
  Vec2d newLast(last + _amount * dn);
  (vn)->setPoint(newLast[0], newLast[1]);

  stroke.UpdateLength();
  return 0;
}

int SamplingShader::shade(Stroke &stroke) const
{
  stroke.Resample(_sampling);
  stroke.UpdateLength();
  return 0;
}

int ExternalContourStretcherShader::shade(Stroke &stroke) const
{
  // float l = stroke.getLength2D();
  Interface0DIterator it;
  Functions0D::Normal2DF0D fun;
  StrokeVertex *sv;
  for (it = stroke.verticesBegin(); !it.isEnd(); ++it) {
    if (fun(it) < 0) {
      return -1;
    }
    Vec2f n(fun.result);
    sv = dynamic_cast<StrokeVertex *>(&(*it));
    Vec2d newPoint(sv->x() + _amount * n.x(), sv->y() + _amount * n.y());
    sv->setPoint(newPoint[0], newPoint[1]);
  }
  stroke.UpdateLength();
  return 0;
}

//!! Bezier curve stroke shader
int BezierCurveShader::shade(Stroke &stroke) const
{
  if (stroke.strokeVerticesSize() < 4) {
    return 0;
  }

  // Build the Bezier curve from this set of data points:
  vector<Vec2d> data;
  StrokeInternal::StrokeVertexIterator v = stroke.strokeVerticesBegin(), vend;
  data.emplace_back(v->x(), v->y());  // first one
  StrokeInternal::StrokeVertexIterator previous = v;
  ++v;
  for (vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    if (!((fabs(v->x() - (previous)->x()) < M_EPSILON) &&
          (fabs(v->y() - (previous)->y()) < M_EPSILON)))
    {
      data.emplace_back(v->x(), v->y());
    }
    previous = v;
  }

  // here we build the bezier curve
  BezierCurve bcurve(data, _error);

  // bad performances are here !!! // FIXME
  vector<Vec2d> CurveVertices;
  vector<BezierCurveSegment *> &bsegments = bcurve.segments();
  vector<BezierCurveSegment *>::iterator s = bsegments.begin(), send;
  vector<Vec2d> &segmentsVertices = (*s)->vertices();
  vector<Vec2d>::iterator p, pend;
  // first point
  CurveVertices.push_back(segmentsVertices[0]);
  for (send = bsegments.end(); s != send; ++s) {
    segmentsVertices = (*s)->vertices();
    p = segmentsVertices.begin();
    ++p;
    for (pend = segmentsVertices.end(); p != pend; ++p) {
      CurveVertices.push_back(*p);
    }
  }

  // Re-sample the Stroke depending on the number of vertices of the bezier curve:
  int originalSize = CurveVertices.size();
#if 0
  float sampling = stroke.ComputeSampling(originalSize);
  stroke.Resample(sampling);
#endif
  stroke.Resample(originalSize);
  int newsize = stroke.strokeVerticesSize();
  int nExtraVertex = 0;
  if (newsize < originalSize) {
    cerr << "Warning: insufficient resampling" << endl;
  }
  else {
    nExtraVertex = newsize - originalSize;
    if (nExtraVertex != 0) {
      if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "Bezier Shader : Stroke " << stroke.getId() << " have not been resampled" << endl;
      }
    }
  }

  // assigns the new coordinates:
  p = CurveVertices.begin();
  vector<Vec2d>::iterator last = p;
  int n;
  StrokeInternal::StrokeVertexIterator it, itend;
  for (n = 0,
      it = stroke.strokeVerticesBegin(),
      itend = stroke.strokeVerticesEnd(),
      pend = CurveVertices.end();
       (it != itend) && (p != pend);
       ++it, ++p, ++n)
  {
    it->setX(p->x());
    it->setY(p->y());
    last = p;
  }
  stroke.UpdateLength();

  // Deal with extra vertices:
  if (nExtraVertex == 0) {
    return 0;
  }

  // nExtraVertex should stay unassigned
  vector<StrokeAttribute> attributes;
  vector<StrokeVertex *> verticesToRemove;
  for (int i = 0; i < nExtraVertex; ++i, ++it, ++n) {
    verticesToRemove.push_back(&(*it));
    if (it.isEnd()) {
      // XXX Shocking! :P Shouldn't we break in this case???
      if (G.debug & G_DEBUG_FREESTYLE) {
        cout << "messed up!" << endl;
      }
    }
  }
  for (it = stroke.strokeVerticesBegin(); it != itend; ++it) {
    attributes.push_back(it->attribute());
  }

  for (vector<StrokeVertex *>::iterator vr = verticesToRemove.begin(),
                                        vrend = verticesToRemove.end();
       vr != vrend;
       ++vr)
  {
    stroke.RemoveVertex(*vr);
  }

  vector<StrokeAttribute>::iterator a = attributes.begin(), aend = attributes.end();
  int index = 0;
  int index1 = int(floor(float(originalSize) / 2.0));
  int index2 = index1 + nExtraVertex;
  for (it = stroke.strokeVerticesBegin(), itend = stroke.strokeVerticesEnd();
       (it != itend) && (a != aend);
       ++it)
  {
    (it)->setAttribute(*a);
    if ((index <= index1) || (index > index2)) {
      ++a;
    }
    ++index;
  }
  return 0;
}

class CurvePiece {
 public:
  StrokeInternal::StrokeVertexIterator _begin;
  StrokeInternal::StrokeVertexIterator _last;
  Vec2d A;
  Vec2d B;
  int size;
  float _error;

  CurvePiece(StrokeInternal::StrokeVertexIterator b,
             StrokeInternal::StrokeVertexIterator l,
             int iSize)
  {
    _error = 0.0f;
    _begin = b;
    _last = l;
    A = Vec2d((_begin)->x(), (_begin)->y());
    B = Vec2d((_last)->x(), (_last)->y());
    size = iSize;
  }

  float error()
  {
    float maxE = 0.0f;
    for (StrokeInternal::StrokeVertexIterator it = _begin; it != _last; ++it) {
      Vec2d P(it->x(), it->y());
      float d = GeomUtils::distPointSegment(P, A, B);
      if (d > maxE) {
        maxE = d;
      }
    }
    _error = maxE;
    return maxE;
  }

  //! Subdivides the curve into two pieces.
  //  The first piece is this same object (modified)
  //  The second piece is returned by the method
  CurvePiece *subdivide()
  {
    StrokeInternal::StrokeVertexIterator it = _begin;
    int ns = size - 1;  // number of segments (ns > 1)
    int ns1 = ns / 2;
    int ns2 = ns - ns1;
    for (int i = 0; i < ns1; ++it, ++i) {
      /* pass */
    }

    CurvePiece *second = new CurvePiece(it, _last, ns2 + 1);
    size = ns1 + 1;
    _last = it;
    B = Vec2d((_last)->x(), (_last)->y());
    return second;
  }
};

int PolygonalizationShader::shade(Stroke &stroke) const
{
  vector<CurvePiece *> _pieces;
  vector<CurvePiece *> _results;
  vector<CurvePiece *>::iterator cp, cpend;

  // Compute first approx:
  StrokeInternal::StrokeVertexIterator a = stroke.strokeVerticesBegin();
  StrokeInternal::StrokeVertexIterator b = stroke.strokeVerticesEnd();
  --b;
  int size = stroke.strokeVerticesSize();

  CurvePiece *piece = new CurvePiece(a, b, size);
  _pieces.push_back(piece);

  while (!_pieces.empty()) {
    piece = _pieces.back();
    _pieces.pop_back();
    if (piece->size > 2 && piece->error() > _error) {
      CurvePiece *second = piece->subdivide();
      _pieces.push_back(second);
      _pieces.push_back(piece);
    }
    else {
      _results.push_back(piece);
    }
  }

  // actually modify the geometry for each piece:
  for (cp = _results.begin(), cpend = _results.end(); cp != cpend; ++cp) {
    a = (*cp)->_begin;
    b = (*cp)->_last;
    Vec2d u = (*cp)->B - (*cp)->A;
    Vec2d n(u[1], -u[0]);
    n.normalize();
    // Vec2d n(0, 0);
    float offset = ((*cp)->_error);
    StrokeInternal::StrokeVertexIterator v;
    for (v = a; v != b; ++v) {
      v->setPoint((*cp)->A.x() + v->u() * u.x() + n.x() * offset,
                  (*cp)->A.y() + v->u() * u.y() + n.y() * offset);
    }
#if 0
    u.normalize();
    (*a)->setPoint((*a)->x() - u.x() * 10, (*a)->y() - u.y() * 10);
#endif
  }
  stroke.UpdateLength();

  // delete stuff
  for (cp = _results.begin(), cpend = _results.end(); cp != cpend; ++cp) {
    delete (*cp);
  }
  _results.clear();
  return 0;
}

int GuidingLinesShader::shade(Stroke &stroke) const
{
  Functions1D::Normal2DF1D norm_fun;
  StrokeInternal::StrokeVertexIterator a = stroke.strokeVerticesBegin();
  StrokeInternal::StrokeVertexIterator b = stroke.strokeVerticesEnd();
  --b;
  int size = stroke.strokeVerticesSize();
  CurvePiece piece(a, b, size);

  Vec2d u = piece.B - piece.A;
  Vec2f n(u[1], -u[0]);
  n.normalize();
  if (norm_fun(stroke) < 0) {
    return -1;
  }
  Vec2f strokeN(norm_fun.result);
  if (n * strokeN < 0) {
    n[0] = -n[0];
    n[1] = -n[1];
  }
  float offset = piece.error() / 2.0f * _offset;
  StrokeInternal::StrokeVertexIterator v, vend;
  for (v = a, vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    v->setPoint(piece.A.x() + v->u() * u.x() + n.x() * offset,
                piece.A.y() + v->u() * u.y() + n.y() * offset);
  }
  stroke.UpdateLength();
  return 0;
}

/////////////////////////////////////////
//
//  Tip Remover
//
/////////////////////////////////////////

TipRemoverShader::TipRemoverShader(real tipLength)
{
  _tipLength = tipLength;
}

int TipRemoverShader::shade(Stroke &stroke) const
{
  int originalSize = stroke.strokeVerticesSize();

  if (originalSize < 4) {
    return 0;
  }

  StrokeInternal::StrokeVertexIterator v, vend;
  vector<StrokeVertex *> verticesToRemove;
  vector<StrokeAttribute> oldAttributes;
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd(); v != vend; ++v) {
    if ((v->curvilinearAbscissa() < _tipLength) ||
        (v->strokeLength() - v->curvilinearAbscissa() < _tipLength))
    {
      verticesToRemove.push_back(&(*v));
    }
    oldAttributes.push_back(v->attribute());
  }

  if (originalSize - verticesToRemove.size() < 2) {
    return 0;
  }

  vector<StrokeVertex *>::iterator sv, svend;
  for (sv = verticesToRemove.begin(), svend = verticesToRemove.end(); sv != svend; ++sv) {
    stroke.RemoveVertex(*sv);
  }

  // Resample so that our new stroke have the same number of vertices than before
  stroke.Resample(originalSize);

  if (int(stroke.strokeVerticesSize()) != originalSize) {  // soc
    cerr << "Warning: resampling problem" << endl;
  }

  // assign old attributes to new stroke vertices:
  vector<StrokeAttribute>::iterator a = oldAttributes.begin(), aend = oldAttributes.end();
  for (v = stroke.strokeVerticesBegin(), vend = stroke.strokeVerticesEnd();
       (v != vend) && (a != aend);
       ++v, ++a)
  {
    v->setAttribute(*a);
  }
  // we're done!
  return 0;
}

}  // namespace Freestyle::StrokeShaders
