

#include "tgl.h"
#include "toonz/strokegenerator.h"
//#include "tofflinegl.h"

#include "tstroke.h"
#include "toonz/preferences.h"

using namespace std;

//-------------------------------------------------------------------

void StrokeGenerator::clear() {
  m_points.clear();
  m_modifiedRegion = TRectD();
  m_lastPointRect.empty();
  m_lastModifiedRegion.empty();
  m_paintedPointCount = 0;
  m_p0 = m_p1 = TPointD();
}

//-------------------------------------------------------------------

bool StrokeGenerator::isEmpty() const { return m_points.empty(); }

//-------------------------------------------------------------------

bool StrokeGenerator::add(const TThickPoint &point, double pixelSize2) {
  if (m_points.empty()) {
    double x = point.x, y = point.y, d = point.thick + 3;
    m_points.push_back(point);
    TRectD rect(x - d, y - d, x + d, y + d);
    m_modifiedRegion     += rect;
    m_lastPointRect      += rect;
    m_lastModifiedRegion += rect;
    m_p0 = m_p1 = point;
    return true;
  }

  TThickPoint lastPoint = m_points.back();
  if (tdistance2(lastPoint, point) >= 4 * pixelSize2) {
    m_points.push_back(point);
    double d = std::max(point.thick, lastPoint.thick) + 3;
    TRectD rect(TRectD(lastPoint, point).enlarge(d));
    m_modifiedRegion += rect;
    m_lastModifiedRegion += rect;
    m_lastPointRect = rect;
    return true;
  }
  
  m_points.back().thick = std::max(m_points.back().thick, point.thick);
  return false;
}

//-------------------------------------------------------------------

void StrokeGenerator::pop() {
  if (!m_points.empty()) {
    TRectD rect;
    TThickPoint point = m_points.back();
    m_points.pop_back();
    if (!m_points.empty()) {
      const TThickPoint &lastPoint = m_points.back();
      double d = std::max(point.thick, lastPoint.thick) + 3;
      rect = TRectD(lastPoint, point).enlarge(d);
    } else {
      double x = point.x, y = point.y, d = point.thick + 3;
      rect = TRectD(x - d, y - d, x + d, y + d);
    }
    m_modifiedRegion += rect;
    m_lastModifiedRegion += rect;
    m_lastPointRect = rect;
  }
}

//-------------------------------------------------------------------

void StrokeGenerator::setLoop(bool loop)
  { m_loop = loop; }

//-------------------------------------------------------------------

void StrokeGenerator::filterPoints() {
  if (m_points.size() < 10) return;

  //  filtra m_points iniziali: generalmente elevate variazioni di thickness
  //  si hanno tra m_points[0] (al massimo m_points[1]) e i successivi)
  int size1 = m_points.size();
  int kMin  = 0;
  int kMax  = std::min(
      4,
      size1 -
          2);  //  confronta 5 m_points iniziali con i successivi corrispondenti
  int k = kMax;
  for (k = kMax; k >= kMin; --k) {
    TThickPoint currPoint = m_points[k];
    TThickPoint nextPoint = m_points[k + 1];
    double dist           = tdistance(currPoint, nextPoint);
    double deltaThick     = fabs(currPoint.thick - nextPoint.thick);
    if (deltaThick > 0.6 * dist)  //  deltaThick <= dist (condizione
                                  //  approssimata di non-autocontenimento per
                                  //  TTQ)
    {
      vector<TThickPoint>::iterator it1 = m_points.begin();
      vector<TThickPoint>::iterator it2 = it1 + k + 1;
      m_points.erase(it1, it2);  //  cancella da m_points[0] a m_points[k]
      assert((int)m_points.size() == size1 - k - 1);
      break;
    }
  }
  //  filtra m_points finali: generalmente elevate variazioni di thickness
  //  si hanno tra m_points[size - 1] (al massimo m_points[size - 2]) e i
  //  predecessori)
  int size2 = m_points.size();
  kMax      = size2 - 1;
  kMin      = std::max(
      kMax - 4,
      1);  //  confronta 5 m_points finali con i predecessori corrispondenti
  k = kMin;
  for (k = kMin; k <= kMax; ++k) {
    TThickPoint currPoint = m_points[k];
    TThickPoint prevPoint = m_points[k - 1];
    double dist           = tdistance(currPoint, prevPoint);
    double deltaThick     = fabs(currPoint.thick - prevPoint.thick);
    if (deltaThick > 0.6 * dist)  //  deltaThick <= dist (condizione
                                  //  approssimata di non-autocontenimento per
                                  //  TTQ)
    {
      int kTmp = k;
      while (k <= kMax)  //  cancella da m_points[k] a m_points[size2 - 1]
      {
        m_points.pop_back();
        ++k;
      }
      assert((int)m_points.size() == size2 - (kMax - kTmp + 1));
      break;
    }
  }
}

//-------------------------------------------------------------------

void StrokeGenerator::drawFragments(int first, int last) {
  if (m_points.empty()) return;
  int i                                  = first;
  if (last >= (int)m_points.size()) last = m_points.size() - 1;
  TThickPoint a;
  TThickPoint b;
  TThickPoint c;
  TPointD v;

  // If drawing a straight line, a stroke can have only two points
  if (m_points.size() == 2) {
    a = m_points[0];
    b = m_points[1];
    if (Preferences::instance()->getShow0ThickLines()) {
      if (a.thick == 0) a.thick = 0.1;
      if (b.thick == 0) b.thick = 0.1;
    }
    // m_p0 = m_p1 = b;
    v          = a.thick * normalizeOrZero(rotate90(b - a));
    m_p0       = a + v;
    m_p1       = a - v;
    v          = b.thick * normalizeOrZero(rotate90(b - a));
    TPointD p0 = b + v;
    TPointD p1 = b - v;
    glBegin(GL_POLYGON);
    tglVertex(m_p0);
    tglVertex(m_p1);
    tglVertex(p1);
    tglVertex(p0);
    glEnd();
    m_p0 = p0;
    m_p1 = p1;
    glBegin(GL_LINE_STRIP);
    tglVertex(a);
    tglVertex(b);
    glEnd();
    return;
  }

  while (i < last) {
    a = m_points[i - 1];
    b = m_points[i];
    c = m_points[i + 1];
    if (Preferences::instance()->getShow0ThickLines()) {
      if (a.thick == 0) a.thick = 0.1;
      if (b.thick == 0) b.thick = 0.1;
      if (c.thick == 0) c.thick = 0.1;
    }
    if (i - 1 == 0) {
      v    = a.thick * normalizeOrZero(rotate90(b - a));
      m_p0 = a + v;
      m_p1 = a - v;
    }
    v          = b.thick * normalizeOrZero(rotate90(c - a));
    TPointD p0 = b + v;
    TPointD p1 = b - v;
    glBegin(GL_POLYGON);
    tglVertex(m_p0);
    tglVertex(m_p1);
    tglVertex(p1);
    tglVertex(p0);
    glEnd();
    m_p0 = p0;
    m_p1 = p1;

    glBegin(GL_LINE_STRIP);
    tglVertex(a);
    tglVertex(b);
    glEnd();
    i++;
  }
  if (last < 2) return;
  v = m_points[last].thick *
      normalizeOrZero(rotate90(m_points[last] - m_points[last - 1]));
  TPointD p0 = m_points[last] + v;
  TPointD p1 = m_points[last] - v;
  glBegin(GL_POLYGON);
  tglVertex(m_p0);
  tglVertex(m_p1);
  tglVertex(p1);
  tglVertex(p0);
  glEnd();
  glBegin(GL_LINE_STRIP);
  tglVertex(m_points[last - 1]);
  tglVertex(m_points[last]);
  glEnd();
}

//-------------------------------------------------------------------

void StrokeGenerator::drawLastFragments() {
  if (m_points.empty()) return;
  int n          = m_points.size();
  int i          = m_paintedPointCount;
  const double h = 0.01;

  if (i == 0) {
    TThickPoint a = m_points[0];
    if (a.thick >= h) tglDrawDisk(a, a.thick);
    i++;
  }

  drawFragments(i, n - 1);

  m_paintedPointCount = std::max(0, n - 2);
}

//-------------------------------------------------------------------

void StrokeGenerator::drawAllFragments() {
  if (m_points.empty()) return;

  int n          = m_points.size();
  int i          = 0;
  const double h = 0.01;

  TThickPoint a = m_points[0];
  if (a.thick >= h) tglDrawDisk(a, a.thick);

  drawFragments(1, n - 1);
  /*
//last fragment
TPointD p0 = c+v;
TPointD p1 = c-v;
glBegin(GL_POLYGON);
tglVertex(m_p0);
tglVertex(m_p1);
tglVertex(p1);
tglVertex(p0);
glEnd();
*/
  a = m_points.back();
  if (a.thick >= h) tglDrawDisk(a, a.thick);
}

//-------------------------------------------------------------------

void StrokeGenerator::drawFilledStroke() {
  if (m_points.size() < 3) {
    drawAllFragments();
    return;
  }

  // Enable stencil buffer for overlap handling
  glEnable(GL_STENCIL_TEST);
  glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
  glStencilFunc(GL_ALWAYS, 0, 0xFF);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // First pass: fill the stencil buffer
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  glStencilMask(0xFF);
  glClear(GL_STENCIL_BUFFER_BIT);

  glBegin(GL_TRIANGLE_FAN);
  TThickPoint first = m_points.front();
  glVertex2d(first.x, first.y);
  for (const TThickPoint &p : m_points) {
    glVertex2d(p.x, p.y);
  }
  glVertex2d(first.x, first.y);
  glEnd();

  // Second pass: draw with stencil test
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
  glStencilMask(0x00);

  glBegin(GL_TRIANGLE_FAN);
  glVertex2d(first.x, first.y);
  for (const TThickPoint &p : m_points) {
    glVertex2d(p.x, p.y);
  }
  glVertex2d(first.x, first.y);
  glEnd();

  glDisable(GL_STENCIL_TEST);
  glDisable(GL_BLEND);

  // Draw endpoints if needed
  if (first.thick > 0.01) {
    tglDrawDisk(first, first.thick);
  }
  TThickPoint last = m_points.back();
  if (last.thick > 0.01 &&
      last != first) {  // Avoid double-drawing if first == last
    tglDrawDisk(last, last.thick);
  }
}

//-------------------------------------------------------------------

TRectD StrokeGenerator::getModifiedRegion() const { return m_modifiedRegion; }

//-------------------------------------------------------------------

void StrokeGenerator::removeMiddlePoints() {
  int size = m_points.size();
  if (size > 2) {
    m_points.erase(m_points.begin() + 1, m_points.begin() + (size - 1));
  }
}

//-------------------------------------------------------------------

TRectD StrokeGenerator::getLastModifiedRegion() {
  TRectD ret           = m_lastModifiedRegion;
  m_lastModifiedRegion = m_lastPointRect;
  return ret;
}

//-------------------------------------------------------------------

TPointD StrokeGenerator::getFirstPoint() { return m_points[0]; }

//-------------------------------------------------------------------

TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints, bool useLoop) const {
  if (onlyLastPoints == 0 || onlyLastPoints > m_points.size())
    return TStroke::interpolate(m_points, error);

  vector<TThickPoint> lastPoints(onlyLastPoints);
  vector<TThickPoint>::const_iterator first =
      m_points.begin() + (m_points.size() - onlyLastPoints);
  copy(first, m_points.end(), lastPoints.begin());

  TStroke *stroke = TStroke::interpolate(lastPoints, error);
  if (useLoop) stroke->setSelfLoop(m_loop);
  return stroke;
}

//-------------------------------------------------------------------
