

#include "tl2lautocloser.h"

#include "tgl.h"

#include "tvectorimage.h"
#include "tstroke.h"

#include <QDebug>

//=============================================================================
#ifdef _WIN32

class MyTimer {
  bool m_enabled;
  LARGE_INTEGER m_freq;
  LARGE_INTEGER m_startTime, m_overhead;

public:
  MyTimer() : m_enabled(false) {
    if (QueryPerformanceFrequency(&m_freq)) {
      m_enabled = true;
      LARGE_INTEGER curTime;
      QueryPerformanceCounter(&m_startTime);
      QueryPerformanceCounter(&curTime);
      m_overhead.QuadPart = curTime.QuadPart - m_startTime.QuadPart;
    }
  }

  void start() {
    if (!m_enabled) return;
    QueryPerformanceCounter(&m_startTime);
  }

  double elapsedSeconds() {
    LARGE_INTEGER curTime;
    QueryPerformanceCounter(&curTime);
    LONGLONG microseconds = 1000000 * (curTime.QuadPart - m_startTime.QuadPart -
                                       m_overhead.QuadPart) /
                            m_freq.QuadPart;
    return 0.000001 * (double)microseconds;
  }
};
#endif

//=============================================================================

namespace {

// find the stroke curvature at w
// cfr. http://en.wikipedia.org/wiki/Curvature
TPointD getCurvature(TStroke *stroke, double w) {
  const double h = 0.0001;
  double w0      = std::max(0.0, w - h);
  double w1      = std::min(1.0, w + h);
  TPointD p0     = stroke->getPoint(w0);
  TPointD p1     = stroke->getPoint(w1);
  double ds      = norm(p0 - p1);
  double f       = (w1 - w0) / ds;

  TPointD d  = stroke->getSpeed(w) * f;
  TPointD d0 = stroke->getSpeed(w0) * f;
  TPointD d1 = stroke->getSpeed(w1) * f;
  TPointD dd = (d1 - d0) * (1.0 / ds);

  double c    = (d.x * dd.y - d.y * dd.x) / pow(d.x * d.x + d.y * d.y, 1.5);
  TPointD crv = normalize(rotate90(d)) * c;

  return crv;
}

//===========================================================================

//
// A point along a stroke: ths stroke itself, pos, w,s, crv
//
struct StrokePoint {
  double w, s;
  TPointD pos, crv, crvdir;  // crvdir is crv normalized (or 0)
  TPointD tgdir;             // tgdir is the normalized tangent
  TStroke *stroke;
  StrokePoint(TStroke *stroke_, double s_)
      : stroke(stroke_), w(stroke_->getParameterAtLength(s_)), s(s_) {
    pos      = stroke->getPoint(w);
    crv      = getCurvature(stroke, w);
    double c = norm(crv);
    if (c > 0)
      crvdir = crv * (1.0 / c);
    else
      crvdir         = TPointD();
    tgdir            = stroke->getSpeed(w);
    double tgdirNorm = norm(tgdir);
    if (tgdirNorm > 0.000001)
      tgdir = tgdir * (1.0 / tgdirNorm);
    else
      tgdir = TPointD(0, 0);
  }
  StrokePoint() : w(0), s(0), pos(), crv(), crvdir(), stroke(0) {}
};

//===========================================================================

// a set of StrokePoint evenly spaced along the whole stroke
// (curvilinear distance between two adjacent point is "inc")
struct StrokePointSet {
  TStroke *stroke;
  std::vector<StrokePoint> points;
  StrokePointSet(TStroke *stroke_ = 0) : stroke(stroke_) {
    const double inc = 5;
    if (stroke_) {
      double length = stroke->getLength();
      double s      = 0;
      if (stroke->isSelfLoop()) length -= inc;
      while (s < length) {
        points.push_back(StrokePoint(stroke, s));
        s += inc;
      }
    }
  }
};

//===========================================================================

class StrokesIntersection {
public:
  std::vector<double> m_ida, m_idb;  // distances to the closest intersection
                                     // (referred to StrokePointSet)

  StrokesIntersection() {}
  StrokesIntersection(const StrokePointSet &psa, const StrokePointSet &psb,
                      const std::vector<DoublePair> *intersection);

  ~StrokesIntersection() {}

  void update(const StrokePointSet &psa, const StrokePointSet &psb,
              const std::vector<DoublePair> &intersections);

  static void wrap(std::vector<double> &is, TStroke *stroke);

  static void computeIntersectionDistances(std::vector<double> &id,
                                           const StrokePointSet &ps,
                                           const std::vector<double> &is);

  StrokesIntersection *swapped() const {
    StrokesIntersection *s = new StrokesIntersection();
    s->m_ida               = m_idb;
    s->m_idb               = m_ida;
    return s;
  }
};

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

StrokesIntersection::StrokesIntersection(
    const StrokePointSet &psa, const StrokePointSet &psb,
    const std::vector<DoublePair> *intersections) {
  if (!psa.stroke || !psb.stroke) return;  // it should never happen
  std::vector<DoublePair> myIntersections;
  if (!intersections) {
    intersections = &myIntersections;
    intersect(psa.stroke, psb.stroke, myIntersections);
  }
  update(psa, psb, *intersections);
}

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

void StrokesIntersection::update(const StrokePointSet &psa,
                                 const StrokePointSet &psb,
                                 const std::vector<DoublePair> &intersections) {
  TStroke *strokea = psa.stroke;
  TStroke *strokeb = psb.stroke;
  m_ida.clear();
  m_idb.clear();

  if (!strokea || !strokeb) return;  // it should never happen

  m_ida.resize(psa.points.size(), -1);
  m_idb.resize(psb.points.size(), -1);

  int n = (int)intersections.size();
  if (n <= 0) return;

  // compute intersection arclengths
  std::vector<double> isa(n), isb(n);
  for (int i = 0; i < n; i++) {
    isa[i] = strokea->getLength(intersections[i].first);
    isb[i] = strokeb->getLength(intersections[i].second);
  }

  if (strokea == strokeb) {
    // una sola stroke. ogni intersezione deve valere per due
    isa.insert(isa.end(), isb.begin(), isb.end());
    std::sort(isa.begin(), isa.end());
    isb = isa;
  } else {
    // due stroke. ordino
    std::sort(isa.begin(), isa.end());
    std::sort(isb.begin(), isb.end());
  }
  if (strokea->isSelfLoop() && !isa.empty()) wrap(isa, strokea);
  if (strokeb->isSelfLoop() && !isb.empty()) wrap(isb, strokeb);
  computeIntersectionDistances(m_ida, psa, isa);
  computeIntersectionDistances(m_idb, psb, isb);
}

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

// the stroke is a self loop. the last intersection is mirrored before s=0
// the first intersection is mirroed after s=length
void StrokesIntersection::wrap(std::vector<double> &is, TStroke *stroke) {
  assert(stroke->isSelfLoop());
  if (!is.empty()) {
    double length = stroke->getLength();
    is.insert(is.begin(), is.back() - length);
    is.push_back(is[1] + length);
  }
}

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

// for each StrokePoint computes the related intersection distance (i.e. the
// distance to
// the closest intersection)
void StrokesIntersection::computeIntersectionDistances(
    std::vector<double> &id, const StrokePointSet &ps,
    const std::vector<double> &is) {
  id.clear();
  id.resize(ps.points.size(), -1);
  int isn = (int)is.size();
  int k   = 0;
  for (int i = 0; i < (int)ps.points.size(); i++) {
    double d = -1;
    if (k < isn) {
      double s = ps.points[i].s;
      while (k + 1 < isn && is[k + 1] < s) k++;
      if (is[k] > s)
        d = is[k] - s;
      else if (k + 1 < isn)
        d = std::min(is[k + 1] - s, s - is[k]);
      else
        d = s - is[k];
    }
    id[i] = d;
  }
}
//---------------------------------------------------------------------------

}  // namespace

//=============================================================================

class TL2LAutocloser::Imp {
public:
  double m_maxDist2;
  std::map<TStroke *, StrokePointSet *> m_strokes;
  std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *>
      m_intersections;

  // debug
  std::pair<StrokePointSet *, StrokePointSet *> m_lastStrokePair;
  std::vector<TL2LAutocloser::Segment> m_segments;

  Imp()
      : m_maxDist2(50 * 50)
      , m_lastStrokePair((StrokePointSet *)0, (StrokePointSet *)0) {}
  ~Imp();

  StrokePointSet *getPointSet(TStroke *stroke) {
    std::map<TStroke *, StrokePointSet *>::iterator it = m_strokes.find(stroke);
    if (it != m_strokes.end()) return it->second;
    StrokePointSet *ps = new StrokePointSet(stroke);
    m_strokes[stroke]  = ps;
    return ps;
  }

  StrokesIntersection *getIntersection(
      TStroke *strokea, TStroke *strokeb,
      const std::vector<DoublePair> *intersection) {
    std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *>::iterator
        it = m_intersections.find(std::make_pair(strokea, strokeb));
    if (it != m_intersections.end()) return it->second;
    StrokesIntersection *si =
        new StrokesIntersection(strokea, strokeb, intersection);
    m_intersections[std::make_pair(strokea, strokeb)] = si;
    m_intersections[std::make_pair(strokeb, strokea)] = si->swapped();
    return si;
  }

  void search(std::vector<TL2LAutocloser::Segment> &segments, TStroke *strokea,
              TStroke *strokeb, const std::vector<DoublePair> *intersection);

  void drawLinks();
  void drawStroke(StrokePointSet *);
  void drawStrokes();
};

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

TL2LAutocloser::Imp::~Imp() {
  std::map<TStroke *, StrokePointSet *>::iterator i;
  for (i = m_strokes.begin(); i != m_strokes.end(); ++i) delete i->second;
  std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *>::iterator j;
  for (j = m_intersections.begin(); j != m_intersections.end(); ++j)
    delete j->second;
  m_strokes.clear();
  m_intersections.clear();
}

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

void TL2LAutocloser::Imp::drawLinks() {
  glColor3d(0, 0, 1);
  glBegin(GL_LINES);
  for (int i = 0; i < (int)m_segments.size(); i++) {
    tglVertex(m_segments[i].p0);
    tglVertex(m_segments[i].p1);
  }
  glEnd();
}

void TL2LAutocloser::Imp::drawStroke(StrokePointSet *ps) {
  if (ps && ps->points.size() >= 2) {
    glBegin(GL_LINES);
    for (int i = 0; i < (int)ps->points.size(); i++)
      tglVertex(ps->points[i].pos);
    glEnd();
  }
}

void TL2LAutocloser::Imp::drawStrokes() {
  if (m_lastStrokePair.first) {
    if (m_lastStrokePair.first == m_lastStrokePair.second) {
      glColor3d(1, 0, 1);
      drawStroke(m_lastStrokePair.first);
    } else {
      glColor3d(1, 0, 0);
      drawStroke(m_lastStrokePair.first);
      glColor3d(0, 1, 0);
      drawStroke(m_lastStrokePair.second);
    }
  }
}

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

// search autoclose segments
void TL2LAutocloser::Imp::search(std::vector<TL2LAutocloser::Segment> &segments,
                                 TStroke *strokea, TStroke *strokeb,
                                 const std::vector<DoublePair> *intersections) {
  m_lastStrokePair.first = m_lastStrokePair.second = 0;
  if (strokea == 0 || strokeb == 0) return;
  /*
#ifdef _WIN32
MyTimer timer;
qDebug() << "search started";
timer.start();
#endif
*/
  bool sameStroke = strokea == strokeb;

  StrokePointSet *psa     = getPointSet(strokea);
  StrokePointSet *psb     = sameStroke ? psa : getPointSet(strokeb);
  m_lastStrokePair.first  = psa;
  m_lastStrokePair.second = psb;
  StrokesIntersection *si = getIntersection(strokea, strokeb, intersections);

  // find links (i.e. best matching point in psb for each point in psa)

  std::vector<std::pair<int, int>> links;
  int na = (int)psa->points.size();
  int nb = (int)psb->points.size();
  int i;
  for (i = 0; i < na; i++) {
    double minDist2 = 0;
    int k           = -1;

    int j0             = 0;
    if (sameStroke) j0 = i + 1;
    for (int j = 0; j < nb; j++) {
      TPointD delta = psb->points[j].pos - psa->points[i].pos;
      double dist2  = norm2(delta);

      if (dist2 > m_maxDist2) continue;
      if (dist2 < 0.000001) continue;

      double dist = sqrt(dist2);
      delta       = delta * (1.0 / dist);
      double cs   = 0.005;

      if ((psa->points[i].crvdir * delta) > -cs) continue;
      if ((psb->points[j].crvdir * delta) < cs) continue;

      if (sameStroke) {
        double ds = fabs(psa->points[i].s - psb->points[j].s);
        if (ds < dist * 1.5) continue;
      }
      if ((si->m_ida[i] > 0 && si->m_ida[i] < dist) ||
          (si->m_idb[j] > 0 && si->m_idb[j] < dist))
        continue;
      if (k < 0 || dist2 < minDist2) {
        k        = j;
        minDist2 = dist2;
      }
    }
    if (k >= 0 && (!sameStroke || k > i) && (0 < i && i < na - 1) &&
        (0 < k && k < nb - 1)) {
      links.push_back(std::make_pair(i, k));
    }
  }

  /*
for(i=0;i<(int)links.size();i++)
{
int ia = links[i].first;
int ib = links[i].second;
double mind2 = norm2(psa->points[ia].pos - psb->points[ib].pos);
TL2LAutocloser::Segment segment;
segment.stroke0 = strokea;
segment.stroke1 = strokeb;
segment.w0 = psa->points[ia].w;
segment.w1 = psb->points[ib].w;
segment.p0 = strokea->getThickPoint(segment.w0);
segment.p1 = strokeb->getThickPoint(segment.w1);
segment.dist2 = mind2;
segments.push_back(segment);
}
*/

  // select best links
  for (i = 0; i < (int)links.size(); i++) {
    int ia       = links[i].first;
    int ib       = links[i].second;
    double d2    = norm2(psa->points[ia].pos - psb->points[ib].pos);
    double mind2 = d2;
    int k        = i;
    while (i + 1 < (int)links.size()) {
      int ia2    = links[i + 1].first;
      int ib2    = links[i + 1].second;
      double d22 = norm2(psa->points[ia2].pos - psb->points[ib2].pos);
      double d2m = (d2 + d22) * 0.5;
      double dsa = psa->points[ia2].s - psa->points[ia].s;
      double dsb = psb->points[ib2].s - psb->points[ib].s;
      if (dsa * dsa > d2m || dsb * dsb > d2m) break;
      if (d22 < mind2) {
        mind2 = d22;
        k     = i + 1;
      }
      ia = ia2;
      ib = ib2;
      d2 = d22;
      i++;
    }
    ia = links[k].first;
    ib = links[k].second;
    TL2LAutocloser::Segment segment;
    segment.stroke0 = strokea;
    segment.stroke1 = strokeb;
    segment.w0      = psa->points[ia].w;
    segment.w1      = psb->points[ib].w;
    segment.p0      = strokea->getThickPoint(segment.w0);
    segment.p1      = strokeb->getThickPoint(segment.w1);
    segment.dist2   = mind2;
    segments.push_back(segment);
  }
  /*
#ifdef _WIN32
double elapsed = timer.elapsedSeconds();
qDebug() << "search completed. time=" << elapsed << "s";
#endif
*/
}

//=============================================================================

TL2LAutocloser::TL2LAutocloser() : m_imp(new Imp()) {}

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

TL2LAutocloser::~TL2LAutocloser() {}

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

void TL2LAutocloser::setMaxDistance2(double dist2) {
  m_imp->m_maxDist2 = dist2;
}

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

double TL2LAutocloser::getMaxDistance2() const { return m_imp->m_maxDist2; }

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

void TL2LAutocloser::search(std::vector<Segment> &segments, TStroke *stroke0,
                            TStroke *stroke1) {
  if (stroke0 && stroke1) m_imp->search(segments, stroke0, stroke1, 0);
}

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

void TL2LAutocloser::search(std::vector<Segment> &segments, TStroke *stroke0,
                            TStroke *stroke1,
                            const std::vector<DoublePair> &intersection) {
  if (stroke0 && stroke1)
    m_imp->search(segments, stroke0, stroke1, &intersection);
}

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

void TL2LAutocloser::draw() {
  m_imp->drawStrokes();
  m_imp->drawLinks();
}
