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

#pragma once

/** \file
 * \ingroup freestyle
 * \brief Classes to define a stroke
 */

#include <map>
#include <vector>

#include "Curve.h"

#include "../view_map/Interface1D.h"
#include "../view_map/Silhouette.h"

#include "../system/FreestyleConfig.h"
#include "../system/StringUtils.h"

#include "MEM_guardedalloc.h"

extern "C" {
struct MTex;
struct bNodeTree;
}

#ifndef MAX_MTEX
#  define MAX_MTEX 18
#endif

namespace Freestyle {

//
//  StrokeAttribute
//
////////////////////////////////////////////////////////

/** Class to define an attribute associated to a Stroke Vertex.
 *  This attribute stores the color, alpha and thickness values for a Stroke Vertex.
 */
class StrokeAttribute {
 public:
  /** default constructor */
  StrokeAttribute();

  /** Copy constructor */
  StrokeAttribute(const StrokeAttribute &iBrother);

  /** Builds a stroke vertex attribute from a set of parameters.
   *    \param iRColor:
   *      The Red Component value.
   *    \param iGColor:
   *      The Green Component value.
   *    \param iBColor:
   *      The Blue Component value.
   *    \param iAlpha:
   *      The transparency value
   *    \param iRThickness:
   *      The thickness of the stroke on the right
   *    \param iLThickness:
   *      The Thickness of the stroke on the left
   */
  StrokeAttribute(float iRColor,
                  float iGColor,
                  float iBColor,
                  float iAlpha,
                  float iRThickness,
                  float iLThickness);

  /** Interpolation constructor.
   *  Builds a StrokeAttribute from two StrokeAttributes and an interpolation parameter.
   *  \param a1:
   *    The first Attribute.
   *  \param a2:
   *    The second parameter.
   *  \param t:
   *    The interpolation parameter.
   */
  StrokeAttribute(const StrokeAttribute &a1, const StrokeAttribute &a2, float t);

  /** destructor */
  virtual ~StrokeAttribute();

  /* operators */
  /** operator = */
  StrokeAttribute &operator=(const StrokeAttribute &iBrother);

  /* accessors */
  /** Returns the attribute's color.
   *  \return The array of 3 floats containing the R,G,B values of the attribute's color.
   */
  inline const float *getColor() const
  {
    return _color;
  }

  /** Returns the R color component. */
  inline const float getColorR() const
  {
    return _color[0];
  }

  /** Returns the G color component. */
  inline const float getColorG() const
  {
    return _color[1];
  }

  /** Returns the B color component. */
  inline const float getColorB() const
  {
    return _color[2];
  }

  /** Returns the RGB color components. */
  inline Vec3f getColorRGB() const
  {
    return Vec3f(_color[0], _color[1], _color[2]);
  }

  /** Returns the alpha color component. */
  inline float getAlpha() const
  {
    return _alpha;
  }

  /** Returns the attribute's thickness.
   *  \return an array of 2 floats. the first value is the thickness on the right of the vertex
   * when following the stroke, the second one is the thickness on the left.
   */
  inline const float *getThickness() const
  {
    return _thickness;
  }

  /** Returns the thickness on the right of the vertex when following the stroke. */
  inline const float getThicknessR() const
  {
    return _thickness[0];
  }

  /** Returns the thickness on the left of the vertex when following the stroke. */
  inline const float getThicknessL() const
  {
    return _thickness[1];
  }

  /** Returns the thickness on the right and on the left of the vertex when following the stroke.
   */
  inline Vec2f getThicknessRL() const
  {
    return Vec2f(_thickness[0], _thickness[1]);
  }

  /** Returns true if the strokevertex is visible, false otherwise */
  inline bool isVisible() const
  {
    return _visible;
  }

  /** Returns an attribute of type real
   *  \param iName:
   *    The name of the attribute
   */
  float getAttributeReal(const char *iName) const;

  /** Returns an attribute of type Vec2f
   *  \param iName:
   *    The name of the attribute
   */
  Vec2f getAttributeVec2f(const char *iName) const;

  /** Returns an attribute of type Vec3f
   *  \param iName:
   *    The name of the attribute
   */
  Vec3f getAttributeVec3f(const char *iName) const;

  /** Checks whether the attribute iName is available */
  bool isAttributeAvailableReal(const char *iName) const;

  /** Checks whether the attribute iName is available */
  bool isAttributeAvailableVec2f(const char *iName) const;

  /** Checks whether the attribute iName is available */
  bool isAttributeAvailableVec3f(const char *iName) const;

  /* modifiers */
  /** sets the attribute's color.
   *    \param r:
   *      The new R value.
   *    \param g:
   *      The new G value.
   *    \param b:
   *      The new B value.
   */
  inline void setColor(float r, float g, float b)
  {
    _color[0] = r;
    _color[1] = g;
    _color[2] = b;
  }

  /** sets the attribute's color.
   *    \param iRGB:
   *      The new RGB values.
   */
  inline void setColor(const Vec3f &iRGB)
  {
    _color[0] = iRGB[0];
    _color[1] = iRGB[1];
    _color[2] = iRGB[2];
  }

  /** sets the attribute's alpha value.
   *  \param alpha:
   *    The new alpha value.
   */
  inline void setAlpha(float alpha)
  {
    _alpha = alpha;
  }

  /** sets the attribute's thickness.
   *  \param tr:
   *    The thickness on the right of the vertex when following the stroke.
   *  \param tl:
   *    The thickness on the left of the vertex when following the stroke.
   */
  inline void setThickness(float tr, float tl)
  {
    _thickness[0] = tr;
    _thickness[1] = tl;
  }

  /** sets the attribute's thickness.
   *  \param tRL:
   *    The thickness on the right and on the left of the vertex when following the stroke.
   */
  inline void setThickness(const Vec2f &tRL)
  {
    _thickness[0] = tRL[0];
    _thickness[1] = tRL[1];
  }

  /** sets the visible flag. True means visible. */
  inline void setVisible(bool iVisible)
  {
    _visible = iVisible;
  }

  /** Adds a user defined attribute of type real
   *  If there is no attribute of name iName, it is added.
   *  Otherwise, the new value replaces the old one.
   *  \param iName:
   *    The name of the attribute
   *  \param att:
   *    The attribute's value
   */
  void setAttributeReal(const char *iName, float att);

  /** Adds a user defined attribute of type Vec2f
   *  If there is no attribute of name iName, it is added.
   *  Otherwise, the new value replaces the old one.
   *  \param iName:
   *    The name of the attribute
   *  \param att:
   *    The attribute's value
   */
  void setAttributeVec2f(const char *iName, const Vec2f &att);

  /** Adds a user defined attribute of type Vec3f
   *  If there is no attribute of name iName, it is added.
   *  Otherwise, the new value replaces the old one.
   *  \param iName:
   *    The name of the attribute
   *  \param att:
   *    The attribute's value
   */
  void setAttributeVec3f(const char *iName, const Vec3f &att);

 private:
  typedef std::map<const char *, float, StringUtils::ltstr> realMap;
  typedef std::map<const char *, Vec2f, StringUtils::ltstr> Vec2fMap;
  typedef std::map<const char *, Vec3f, StringUtils::ltstr> Vec3fMap;

  //! the color
  float _color[3];
  //! alpha
  float _alpha;
  //! the thickness on the right and on the left of the backbone vertex (the stroke is oriented)
  float _thickness[2];
  bool _visible;
  realMap *_userAttributesReal;
  Vec2fMap *_userAttributesVec2f;
  Vec3fMap *_userAttributesVec3f;

  MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:StrokeAttribute")
};

//
//  StrokeVertex
//
////////////////////////////////////////////////////////

/** Class to define a stroke vertex. */
class StrokeVertex : public CurvePoint {
 public:  // Implementation of Interface0D
  /** Returns the string "StrokeVertex" */
  virtual string getExactTypeName() const
  {
    return "StrokeVertex";
  }

 private:
  StrokeAttribute _Attribute;  //! The attribute associated to the vertex
  float _CurvilignAbscissa;    //! the curvilign abscissa
  float _StrokeLength;         // stroke length

 public:
  /** default constructor */
  StrokeVertex();

  /** Copy constructor */
  StrokeVertex(const StrokeVertex &iBrother);

  /** Builds a stroke vertex from a SVertex */
  StrokeVertex(SVertex *iSVertex);

  /** Builds a stroke vertex from a CurvePoint */
  StrokeVertex(CurvePoint *iPoint);

  /** Builds Stroke Vertex from 2 stroke vertices and an interpolation parameter. */
  StrokeVertex(StrokeVertex *iA, StrokeVertex *iB, float t3);

  /** Builds a stroke from a view vertex and an attribute */
  StrokeVertex(SVertex *iSVertex, const StrokeAttribute &iAttribute);

  /* operators */
  /** operator = */
  StrokeVertex &operator=(const StrokeVertex &iBrother);

  /* accessors */
  /** Returns the 2D point x coordinate */
  inline real x() const
  {
    return _Point2d[0];
  }

  /** Returns the 2D point y coordinate */
  inline real y() const
  {
    return _Point2d[1];
  }

  /** Returns the 2D point coordinates as a Vec2r */
  inline Vec2r getPoint() const
  {
    return getPoint2D();
  }

  /** Returns the i-th 2D point coordinate (i=0 or 1). */
  inline real operator[](const int i) const
  {
    return _Point2d[i];
  }

  /** Returns the StrokeAttribute for this StrokeVertex */
  inline const StrokeAttribute &attribute() const
  {
    return _Attribute;
  }

  /** Returns a non-const reference to the StrokeAttribute of this StrokeVertex */
  inline StrokeAttribute &attribute()
  {
    return _Attribute;
  }

  /** Returns the curvilinear abscissa */
  inline float curvilinearAbscissa() const
  {
    return _CurvilignAbscissa;
  }

  /** Returns the length of the Stroke to which this StrokeVertex belongs */
  inline float strokeLength() const
  {
    return _StrokeLength;
  }

  /** Returns the curvilinear abscissa of this StrokeVertex in the Stroke */
  inline float u() const
  {
    return _CurvilignAbscissa / _StrokeLength;
  }

  /* modifiers */
  /** sets the 2D x value */
  inline void setX(real x)
  {
    _Point2d[0] = x;
  }

  /** sets the 2D y value */
  inline void setY(real y)
  {
    _Point2d[1] = y;
  }

  /** sets the 2D x and y values */
  inline void setPoint(real x, real y)
  {
    _Point2d[0] = x;
    _Point2d[1] = y;
  }

  /** sets the 2D x and y values */
  inline void setPoint(const Vec2r &p)
  {
    _Point2d[0] = p[0];
    _Point2d[1] = p[1];
  }

  /** Returns a reference to the i-th 2D point coordinate (i=0 or 1). */
  inline real &operator[](const int i)
  {
    return _Point2d[i];
  }

  /** sets the attribute. */
  inline void setAttribute(const StrokeAttribute &iAttribute)
  {
    _Attribute = iAttribute;
  }

  /** sets the curvilinear abscissa of this StrokeVertex in the Stroke */
  inline void setCurvilinearAbscissa(float iAbscissa)
  {
    _CurvilignAbscissa = iAbscissa;
  }

  /** sets the Stroke's length (it's only a value stored by the Stroke Vertex, it won't change the
   * real Stroke's length.)
   */
  inline void setStrokeLength(float iLength)
  {
    _StrokeLength = iLength;
  }

  /* interface definition */
  /* inherited */

  MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:StrokeVertex")
};

//
//  Stroke
//
////////////////////////////////////////////////////////

class StrokeRenderer;
class StrokeRep;

namespace StrokeInternal {

class vertex_const_traits;
class vertex_nonconst_traits;
template<class Traits> class vertex_iterator_base;
class StrokeVertexIterator;

}  // end of namespace StrokeInternal

/** Class to define a stroke.
 *  A stroke is made of a set of 2D vertices (StrokeVertex), regularly spaced out.
 *  This set of vertices defines the stroke's backbone geometry.
 *  Each of these stroke vertices defines the stroke's shape and appearance at this vertex
 * position.
 */
class Stroke : public Interface1D {
 public:  // Implementation of Interface1D
  /** Returns the string "Stroke" */
  virtual string getExactTypeName() const
  {
    return "Stroke";
  }

  // Data access methods

  /** Returns the Id of the Stroke */
  virtual Id getId() const
  {
    return _id;
  }

  /** The different blending modes available to simulate the interaction media-medium. */
  typedef enum {
    DRY_MEDIUM,    /**< To simulate a dry medium such as Pencil or Charcoal. */
    HUMID_MEDIUM,  /**< To simulate ink painting (color subtraction blending). */
    OPAQUE_MEDIUM, /**< To simulate an opaque medium (oil, spray...). */
  } MediumType;

 public:
  typedef std::deque<StrokeVertex *> vertex_container;  // the vertices container
  typedef std::vector<ViewEdge *> viewedge_container;   // the viewedges container
  typedef StrokeInternal::vertex_iterator_base<StrokeInternal::vertex_nonconst_traits>
      vertex_iterator;
  typedef StrokeInternal::vertex_iterator_base<StrokeInternal::vertex_const_traits>
      const_vertex_iterator;

 public:
  // typedef StrokeVertex vertex_type;

 private:
  vertex_container _Vertices;  //! The stroke's backbone vertices
  Id _id;
  float _Length;  // The stroke length
  viewedge_container _ViewEdges;
  float _sampling;
  float _textureStep;
  // StrokeRenderer *_renderer; // mark implementation OpenGL renderer
  MediumType _mediumType;
  uint _textureId;
  MTex *_mtex[MAX_MTEX];
  bNodeTree *_nodeTree;
  bool _tips;
  StrokeRep *_rep;
  Vec2r _extremityOrientations[2];  // the orientations of the first and last extermity

 public:
  /** default constructor */
  Stroke();

  /** copy constructor */
  Stroke(const Stroke &iBrother);

  /** Builds a stroke from a set of StrokeVertex.
   *  This constructor is templated by an iterator type.
   *  This iterator type must allow the vertices parsing using the ++ operator.
   *    \param iBegin:
   *      The iterator pointing to the first vertex.
   *    \param iEnd:
   *      The iterator pointing to the end of the vertex list.
   */
  template<class InputVertexIterator> Stroke(InputVertexIterator iBegin, InputVertexIterator iEnd);

  /** Destructor */
  virtual ~Stroke();

  /* operators */
  /** operator = */
  Stroke &operator=(const Stroke &iBrother);

  /** Compute the sampling needed to get iNVertices vertices.
   *  If the specified number of vertices is less than the actual number of vertices, the actual
   * sampling value is returned. (To remove Vertices, use the RemoveVertex() method of this class).
   *  \param iNVertices:
   *    The number of StrokeVertices we eventually want in our Stroke.
   *  \return the sampling that must be used in the Resample(float) method.
   *  \see Resample(int)
   *  \see Resample(float)
   */
  float ComputeSampling(int iNVertices);

  /** Resampling method.
   *  Resamples the curve so that it eventually has iNPoints. That means it is going to add
   * iNPoints-vertices_size, if vertices_size is the number of points we already have. If
   * vertices_size >= iNPoints, no resampling is done.
   * \param iNPoints: The number of vertices we
   * eventually want in our stroke.
   */
  int Resample(int iNPoints);

  /** Resampling method.
   *  Resamples the curve with a given sampling.
   *  If this sampling is < to the actual sampling value, no resampling is done.
   *  \param iSampling:
   *    The new sampling value.
   */
  int Resample(float iSampling);

  /** Removes all vertices from the Stroke.
   */
  void RemoveAllVertices();

  /** Removes the stroke vertex iVertex
   *  from the stroke.
   *  The length and curvilinear abscissa are updated
   *  consequently.
   */
  void RemoveVertex(StrokeVertex *iVertex);

  /** Inserts the stroke vertex iVertex in the stroke before next.
   *  The length, curvilinear abscissa are updated consequently.
   *  \param iVertex:
   *    The StrokeVertex to insert in the Stroke.
   *  \param next:
   *    A StrokeVertexIterator pointing to the StrokeVertex before which iVertex must be inserted.
   */
  void InsertVertex(StrokeVertex *iVertex, StrokeInternal::StrokeVertexIterator next);

  /** Updates the 2D length of the Stroke */
  void UpdateLength();

  /* Render method */
  void ScaleThickness(float iFactor);
  void Render(const StrokeRenderer *iRenderer);
  void RenderBasic(const StrokeRenderer *iRenderer);

  /* Iterator definition */

  /* accessors */
  /** Returns the 2D length of the Stroke */
  inline real getLength2D() const
  {
    return _Length;
  }

  /** Returns a reference to the time stamp value of the stroke. */
  /** Returns the MediumType used for this Stroke. */
  inline MediumType getMediumType() const
  {
    return _mediumType;
  }

  /** Returns the id of the texture used to simulate th marks system for this Stroke */
  inline uint getTextureId()
  {
    return _textureId;
  }

  /** Returns the spacing of texture coordinates along the stroke length */
  inline float getTextureStep()
  {
    return _textureStep;
  }

  /** Returns the texture used at given index to simulate the marks system for this Stroke */
  inline MTex *getMTex(int idx)
  {
    return _mtex[idx];
  }

  /** Return the shader node tree to define textures. */
  inline bNodeTree *getNodeTree()
  {
    return _nodeTree;
  }

  /** Returns true if this Stroke has textures assigned, false otherwise. */
  inline bool hasTex() const
  {
    return (_mtex[0] != nullptr) || _nodeTree;
  }

  /** Returns true if this Stroke uses a texture with tips, false otherwise. */
  inline bool hasTips() const
  {
    return _tips;
  }

  /* these advanced iterators are used only in C++ */
  inline int vertices_size() const
  {
    return _Vertices.size();
  }

  inline viewedge_container::const_iterator viewedges_begin() const
  {
    return _ViewEdges.begin();
  }

  inline viewedge_container::iterator viewedges_begin()
  {
    return _ViewEdges.begin();
  }

  inline viewedge_container::const_iterator viewedges_end() const
  {
    return _ViewEdges.end();
  }

  inline viewedge_container::iterator viewedges_end()
  {
    return _ViewEdges.end();
  }

  inline int viewedges_size() const
  {
    return _ViewEdges.size();
  }

  inline Vec2r getBeginningOrientation() const
  {
    return _extremityOrientations[0];
  }

  inline real getBeginningOrientationX() const
  {
    return _extremityOrientations[0].x();
  }

  inline real getBeginningOrientationY() const
  {
    return _extremityOrientations[0].y();
  }

  inline Vec2r getEndingOrientation() const
  {
    return _extremityOrientations[1];
  }

  inline real getEndingOrientationX() const
  {
    return _extremityOrientations[1].x();
  }

  inline real getEndingOrientationY() const
  {
    return _extremityOrientations[1].y();
  }

  /* modifiers */
  /** sets the Id of the Stroke. */
  inline void setId(const Id &id)
  {
    _id = id;
  }

  /** sets the 2D length of the Stroke. */
  void setLength(float iLength);

  /** sets the medium type that must be used for this Stroke. */
  inline void setMediumType(MediumType iType)
  {
    _mediumType = iType;
  }

  /** sets the texture id to be used to simulate the marks system for this Stroke. */
  inline void setTextureId(uint id)
  {
    _textureId = id;
  }

  /** sets the spacing of texture coordinates along the stroke length. */
  inline void setTextureStep(float step)
  {
    _textureStep = step;
  }

  /** assigns a blender texture to the first available slot. */
  inline int setMTex(MTex *mtex)
  {
    for (int a = 0; a < MAX_MTEX; a++) {
      if (!_mtex[a]) {
        _mtex[a] = mtex;
        return 0;
      }
    }
    return -1; /* no free slots */
  }

  /** assigns a node tree (of new shading nodes) to define textures. */
  inline void setNodeTree(bNodeTree *iNodeTree)
  {
    _nodeTree = iNodeTree;
  }

  /** sets the flag telling whether this stroke is using a texture with tips or not. */
  inline void setTips(bool iTips)
  {
    _tips = iTips;
  }

  inline void push_back(StrokeVertex *iVertex)
  {
    _Vertices.push_back(iVertex);
  }

  inline void push_front(StrokeVertex *iVertex)
  {
    _Vertices.push_front(iVertex);
  }

  inline void AddViewEdge(ViewEdge *iViewEdge)
  {
    _ViewEdges.push_back(iViewEdge);
  }

  inline void setBeginningOrientation(const Vec2r &iOrientation)
  {
    _extremityOrientations[0] = iOrientation;
  }

  inline void setBeginningOrientation(real x, real y)
  {
    _extremityOrientations[0] = Vec2r(x, y);
  }

  inline void setEndingOrientation(const Vec2r &iOrientation)
  {
    _extremityOrientations[1] = iOrientation;
  }

  inline void setEndingOrientation(real x, real y)
  {
    _extremityOrientations[1] = Vec2r(x, y);
  }

  /* Information access interface */

  // embedding vertex iterator
  const_vertex_iterator vertices_begin() const;
  vertex_iterator vertices_begin(float sampling = 0.0f);
  const_vertex_iterator vertices_end() const;
  vertex_iterator vertices_end();

  /** Returns a StrokeVertexIterator pointing on the first StrokeVertex of the Stroke. One can
   * specify a sampling value to re-sample the Stroke on the fly if needed.
   *
   * \param t: The resampling value with which we want our Stroke to be resampled.
   * If 0 is specified, no resampling is done.
   */
  StrokeInternal::StrokeVertexIterator strokeVerticesBegin(float t = 0.0f);

  /** Returns a StrokeVertexIterator pointing after the last StrokeVertex of the Stroke. */
  StrokeInternal::StrokeVertexIterator strokeVerticesEnd();

  /** Returns the number of StrokeVertex constituting the Stroke. */
  inline uint strokeVerticesSize() const
  {
    return _Vertices.size();
  }

  /** Returns the i-th StrokeVertex constituting the Stroke. */
  inline StrokeVertex &strokeVerticeAt(uint i)
  {
    return *(_Vertices.at(i));
  }

  // Iterator access (Interface1D)
  /** Returns an Interface0DIterator pointing on the first StrokeVertex of the Stroke. */
  virtual Interface0DIterator verticesBegin();

  /** Returns an Interface0DIterator pointing after the last StrokeVertex of the Stroke. */
  virtual Interface0DIterator verticesEnd();

  virtual Interface0DIterator pointsBegin(float t = 0.0f);
  virtual Interface0DIterator pointsEnd(float t = 0.0f);

  MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:Stroke")
};

//
//  Implementation
//
////////////////////////////////////////////////////////

template<class InputVertexIterator>
Stroke::Stroke(InputVertexIterator iBegin, InputVertexIterator iEnd)
{
  for (InputVertexIterator v = iBegin, vend = iEnd; v != vend; v++) {
    _Vertices.push_back(*v);
  }
  _Length = 0;
  _id = 0;
}

} /* namespace Freestyle */
