//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2016 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "skyline.h"
#include "segment.h"

namespace Ms {

static const qreal MAXIMUM_Y = 1000000.0;
static const qreal MINIMUM_Y = -1000000.0;

// #define SKL_DEBUG

#ifdef SKL_DEBUG
#define DP(...)   printf(__VA_ARGS__)
#else
#define DP(...)
#endif


//---------------------------------------------------------
//   add
//---------------------------------------------------------

void Skyline::add(const QRectF& r)
      {
      _north.add(r.x(), r.top(), r.width());
      _south.add(r.x(), r.bottom(), r.width());
      }

//---------------------------------------------------------
//   insert
//---------------------------------------------------------

SkylineLine::SegIter SkylineLine::insert(SegIter i, qreal x, qreal y, qreal w)
      {
      const qreal xr = x + w;
      // Only x coordinate change is handled here as width change gets handled
      // in SkylineLine::add().
      if (i != seg.end() && xr > i->x)
            i->x = xr;
      return seg.emplace(i, x, y, w);
      }

//---------------------------------------------------------
//   append
//---------------------------------------------------------

void SkylineLine::append(qreal x, qreal y, qreal w)
      {
      seg.emplace_back(x, y, w);
      }

//---------------------------------------------------------
//   getApproxPosition
//---------------------------------------------------------

SkylineLine::SegIter SkylineLine::find(qreal x)
      {
      auto it = std::upper_bound(seg.begin(), seg.end(), x, [](qreal x, const SkylineSegment& s) { return x < s.x; });
      if (it == seg.begin())
            return it;
      return (--it);
      }

SkylineLine::SegConstIter SkylineLine::find(qreal x) const
      {
      return const_cast<SkylineLine*>(this)->find(x);
      }

//---------------------------------------------------------
//   add
//---------------------------------------------------------

void SkylineLine::add(const Shape& s)
      {
      for (const auto& r : s)
            add(r);
      }

void SkylineLine::add(const QRectF& r)
      {
      if (north)
            add(r.x(), r.top(), r.width());
      else
            add(r.x(), r.bottom(), r.width());
      }

void Skyline::add(const Shape& s)
      {
      for (const auto& r : s)
            add(r);
      }

void SkylineLine::add(qreal x, qreal y, qreal w)
      {
//      Q_ASSERT(w >= 0.0);
      if (x < 0.0) {
            w -= -x;
            x = 0.0;
            if (w <= 0.0)
                  return;
            }

      DP("===add  %f %f %f\n", x, y, w);

      SegIter i = find(x);
      qreal cx = seg.empty() ? 0.0 : i->x;
      for (; i != seg.end(); ++i) {
            qreal cy = i->y;
            if ((x + w) <= cx)                                          // A
                  return; // break;
            if (x > (cx + i->w)) {                                      // B
                  cx += i->w;
                  continue;
                  }
            if ((north && (cy <= y)) || (!north && (cy >= y))) {
                  cx += i->w;
                  continue;
                  }
            if ((x >= cx) && ((x+w) < (cx+i->w))) {                     // (E) insert segment
                  DP("    insert at %f %f   x:%f w:%f\n", cx, i->w, x, w);
                  qreal w1 = x - cx;
                  qreal w2 = w;
                  qreal w3 = i->w - (w1 + w2);
                  if (w1 > 0.0000001) {
                        i->w = w1;
                        ++i;
                        i = insert(i, x, y, w2);
                        DP("       A w1 %f w2 %f\n", w1, w2);
                        }
                  else {
                        i->w = w2;
                        i->y = y;
                        DP("       B w2 %f\n", w2);
                        }
                  if (w3 > 0.0000001) {
                        ++i;
                        DP("       C w3 %f\n", w3);
                        insert(i, x + w2, cy, w3);
                        }
                  return;
                  }
            else if ((x <= cx) && ((x + w) >= (cx + i->w))) {               // F
                  DP("    change(F) cx %f y %f\n", cx, y);
                  i->y = y;
                  }
            else if (x < cx) {                                          // C
                  qreal w1 = x + w - cx;
                  i->w    -= w1;
                  DP("    add(C) cx %f y %f w %f w1 %f\n", cx, y, w1, i->w);
                  insert(i, cx, y, w1);
                  return;
                  }
            else {                                                      // D
                  qreal w1 = x - cx;
                  qreal w2 = i->w - w1;
                  if (w2 > 0.0000001) {
                        i->w = w1;
                        cx  += w1;
                        DP("    add(D) %f %f\n", y, w2);
                        ++i;
                        i = insert(i, cx, y, w2);
                        }
                  }
            cx += i->w;
            }
      if (x >= cx) {
            if (x > cx) {
                  qreal cy = north ? MAXIMUM_Y : MINIMUM_Y;
                  DP("    append1 %f %f\n", cy, x - cx);
                  append(cx, cy, x - cx);
                  }
            DP("    append2 %f %f\n", y, w);
            append(x, y, w);
            }
      else if (x + w > cx)
            append(cx, y, x + w - cx);
      }

//---------------------------------------------------------
//   clear
//---------------------------------------------------------

void Skyline::clear()
      {
      _north.clear();
      _south.clear();
      }

//-------------------------------------------------------------------
//   minDistance
//    a is located below this skyline.
//    Calculates the minimum distance between two skylines
//-------------------------------------------------------------------

qreal Skyline::minDistance(const Skyline& s) const
      {
      return south().minDistance(s.north());
      }

qreal SkylineLine::minDistance(const SkylineLine& sl) const
      {
      qreal dist = MINIMUM_Y;

      qreal x1 = 0.0;
      qreal x2 = 0.0;
      auto k   = sl.begin();
      for (auto i = begin(); i != end(); ++i) {
            while (k != sl.end() && (x2 + k->w) < x1) {
                  x2 += k->w;
                  ++k;
                  }
            if (k == sl.end())
                  break;
            for (;;) {
                  if ((x1 + i->w > x2) && (x1 < x2 + k->w))
                        dist = qMax(dist, i->y - k->y);
                  if (x2 + k->w < x1 + i->w) {
                        x2 += k->w;
                        ++k;
                        if (k == sl.end())
                              break;
                        }
                  else
                        break;
                  }
            if (k == sl.end())
                  break;
            x1 += i->w;
            }
      return dist;
      }

//---------------------------------------------------------
//   paint
//---------------------------------------------------------

void Skyline::paint(QPainter& p) const
      {
      p.save();

      p.setBrush(Qt::NoBrush);
      QMatrix matrix = p.matrix();
      p.setPen(QPen(QBrush(Qt::darkYellow), 2.0 / matrix.m11()));
      _north.paint(p);
      p.setPen(QPen(QBrush(Qt::green), 2.0 / matrix.m11()));
      _south.paint(p);
      p.restore();
      }

void SkylineLine::paint(QPainter& p) const
      {
      qreal x1 = 0.0;
      qreal x2;
      qreal y = 0.0;

      bool pvalid = false;
      for (const SkylineSegment& s : *this) {
            x2 = x1 + s.w;
            if (valid(s)) {
                  if (pvalid)
                        p.drawLine(QLineF(x1, y, x1, s.y));
                  y  = s.y;
                  p.drawLine(QLineF(x1, y, x2, y));
                  pvalid = true;
                  }
            else
                  pvalid = false;
            x1 = x2;
            }
      }

bool SkylineLine::valid(const SkylineSegment& s) const
      {
      return north ? (s.y != MAXIMUM_Y) : (s.y != MINIMUM_Y);
      }

//---------------------------------------------------------
//   dump
//---------------------------------------------------------

void Skyline::dump(const char* p, bool n) const
      {
      printf("Skyline dump: %p %s\n", this, p);
      if (n)
            _north.dump();
      else
            _south.dump();
      }

void SkylineLine::dump() const
      {
      qreal x = 0.0;
      for (const SkylineSegment& s : *this) {
            printf("   x %f y %f w %f\n", x, s.y, s.w);
            x += s.w;
            }
      }

//---------------------------------------------------------
//   max
//---------------------------------------------------------

qreal SkylineLine::max() const
      {
      qreal val;
      if (north) {
            val = MAXIMUM_Y;
            for (const SkylineSegment& s : *this)
                  val = qMin(val, s.y);
            }
      else {
            val = MINIMUM_Y;
            for (const SkylineSegment& s : *this)
                  val = qMax(val, s.y);
            }
      return val;
      }


} // namespace Ms

