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

#pragma once

/** \file
 * \ingroup freestyle
 * \brief Class gathering stroke creation algorithms
 */

#include <string>

#include "AdvancedFunctions1D.h"

#include "../system/TimeStamp.h"

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

#ifdef WITH_CXX_GUARDEDALLOC
#  include "MEM_guardedalloc.h"
#endif

namespace Freestyle {

//
// UnaryPredicate1D (base class for predicates in 1D)
//
///////////////////////////////////////////////////////////

/** Base class for Unary Predicates that work on Interface1D.
 *  A UnaryPredicate1D is a functor that evaluates a condition on a Interface1D and returns
 *  true or false depending on whether this condition is satisfied or not.
 *  The UnaryPredicate1D is used by calling its () operator.
 *  Any inherited class must overload the () operator.
 */
class UnaryPredicate1D {
 public:
  bool result;
  void *py_up1D;

  /** Default constructor. */
  UnaryPredicate1D()
  {
    py_up1D = nullptr;
  }

  /** Destructor. */
  virtual ~UnaryPredicate1D() {}

  /** Returns the string of the name of the UnaryPredicate1D. */
  virtual string getName() const
  {
    return "UnaryPredicate1D";
  }

  /** The () operator. Must be overload by inherited classes.
   *  \param inter:
   *    The Interface1D on  which we wish to evaluate the predicate.
   *  \return true if the condition is satisfied, false otherwise.
   */
  virtual int operator()(Interface1D &inter);

#ifdef WITH_CXX_GUARDEDALLOC
  MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:UnaryPredicate1D")
#endif
};

//
// BinaryPredicate1D (base class for predicates in 1D)
//
///////////////////////////////////////////////////////////

/** Base class for Binary Predicates working on Interface1D.
 *  A BinaryPredicate1D is typically an ordering relation between two Interface1D.
 *  It evaluates a relation between 2 Interface1D and returns true or false.
 *  It is used by calling the () operator.
 */
class BinaryPredicate1D {
 public:
  bool result;
  void *py_bp1D;

  /** Default constructor. */
  BinaryPredicate1D()
  {
    py_bp1D = nullptr;
  }

  /** Destructor. */
  virtual ~BinaryPredicate1D() {}

  /** Returns the string of the name of the binary predicate. */
  virtual string getName() const
  {
    return "BinaryPredicate1D";
  }

  /** The () operator. Must be overload by inherited classes.
   *  It evaluates a relation between 2 Interface1D.
   *  \param inter1:
   *    The first Interface1D.
   *  \param inter2:
   *    The second Interface1D.
   *  \return true or false.
   */
  virtual int operator()(Interface1D &inter1, Interface1D &inter2);

#ifdef WITH_CXX_GUARDEDALLOC
  MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:BinaryPredicate1D")
#endif
};

//
// Predicates definitions
//
///////////////////////////////////////////////////////////

namespace Predicates1D {

// TrueUP1D
/** Returns true */
class TrueUP1D : public UnaryPredicate1D {
 public:
  /** Constructor */
  TrueUP1D() {}

  /** Returns the string "TrueUP1D". */
  string getName() const
  {
    return "TrueUP1D";
  }

  /** the () operator */
  int operator()(Interface1D &)
  {
    result = true;
    return 0;
  }
};

// FalseUP1D
/** Returns false */
class FalseUP1D : public UnaryPredicate1D {
 public:
  /** Constructor */
  FalseUP1D() {}

  /** Returns the string "FalseUP1D". */
  string getName() const
  {
    return "FalseUP1D";
  }

  /** the () operator */
  int operator()(Interface1D &)
  {
    result = false;
    return 0;
  }
};

// QuantitativeInvisibilityUP1D
/** Returns true if the Quantitative Invisibility evaluated at an Interface1D, using the
 * QuantitativeInvisibilityF1D functor, equals a certain user-defined value.
 */
class QuantitativeInvisibilityUP1D : public UnaryPredicate1D {
 public:
  /** Builds the Predicate.
   *  \param qi:
   *    The Quantitative Invisibility you want the Interface1D to have
   */
  QuantitativeInvisibilityUP1D(uint qi = 0) : _qi(qi) {}

  /** Returns the string "QuantitativeInvisibilityUP1D" */
  string getName() const
  {
    return "QuantitativeInvisibilityUP1D";
  }

  /** the () operator */
  int operator()(Interface1D &inter)
  {
    Functions1D::QuantitativeInvisibilityF1D func;
    if (func(inter) < 0) {
      return -1;
    }
    result = (func.result == _qi);
    return 0;
  }

 private:
  uint _qi;
};

// ContourUP1D
/** Returns true if the Interface1D is a contour.
 *  An Interface1D is a contour if it is bordered by a different shape on each of its sides.
 */
class ContourUP1D : public UnaryPredicate1D {
 private:
  Functions1D::CurveNatureF1D _getNature;

 public:
  /** Returns the string "ContourUP1D". */
  string getName() const
  {
    return "ContourUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    if (_getNature(inter) < 0) {
      return -1;
    }
    if ((_getNature.result & Nature::SILHOUETTE) || (_getNature.result & Nature::BORDER)) {
      Interface0DIterator it = inter.verticesBegin();
      for (; !it.isEnd(); ++it) {
        if (Functions0D::getOccludeeF0D(it) != Functions0D::getShapeF0D(it)) {
          result = true;
          return 0;
        }
      }
    }
    result = false;
    return 0;
  }
};

// ExternalContourUP1D
/** Returns true if the Interface1D is an external contour.
 *  An Interface1D is an external contour if it is bordered by no shape on one of its sides.
 */
class ExternalContourUP1D : public UnaryPredicate1D {
 private:
  Functions1D::CurveNatureF1D _getNature;

 public:
  /** Returns the string "ExternalContourUP1D" */
  string getName() const
  {
    return "ExternalContourUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    if (_getNature(inter) < 0) {
      return -1;
    }
    if ((_getNature.result & Nature::SILHOUETTE) || (_getNature.result & Nature::BORDER)) {
      set<ViewShape *> occluded;
      Functions1D::getOccludeeF1D(inter, occluded);
      for (set<ViewShape *>::iterator os = occluded.begin(), osend = occluded.end(); os != osend;
           ++os)
      {
        if ((*os) == 0) {
          result = true;
          return 0;
        }
      }
    }
    result = false;
    return 0;
  }
};

// EqualToTimeStampUP1D
/** Returns true if the Interface1D's time stamp is equal to a certain user-defined value. */
class EqualToTimeStampUP1D : public UnaryPredicate1D {
 protected:
  uint _timeStamp;

 public:
  EqualToTimeStampUP1D(uint ts) : UnaryPredicate1D()
  {
    _timeStamp = ts;
  }

  /** Returns the string "EqualToTimeStampUP1D". */
  string getName() const
  {
    return "EqualToTimeStampUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    result = (inter.getTimeStamp() == _timeStamp);
    return 0;
  }
};

// EqualToChainingTimeStampUP1D
/** Returns true if the Interface1D's time stamp is equal to a certain user-defined value. */
class EqualToChainingTimeStampUP1D : public UnaryPredicate1D {
 protected:
  uint _timeStamp;

 public:
  EqualToChainingTimeStampUP1D(uint ts) : UnaryPredicate1D()
  {
    _timeStamp = ts;
  }

  /** Returns the string "EqualToChainingTimeStampUP1D". */
  string getName() const
  {
    return "EqualToChainingTimeStampUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    ViewEdge *edge = dynamic_cast<ViewEdge *>(&inter);
    if (!edge) {
      result = false;
      return 0;
    }
    result = (edge->getChainingTimeStamp() >= _timeStamp);
    return 0;
  }
};

// ShapeUP1D
/** Returns true if the shape to which the Interface1D belongs to has the same Id as the one
 * specified by the user. */
class ShapeUP1D : public UnaryPredicate1D {
 private:
  Id _id;

 public:
  /** Builds the Predicate.
   *  \param idFirst:
   *    The first Id component.
   *  \param idSecond:
   *    The second Id component.
   */
  ShapeUP1D(uint idFirst, uint idSecond = 0) : UnaryPredicate1D()
  {
    _id = Id(idFirst, idSecond);
  }

  /** Returns the string "ShapeUP1D". */
  string getName() const
  {
    return "ShapeUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    set<ViewShape *> shapes;
    Functions1D::getShapeF1D(inter, shapes);
    for (set<ViewShape *>::iterator s = shapes.begin(), send = shapes.end(); s != send; ++s) {
      if ((*s)->getId() == _id) {
        result = true;
        return 0;
      }
    }
    result = false;
    return 0;
  }
};

// WithinImageBoundaryUP1D
/** Returns true if the Interface1D is (partly) within the image boundary. */
class WithinImageBoundaryUP1D : public UnaryPredicate1D {
 private:
  real _xmin, _ymin, _xmax, _ymax;

 public:
  /** Builds the Predicate.
   *  \param xmin:
   *    The X lower bound of the image boundary.
   *  \param ymin:
   *    The Y lower bound of the image boundary.
   *  \param xmax:
   *    The X upper bound of the image boundary.
   *  \param ymax:
   *    The Y upper bound of the image boundary.
   */
  WithinImageBoundaryUP1D(const real xmin, const real ymin, const real xmax, const real ymax)
      : _xmin(xmin), _ymin(ymin), _xmax(xmax), _ymax(ymax)
  {
  }

  /** Returns the string "WithinImageBoundaryUP1D" */
  string getName() const
  {
    return "WithinImageBoundaryUP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &inter)
  {
    // 1st pass: check if a point is within the image boundary.
    Interface0DIterator it = inter.verticesBegin(), itend = inter.verticesEnd();
    for (; it != itend; ++it) {
      real x = (*it).getProjectedX();
      real y = (*it).getProjectedY();
      if (_xmin <= x && x <= _xmax && _ymin <= y && y <= _ymax) {
        result = true;
        return 0;
      }
    }
    // 2nd pass: check if a line segment intersects with the image boundary.
    it = inter.verticesBegin();
    if (it != itend) {
      Vec2r pmin(_xmin, _ymin);
      Vec2r pmax(_xmax, _ymax);
      Vec2r prev((*it).getPoint2D());
      ++it;
      for (; it != itend; ++it) {
        Vec2r p((*it).getPoint2D());
        if (GeomUtils::intersect2dSeg2dArea(pmin, pmax, prev, p)) {
          result = true;
          return 0;
        }
        prev = p;
      }
    }
    result = false;
    return 0;
  }
};

//
//   Binary Predicates definitions
//
///////////////////////////////////////////////////////////

// TrueBP1D
/** Returns true. */
class TrueBP1D : public BinaryPredicate1D {
 public:
  /** Returns the string "TrueBP1D" */
  string getName() const
  {
    return "TrueBP1D";
  }

  /** The () operator. */
  int operator()(Interface1D & /*i1*/, Interface1D & /*i2*/)
  {
    result = true;
    return 0;
  }
};

// FalseBP1D
/** Returns false. */
class FalseBP1D : public BinaryPredicate1D {
 public:
  /** Returns the string "FalseBP1D" */
  string getName() const
  {
    return "FalseBP1D";
  }

  /** The () operator. */
  int operator()(Interface1D & /*i1*/, Interface1D & /*i2*/)
  {
    result = false;
    return 0;
  }
};

// Length2DBP1D
/** Returns true if the 2D length of the Interface1D i1 is less than the 2D length of the
 * Interface1D i2. */
class Length2DBP1D : public BinaryPredicate1D {
 public:
  /** Returns the string "Length2DBP1D" */
  string getName() const
  {
    return "Length2DBP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &i1, Interface1D &i2)
  {
    result = (i1.getLength2D() > i2.getLength2D());
    return 0;
  }
};

// SameShapeIdBP1D
/** Returns true if the Interface1D i1 and i2 belong to the same shape. */
class SameShapeIdBP1D : public BinaryPredicate1D {
 public:
  /** Returns the string "SameShapeIdBP1D" */
  string getName() const
  {
    return "SameShapeIdBP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &i1, Interface1D &i2)
  {
    set<ViewShape *> shapes1;
    Functions1D::getShapeF1D(i1, shapes1);
    set<ViewShape *> shapes2;
    Functions1D::getShapeF1D(i2, shapes2);
    // FIXME:// n2 algo, can do better...
    for (set<ViewShape *>::iterator s = shapes1.begin(), send = shapes1.end(); s != send; ++s) {
      Id current = (*s)->getId();
      for (set<ViewShape *>::iterator s2 = shapes2.begin(), s2end = shapes2.end(); s2 != s2end;
           ++s2)
      {
        if ((*s2)->getId() == current) {
          result = true;
          return 0;
        }
      }
    }
    result = false;
    return 0;
  }
};

// ViewMapGradientNormBP1D
/** Returns true if the evaluation of the Gradient norm Function is higher for Interface1D i1 than
 * for i2. */
class ViewMapGradientNormBP1D : public BinaryPredicate1D {
 private:
  Functions1D::GetViewMapGradientNormF1D _func;

 public:
  ViewMapGradientNormBP1D(int level, IntegrationType iType = MEAN, float sampling = 2.0)
      : BinaryPredicate1D(), _func(level, iType, sampling)
  {
  }

  /** Returns the string "ViewMapGradientNormBP1D" */
  string getName() const
  {
    return "ViewMapGradientNormBP1D";
  }

  /** The () operator. */
  int operator()(Interface1D &i1, Interface1D &i2)
  {
    if (_func(i1) < 0) {
      return -1;
    }
    real n1 = _func.result;
    if (_func(i2) < 0) {
      return -1;
    }
    real n2 = _func.result;
    result = (n1 > n2);
    return 0;
  }
};

}  // end of namespace Predicates1D

} /* namespace Freestyle */
