//=============================================================================
//  MusE Reader
//  Music Score Reader
//
//  Copyright (C) 2010-2011 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.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include "omrpage.h"
#include "image.h"
#include "utils.h"
#include "omr.h"
#ifdef OCR
#include "ocr.h"
#endif
#include "libmscore/score.h"
#include "libmscore/text.h"
#include "libmscore/measurebase.h"
#include "libmscore/box.h"
#include "libmscore/sym.h"
#include "libmscore/note.h"
#include "pattern.h"

namespace Ms {
    //static const double noteTH = 1.0;
    static const double timesigTH = 0.7;
    //static const double clefTH = 0.7;
    static const double keysigTH = 0.8;

    struct Hv {
        int x;
        int val;
        Hv(int a, int b) : x(a), val(b) {}
        bool operator< (const Hv& a) const { return a.val < val; }
    };

struct Peak {
      int x;
      double val;
      int sym;

      bool operator<(const Peak& p) const { return p.val < val; }
      Peak(int _x, double _val) : x(_x), val(_val) {}
      Peak(int _x, double _val, int s) : x(_x), val(_val), sym(s) {}
      };

//---------------------------------------------------------
//   Lv
//    line + value
//---------------------------------------------------------

struct Lv {
      int line;
      double val;
      Lv(int a, double b) : line(a), val(b) {}
      bool operator< (const Lv& a) const { return a.val < val; }
      };

//---------------------------------------------------------
//   OmrPage
//---------------------------------------------------------

OmrPage::OmrPage(Omr* parent)
      {
      _omr = parent;
      cropL = cropR = cropT = cropB = 0;
      }

//---------------------------------------------------------
//   dot
//---------------------------------------------------------

bool OmrPage::dot(int x, int y) const
      {
      const uint* p = scanLine(y) + (x / 32);
      return (*p) & (0x1 << (x % 32));
      }

//---------------------------------------------------------
//   isBlack
//---------------------------------------------------------

bool OmrPage::isBlack(int x, int y) const
      {
      QRgb c = _image.pixel(x,y);
      return (qGray(c) < 100);
      }


//---------------------------------------------------------
//   read
//---------------------------------------------------------

void OmrPage::read()
      {
      //removeBorder();
      crop();
      slice();
      deSkew();
      crop();
      slice();
      getStaffLines();
      getRatio();
      }

struct SysState {
      int status;// 0 for start_staff, 1 for end_staff
      int index;//staff index
      };


struct BAR_STATE {
      int x;
      int status;//0 represents white space, 1 represents bar
      };

//---------------------------------------------------------
//   searchBarLines
//---------------------------------------------------------
float OmrPage::searchBarLines(int start_staff, int end_staff)
      {
      OmrStaff& r1 = staves[start_staff];
      OmrStaff& r2 = staves[end_staff];

      int x1 = r1.x();
      int x2 = x1 + r1.width();
      int y1 = r1.y();
      int y2 = r2.y() + r2.height();

      int th = 0;
      for (int i = start_staff; i <= end_staff; ++i) {
            th += staves[i].height() / 2;
            }

      int vpw = x2 - x1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
      float vp[vpw];
      memset(vp, 0, sizeof(float) * vpw);

      //using note constraints
      //searchNotes();

      int note_constraints[x2 - x1];
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<float> vp(vpw);      // Default-initialized, doesn't need to be cleared
      std::vector<int> note_constraints(x2 - x1);
#endif
      for (OmrNote* n : r1.notes()) {
            for(int x = n->x(); x <= n->x() + n->width(); ++x)
                  note_constraints[x - x1] = 1;
            }
      for (OmrNote* n : r2.notes()) {
            for(int x = n->x(); x <= n->x() + n->width(); ++x)
                  note_constraints[x - x1] = 1;
            }

      //
      // compute vertical projections
      //
      for (int x = x1; x < x2; ++x) {
            int dots = 0;
            for (int y = y1; y < y2; ++y) {
                  if (this->dot(x, y))
                        ++dots;
                  }
            if (!note_constraints[x - x1])
                  vp[x - x1] = dots - th;
            else
                  vp[x - x1] = -HUGE_VAL;
            }

#if (!defined (_MSCVER) && !defined (_MSC_VER))
      float scores[x2 - x1 + 1][2];
      BAR_STATE pred[x2 - x1 + 1][2];
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<float[2]> scores(x2 - x1 + 1);
      std::vector<BAR_STATE[2]> pred(x2 - x1 + 1);
#endif
      BAR_STATE bs;

      //initialization
      bs.x = -1; bs.status = -1;
      for (int x = x1; x <= x2; ++x) {
            int i = x - x1;
            for (int status = 0; status <= 1; ++status) {
                  scores[i][status] = -HUGE_VAL;
                  pred[i][status] = bs;
                  }
            }
      scores[0][0] = 0;

      //forward pass
      for (int x = x1; x <= x2; ++x) {
            int i = x - x1;
            for (int cur = 0; cur <= 1; ++cur) {
                  //current state
                  if (scores[i][cur] < -1000)
                        continue;

                  if (cur) {
                        scores[i][cur] += vp[i];
                        int next = 0;
                        int step = 3 * _spatium;//constraints between adjacent barlines
                        if (step + i <= x2 - x1) {
                              if (scores[step + i][next] < scores[i][cur]) {
                                    scores[step + i][next] = scores[i][cur];
                                    bs.x = x; bs.status = cur;
                                    pred[step + i][next] = bs;
                                    }
                              }
                        }
                  else {
                        for (int next = 0; next <= 1; ++next) {
                              int step = 1;
                              if (step + i <= x2 - x1) {
                                    if (scores[step + i][next] < scores[i][cur]) {
                                          scores[step + i][next] = scores[i][cur];
                                          bs.x = x; bs.status = cur;
                                          pred[step + i][next] = bs;
                                          }
                                    }
                              }
                        }
                  }
            }
      return(scores[x2][0]);
      }

//---------------------------------------------------------
//   identifySystems
//---------------------------------------------------------
void OmrPage::identifySystems()
      {
      int numStaves = staves.size();
      if(numStaves == 0) return;

      //
      //memory allocation
      //
      float **temp_scores = new float*[numStaves];
      for (int i = 0; i < numStaves; i++)
            temp_scores[i] = new float[numStaves];

      int **hashed = new int*[numStaves];
      for (int i = 0; i < numStaves; i++)
            hashed[i] = new int[numStaves];

      float **scores = new float*[numStaves];
      for (int i = 0; i < numStaves; i++)
            scores[i] = new float[2];
      SysState **pred = new SysState*[numStaves];
      for (int i = 0; i < numStaves; i++)
            pred[i] = new SysState[2];

      //
      //initialization
      //
      for (int i = 0; i < numStaves; i++) {
            for (int j = 0; j < numStaves; j++) {
                  hashed[i][j] = 0;
                  }
            }

      SysState ss;
      ss.index = -1; ss.status = -1;

      for (int i = 0; i < numStaves; ++i) {
            for (int j = 0; j < 2; j++) {
                  scores[i][j] = -HUGE_VAL;
                  pred[i][j] = ss;
                  }
            }
      scores[0][0] = 0;

      //
      //identify solid note heads
      //
      int **note_labels = new int*[numStaves];
      for (int i = 0; i < numStaves; i++)
            note_labels[i] = new int[width()];
      for (int i = 0; i < numStaves; i++){
            for (int j = 0; j < width(); j++){
                  note_labels[i][j] = 0;
                  }
            }

      //
      // search notes for each system
      //
//      int note_ran = spatium()/4.0;
//      for (int i = 0; i < numStaves; i++){
//            OmrSystem omrSystem(this);
//            omrSystem.staves().append(staves[i]);
//            omrSystem.searchNotes(note_labels[i], note_ran);
//            }

      //
      // System Identification
      //
      int status;
      float cur_score;
      int cur_staff,next_staff;
      for (cur_staff = 0; cur_staff < numStaves; ++cur_staff) {
            status = 0;//start_staff
            for (next_staff = cur_staff; next_staff < numStaves; ++next_staff) { //connects to end_staff
                  if (hashed[cur_staff][next_staff]) {
                        cur_score = temp_scores[cur_staff][next_staff];
                        }
                  else {
                        //evaluate staff segment [c,n], dp here
                        OmrSystem omrSystem(this);
                        for (int i = cur_staff; i <= next_staff; ++i) {
                              omrSystem.staves().append(staves[i]);
                              }

                        cur_score = omrSystem.searchBarLinesvar(next_staff - cur_staff + 1, note_labels + cur_staff);
                        temp_scores[cur_staff][next_staff] = cur_score;
                        hashed[cur_staff][next_staff] = 1;
                        }

                  //forward pass
                  if (scores[cur_staff][status] + cur_score > scores[next_staff][1 - status]) {
                        scores[next_staff][1 - status] = scores[cur_staff][status] + cur_score;
                        ss.status = status; ss.index = cur_staff;
                        pred[next_staff][1 - status] = ss;
                        }
                  }

            status = 1;//end_staff
            next_staff = cur_staff + 1;
            if (next_staff < numStaves) {
                  //forward pass
                  if (scores[cur_staff][status] > scores[next_staff][1 - status]) {
                        scores[next_staff][1 - status] = scores[cur_staff][status];
                        ss.status = status; ss.index = cur_staff;
                        pred[next_staff][1 - status] = ss;
                        }
                  }
            }

      //backtrack
      status = 1;//end_staff
      cur_staff = numStaves - 1;//last staff index
      while (cur_staff > 0 || status != 0) {
            ss = pred[cur_staff][status];

            if (!ss.status) {
                  //add system here
                  OmrSystem omrSystem(this);
                  for (int i = ss.index; i <= cur_staff; ++i) {
                        omrSystem.staves().append(staves[i]);
                        }
                  omrSystem.searchBarLinesvar(cur_staff - ss.index + 1, note_labels + ss.index);
                  _systems.append(omrSystem);
                  }
            cur_staff = ss.index;
            status = ss.status;
            }

      int systems = _systems.size();
      for (int i = 0; i < systems; ++i) {
            OmrSystem* system = &_systems[i];
            int n = system->barLines.size();
            for (int k = 0; k < n - 1; ++k) {
                  const QLine& l1 = system->barLines[k];
                  const QLine& l2 = system->barLines[k + 1];
                  OmrMeasure m(l1.x1(), l2.x1());
                  system->measures().append(m);
                  }
            }

      //delete allocated space
      for (int i = 0; i < numStaves; i++) {
            delete[] scores[i];
            delete[] pred[i];
            delete[] temp_scores[i];
            delete[] hashed[i];
            delete[] note_labels[i];
            }

      delete[] note_labels;
      delete[] scores;
      delete[] pred;
      delete[] temp_scores;
      delete[] hashed;
      }


//---------------------------------------------------------
//   readBarLines
//---------------------------------------------------------

void OmrPage::readBarLines()
      {
      //QFuture<void> bl = QtConcurrent::run(_systems, &OmrSystem::searchSysBarLines());
      //bl.waitForFinished();

      //int numStaves    = staves.size();
      int systems = _systems.size();

      for (int i = 0; i < systems; ++i) {
            OmrSystem* system = &_systems[i];
            int n = system->barLines.size();
            for (int k = 0; k < n - 1; ++k) {
                  const QLine& l1 = system->barLines[k];
                  const QLine& l2 = system->barLines[k + 1];
                  OmrMeasure m(l1.x1(), l2.x1());
                  for (int ss = 0; ss < system->staves().size(); ++ss) {
                        OmrStaff& staff = system->staves()[ss];
                        QList<OmrChord> chords;
                        int nx = 0;
                        SymId nsym = SymId::noSym;
                        OmrChord chord;
                        for(OmrNote* n1 : staff.notes()) {
                              int x = n1->x();
                              if (x >= m.x2())
                                    break;
                              if (x >= m.x1() && x < m.x2()) {
                                    if (qAbs(x - nx) > int(_spatium / 2) || (nsym != n1->sym)) {
                                          if (!chord.notes.isEmpty()) {
                                                SymId sym = chord.notes.front()->sym;
                                                if (sym == SymId::noteheadBlack)
                                                      chord.duration.setType(TDuration::DurationType::V_QUARTER);
                                                else if (sym == SymId::noteheadHalf)
                                                      chord.duration.setType(TDuration::DurationType::V_HALF);
                                                chords.append(chord);
                                                chord.notes.clear();
                                                }
                                          }
                                    nx = x;
                                    nsym = n1->sym;
                                    chord.notes.append(n1);
                                    }
                              }
                        if (!chord.notes.isEmpty()) {
                              SymId sym = chord.notes.front()->sym;
                              if (sym == SymId::noteheadBlack)
                                    chord.duration.setType(TDuration::DurationType::V_QUARTER);
                              else if (sym == SymId::noteheadHalf)
                                    chord.duration.setType(TDuration::DurationType::V_HALF);
                              chords.append(chord);
                              }
                        m.chords().append(chords);
                        }
                  system->measures().append(m);
                  }
            }

      }

//---------------------------------------------------------
//   searchClef
//---------------------------------------------------------

OmrClef OmrPage::searchClef(OmrSystem* system, OmrStaff* staff)
      {
      std::vector<Pattern*> pl = {
            Omr::trebleclefPattern,
            Omr::bassclefPattern
            };
      const OmrMeasure& m = system->measures().front();
//printf("search clef %d   %d-%d\n", staff->y(), m.x1(), m.x2());

      int x1 = m.x1() + 2;
      int x2 = x1 + (m.x2() - x1) / 2;
      OmrPattern p = searchPattern(pl, staff->y(), x1, x2);

      OmrClef clef(p);
      if (p.sym == SymId::gClef)
            clef.type = ClefType::G;
      else if (p.sym == SymId::fClef)
            clef.type = ClefType::F;
      else
            clef.type = ClefType::G;
      return clef;
      }

//---------------------------------------------------------
//   searchPattern
//---------------------------------------------------------

OmrPattern OmrPage::searchPattern(const std::vector<Pattern*>& pl, int y, int x1, int x2)
      {
      OmrPattern p;
      p.sym = SymId::noSym;
      p.prob = 0.0;
      for (Pattern* pattern : pl) {
            double val = 0.0;
            int xx = 0;
            int hw = pattern->w();

            for (int x = x1; x < (x2 - hw); ++x) {
                  double val1 = pattern->match(&image(), x - pattern->base().x(), y - pattern->base().y());
                  if (val1 > val) {
                        val = val1;
                        xx = x;
                        }
                  }

            if (val > p.prob) {
                  p.setRect(xx, y, pattern->w(), pattern->h());
                  p.sym = pattern->id();
                  p.prob = val;
                  }
//printf("Pattern found %d %f %d\n", int(pattern->id()), val, xx);
            }
      return p;
      }

//---------------------------------------------------------
//   searchTimeSig
//---------------------------------------------------------

OmrTimesig* OmrPage::searchTimeSig(OmrSystem* system)
      {
      int z = -1;
      int n = -1;
      double zval = 0;
      double nval = 0;
      QRect rz, rn;

      int y         = system->staves().front().y();
      OmrMeasure* m = &system->measures().front();
      int x1        = m->x1();

      for (int i = 0; i < 10; ++i) {
            Pattern* pattern = Omr::timesigPattern[i];
            double val = 0.0;
            int hh = pattern->h();
            int hw = pattern->w();
            int x2 = m->x2() - hw;
            QRect r;

            for (int x = x1; x < x2; ++x) {
                  double val1 = pattern->match(&image(), x, y);
                  if (val1 > val) {
                        val = val1;
                        r = QRect(x, y, hw, hh);
                        }
                  }

            if (val > timesigTH && val > zval) {
                  z = i;
                  zval = val;
                  rz = r;
                  }
//printf("   found %d %f\n", i, val);
            }

      if (z < 0)
            return 0;
      y = system->staves().front().y() + lrint(_spatium * 2);

      x1 = rz.x();
      int x2 = x1 + 1;
      OmrTimesig* ts = 0;
      for (int i = 0; i < 10; ++i) {
            Pattern* pattern = Omr::timesigPattern[i];
            double val = 0.0;
            int hh = pattern->h();
            int hw = pattern->w();
            QRect r;

            for (int x = x1; x < x2; ++x) {
                  double val1 = pattern->match(&image(), x, y);
                  if (val1 > val) {
                        val = val1;
                        r = QRect(x, y, hw, hh);
                        }
                  }

            if (val > timesigTH && val > nval) {
                  n = i;
                  nval = val;
                  rn = r;
                  }
//printf("   found %d %f\n", i, val);
            }
      if (n > 0) {
            ts = new OmrTimesig(rz | rn);
            ts->timesig = Fraction(z, n);
//printf("timesig  %d/%d\n", z, n);
            }
      return ts;
      }

//---------------------------------------------------------
//   searchKeySig
//---------------------------------------------------------

void OmrPage::searchKeySig(OmrSystem* system, OmrStaff* staff)
      {
      Pattern* pl[2];
      pl[0] = Omr::sharpPattern;
      pl[1] = Omr::flatPattern;

      double zval = 0;

      int y = system->staves().front().y();
      OmrMeasure* m = &system->measures().front();
      int x1 = m->x1();

      for (int i = 0; i < 2; ++i) {
            Pattern* pattern = pl[i];
            double val = 0.0;
            int hh = pattern->h();
            int hw = pattern->w();
            int x2 = m->x2() - hw;
            QRect r;

            for (int x = x1; x < x2; ++x) {
                  double val1 = pattern->match(&image(), x, y - hh / 2);
                  if (val1 > val) {
                        val = val1;
                        r = QRect(x, y, hw, hh);
                        }
                  }

            if (val > keysigTH && val > zval) {
                  zval = val;
                  OmrKeySig key(r);
                  key.type = i == 0 ? 1 : -1;
                  staff->setKeySig(key);
                  }
//printf(" key found %d %f\n", i, val);
            }
      }

//---------------------------------------------------------
//   maxP
//---------------------------------------------------------

int maxP(int* projection, int x1, int x2)
      {
      int xx = x1;
      int max = 0;
      for (int x = x1; x < x2; ++x) {
            if (projection[x] > max) {
                  max = projection[x];
                  xx = x;
                  }
            }
      return xx;
      }

//---------------------------------------------------------
//   BSTATE
//---------------------------------------------------------
struct BSTATE {
      int x;
      int status;//0 represents white space, 1 represents bar
      };


//---------------------------------------------------------
//   searchSysBarLines
//---------------------------------------------------------

void OmrSystem::searchSysBarLines()
      {
      OmrStaff& r1 = _staves[0];
      OmrStaff& r2 = _staves[_staves.size() - 1];//[1];

      int x1 = r1.x();
      int x2 = x1 + r1.width();
      int y1 = r1.y();
      int y2 = r2.y() + r2.height();
      int h = y2 - y1 + 1;
      int th = /*r1.height() + r2.height() - 5;*/ h / 2;     // threshold, data score for null model

      int vpw = x2 - x1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
      float vp[vpw];
      memset(vp, 0, sizeof(float) * vpw);

      //using note constraints
      searchNotes();

      int note_constraints[x2 - x1];
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<float> vp(vpw);      // Default-initialized, doesn't need to be cleared
      
      //using note constraints
      searchNotes();

      std::vector<int> note_constraints(x2 - x1);
#endif
      for (int i = 0; i < _staves.size(); i++) {
            OmrStaff& r = _staves[i];
            for (OmrNote* n : r.notes()) {
                  for (int x = n->x(); x <= n->x() + n->width(); ++x)
                        note_constraints[x - x1] = 1;
                  }
            }

      //
      // compute vertical projections
      //
      for (int x = x1; x < x2; ++x) {
            int dots = 0;
            for (int y = y1; y < y2; ++y) {
                  if (_page->dot(x, y))
                        ++dots;
                  }
            if (!note_constraints[x - x1])
                  vp[x - x1] = dots - th;
            else
                  vp[x - x1] = -HUGE_VAL;
            }

#if (!defined (_MSCVER) && !defined (_MSC_VER))
      float scores[x2 - x1 + 1][2];
      BSTATE pred[x2 - x1 + 1][2];
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<float[2]> scores(x2 - x1 + 1);
      std::vector<BSTATE[2]> pred(x2 - x1 + 1);
#endif
      BSTATE bs;

      //initialization
      bs.x = -1; bs.status = -1;
      for (int x = x1; x <= x2; ++x) {
            int i = x - x1;
            for (int status = 0; status <= 1; ++status) {
                  scores[i][status] = -HUGE_VAL;
                  pred[i][status] = bs;
                  }
            }
      scores[0][0] = 0;

      //forward pass
      for (int x = x1; x < x2; ++x) {
            int i = x - x1;
            for (int cur = 0; cur <= 1; ++cur) {
                  //current state
                  if (scores[i][cur] < -1000) continue;

                  if (cur) {
                        scores[i][cur] += vp[i];
                        int next = 0;
                        int step = 3 * _page->spatium();//constraints between adjacent barlines
                        if (step + i <= x2 - x1) {
                              if (scores[step + i][next] < scores[i][cur]) {
                                    scores[step + i][next] = scores[i][cur];
                                    bs.x = x; bs.status = cur;
                                    pred[step + i][next] = bs;
                                    }
                              }
                        }
                  else {
                        for (int next = 0; next <= 1; ++next) {
                              int step = 1;
                              if (step + i <= x2 - x1) {
                                    if(scores[step + i][next] < scores[i][cur]) {
                                          scores[step + i][next] = scores[i][cur];
                                          bs.x = x; bs.status = cur;
                                          pred[step + i][next] = bs;
                                          }
                                    }
                              }
                        }
                  }
            }

      //trace back
      int state = 0;
      for (int x = x2; x > x1; ) {
            int i = x - x1;
            bs = pred[i][state];
            state = bs.status;
            x = bs.x;

            if(state)
                  barLines.append(QLine(x, y1, x, y2));
            }
      }

//-------------------------------------------------------------------
//   searchBarLinesvar: dynamic programming for system identification
//-------------------------------------------------------------------
float OmrSystem::searchBarLinesvar(int n_staff, int **note_labels)
      {
      OmrStaff& r1 = _staves[0];
      OmrStaff& r2 = _staves[n_staff - 1];

      int x1 = r1.left();
      int x2 = r1.right();
      int y1 = r1.top();
      int y2 = r2.bottom();

      int th = (y2 - y1) / 2;//threshold

      //
      //compute note constraints
      //
      int *note_constraints = new int[x2 - x1 + 1];
      memset(note_constraints, 0, sizeof(int) * (x2 - x1 + 1));
      for (int i = 0; i < n_staff; ++i) {
            for (int j = x1; j <= x2; ++j) {
                  if (note_labels[i][j] == 1)
                        note_constraints[j - x1] = 1;
                  }
            }

      //
      // compute vertical projections
      //

      int vpw = x2 - x1 + 1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
      float vp[vpw];

      for(int i = 0; i < vpw; i++) vp[i] = -HUGE_VAL;
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<float> vp(vpw, -HUGE_VAL);       // auto-initialized to -HUGE_VAL, doesn't need the loop
#endif


      int bar_max_width = 3;
      for (int x = x1 + bar_max_width; x < x2 - bar_max_width; ++x) {
            int dots = 0;
            for (int y = y1; y < y2; ++y) {
                  for (int w = -bar_max_width; w <= bar_max_width; w++) {
                        if (_page->dot(x + w, y)) {
                              ++dots;
                              break;
                              }
                        }
                  }

            if (!note_constraints[x - x1])
                  vp[x - x1] = dots - th;
            else
                  vp[x - x1] = -HUGE_VAL;
            }

      float **scores = new float *[x2 - x1 + 1];
      BSTATE **pred = new BSTATE *[x2 - x1 + 1];
      for (int i = 0; i< x2 - x1 + 1; i++) {
            scores[i] = new float[2];
            pred[i] = new BSTATE[2];
            }

      BSTATE bs;

      //initialization
      bs.x = -1; bs.status = -1;
      for (int x = x1; x <= x2; ++x) {
            int i = x - x1;
            for (int status = 0; status <= 1; ++status) {
                  scores[i][status] = -HUGE_VAL;
                  pred[i][status] = bs;
                  }
            }
      scores[0][0] = 0;

      //forward pass

      for (int x = x1; x <= x2; ++x) {
            int i = x - x1;

            for(int cur = 0; cur <= 1; ++cur) {
                  //current state
                  if (scores[i][cur] < -1000)
                        continue;

                  if (cur) {
                        scores[i][cur] += vp[i];
                        int next = 0;
                        int step = 8 * _page->spatium();//minimum distance between adjacent barlines
                        if (step + i <= x2 - x1) {
                              if (scores[step + i][next] < scores[i][cur]) {
                                    scores[step + i][next] = scores[i][cur];
                                    bs.x = x; bs.status = cur;
                                    pred[step + i][next] = bs;
                                    }
                              }
                        else {
                              if (scores[x2 - x1][next] < scores[i][cur]) {
                                    scores[x2 - x1][next] = scores[i][cur];
                                    bs.x = x; bs.status = cur;
                                    pred[x2 - x1][next] = bs;
                                    }
                              }

                        }
                  else {
                        for (int next = 0; next <= 1; ++next) {
                              int step = 1;
                              if (step + i <= x2 - x1) {
                                    if (scores[step + i][next] < scores[i][cur]) {
                                          scores[step + i][next] = scores[i][cur];
                                          bs.x = x; bs.status = cur;
                                          pred[step + i][next] = bs;
                                          }
                                    }
                              }
                        }
                  }
            }

      //trace back
      int state = 0;

      for (int x = x2; x > x1; ) {
            int i = x - x1;
            bs = pred[i][state];
            state = bs.status;
            x = bs.x;

            if(state) {
                  barLines.append(QLine(x, y1, x, y2));
                  }
            }

      float best_score = scores[x2 - x1][0];

      delete []note_constraints;
      for (int i = 0; i< x2 - x1 + 1; i++) {
            delete []scores[i];
            delete []pred[i];
            }
      delete []scores;
      delete []pred;


      return(best_score);
      }

//---------------------------------------------------------
//   noteCompare
//---------------------------------------------------------

static bool noteCompare(OmrNote* n1, OmrNote* n2)
      {
      return n1->x() < n2->x();
      }

static bool intersectFuzz(const QRect& a, const QRect& b, int fuzz)
      {
      int xfuzz = fuzz;
      int yfuzz = fuzz;
      if (a.x() > b.x())
            xfuzz = -xfuzz;
      if (a.y() > b.y())
            yfuzz = -yfuzz;

      return (a.intersects(b.translated(xfuzz, yfuzz)));
      }

//---------------------------------------------------------
//   searchNotes
//---------------------------------------------------------

void OmrSystem::searchNotes()
      {
      for (int i = 0; i < _staves.size(); ++i) {
            OmrStaff* r = &_staves[i];
            int x1 = r->x();
            int x2 = x1 + r->width();

            //
            // search notes on a range of vertical line position
            //
            for (int line = 0; line < 8; ++line)
                  searchNotes(&r->notes(), x1, x2, r->y(), line);

            //
            // detect collisions
            //
            int fuzz = int(_page->spatium()) / 2;
            for(OmrNote* n : r->notes()) {
                  for(OmrNote* m : r->notes()) {
                        if (m == n)
                              continue;
                        if (intersectFuzz(*m, *n, fuzz)) {
                              if (m->prob > n->prob)
                                    r->notes().removeOne(n);
                              else
                                    r->notes().removeOne(m);
                              }
                        }
                  }
            qSort(r->notes().begin(), r->notes().end(), noteCompare);
            }
      }

//---------------------------------------------------------
//   searchNotes
//---------------------------------------------------------

void OmrSystem::searchNotes(int *note_labels, int ran)
      {
      for (int i = 0; i < _staves.size(); ++i) {
            OmrStaff* r = &_staves[i];
            int x1 = r->x();
            int x2 = x1 + r->width();

            //
            // search notes on a range of vertical line position
            //
            for (int line = -5; line < 14; ++line)
                  searchNotes(&r->notes(), x1, x2, r->y(), line);

            //
            // save detected note horizontal positions into note_labels
            //
            for(OmrNote* n : r->notes()) {
                  QPoint p = n->center();
                  int h_cent = p.x();
                  for(int h = h_cent - ran; h <= h_cent + ran; h++){
                        if(h >= x1 && h < x2) note_labels[h] = 1;
                        }
                  }
            }
      }

//---------------------------------------------------------
//   addText
//---------------------------------------------------------

#ifdef OCR
static void addText(Score* score, int subtype, const QString& s)
{
#if 0 //TODO-1
      MeasureBase* measure = score->first();
      if (measure == 0 || measure->type() != Element::VBOX) {
            measure = new VBox(score);
            measure->setNext(score->first());
            measure->setTick(0);
            score->add(measure);
            }
      Text* text = new Text(score);
      switch(subtype) {
            case TEXT_TITLE:    text->setTextStyle(TEXT_STYLE_TITLE);    break;
            case TEXT_SUBTITLE: text->setTextStyle(TEXT_STYLE_SUBTITLE); break;
            case TEXT_COMPOSER: text->setTextStyle(TEXT_STYLE_COMPOSER); break;
            case TEXT_POET:     text->setTextStyle(TEXT_STYLE_POET);     break;
            }
      text->setSubtype(subtype);
      text->setText(s);
      measure->add(text);
#endif
      }
#endif

//---------------------------------------------------------
//   readHeader
//---------------------------------------------------------

void OmrPage::readHeader(Score*)
      {
      if (_slices.isEmpty())
            return;
      double maxHeight = _spatium * 4 * 2;

      int slice = 0;
      double maxH = 0.0;
      //      int maxIdx;
      for (; slice < _slices.size(); ++slice) {
            double h = _slices[slice].height();

            if (h > maxHeight)
                  break;
            if (h > maxH) {
                  maxH = h;
                  //                  maxIdx = slice;
                  }
            }
#ifdef OCR
      //
      // assume that highest slice contains header text
      //
      OcrImage img = OcrImage(_image.bits(), _slices[maxIdx], (_image.width() + 31) / 32);
      QString s = _omr->ocr()->readLine(img).trimmed();
      if (!s.isEmpty())
            score->addText("title", s);

      QString subTitle;
      for (int i = maxIdx + 1; i < slice; ++i) {
            OcrImage img = OcrImage(_image.bits(), _slices[i], (_image.width() + 31) / 32);
            QString s = _omr->ocr()->readLine(img).trimmed();
            if (!s.isEmpty()) {
                  if (!subTitle.isEmpty())
                        subTitle += "\n";
                  subTitle += s;
                  }
            }
      if (!subTitle.isEmpty())
            score->addText("subtitle", subTitle);
#endif
#if 0
      OcrImage img = OcrImage(_image.bits(), _slices[0], (_image.width() + 31) / 32);
      QString s = _omr->ocr()->readLine(img).trimmed();
      if (!s.isEmpty())
            addText(score, TEXT_TITLE, s);

      img = OcrImage(_image.bits(), _slices[1], (_image.width() + 31) / 32);
      s = _omr->ocr()->readLine(img).trimmed();
      if (!s.isEmpty())
            addText(score, TEXT_SUBTITLE, s);

      img = OcrImage(_image.bits(), _slices[2], (_image.width() + 31) / 32);
      s = _omr->ocr()->readLine(img).trimmed();
      if (!s.isEmpty())
            addText(score, TEXT_COMPOSER, s);
#endif
      }

//---------------------------------------------------------
//   removeBorder
//---------------------------------------------------------

void OmrPage::removeBorder()
      {
#if 0
      cropT = cropB = cropL = cropR = 0;
      for (int y = 0; y < height(); ++y) {
            for (int x = 0; x < width(); ++x) {
                  QRgb c = _image.pixel(x,y);
                  if(qGray(c) > 0){
                        cropT = y;
                        break;
                        }
                  }
            if (cropT)
                  break;
            }
      for (int y = height() - 1; y >= cropT; --y) {
            for (int x = 0; x < width(); ++x) {
                  QRgb c = _image.pixel(x,y);
                  if(qGray(c) > 0){
                        cropB = y;
                        break;
                        }
                  }
            if (cropB)
                  break;
            }
      int y1 = cropT;
      int y2 = cropB;
      for (int x = 0; x < width(); ++x) {
            for (int y = y1; y < y2; ++y) {
                  QRgb c = _image.pixel(x,y);
                  if(qGray(c) > 0){
                        cropL = x;
                        break;
                        }
                  }
            if (cropL)
                  break;
            }
      for (int x = width() - 1; x >= cropL; --x) {
            for (int y = y1; y < y2; ++y) {
                  QRgb c = _image.pixel(x,y);
                  if(qGray(c) > 0){
                        cropR = x;
                        break;
                        }
                  }
            if (cropR)
                  break;
            }

      _image = _image.copy(cropL, cropT, cropR - cropL + 1, cropB - cropT + 1);
#endif
      }

//---------------------------------------------------------
//   crop
//---------------------------------------------------------

void OmrPage::crop()
      {
      int wl = wordsPerLine();
      cropT = cropB = cropL = cropR = 0;
      for (int y = 0; y < height(); ++y) {
            const uint* p = scanLine(y);
            for (int k = 0; k < wl; ++k) {
                  if (*p++) {
                        cropT = y;
                        break;
                        }
                  }
            if (cropT)
                  break;
            }
      for (int y = height() - 1; y >= cropT; --y) {
            const uint* p = scanLine(y);
            for (int k = 0; k < wl; ++k) {
                  if (*p++) {
                        cropB = height() - y - 1;
                        break;
                        }
                  }
            if (cropB)
                  break;
            }
      int y1 = cropT;
      int y2 = height() - cropT - cropB;
      for (int x = 0; x < wl; ++x) {
            for (int y = y1; y < y2; ++y) {
                  if (*(scanLine(y) + x)) {
                        cropL = x;
                        break;
                        }
                  }
            if (cropL)
                  break;
            }
      for (int x = wl - 1; x >= cropL; --x) {
            for (int y = y1; y < y2; ++y) {
                  if (*(scanLine(y) + x)) {
                        cropR = wl - x - 1;
                        break;
                        }
                  }
            if (cropR)
                  break;
            }
//printf("*** crop: T%d B%d L%d R:%d\n", cropT, cropB, cropL, cropR);
      }

//---------------------------------------------------------
//   slice
//---------------------------------------------------------

void OmrPage::slice()
      {
      _slices.clear();
      int y1 = cropT;
      int y2 = height() - cropB;
      int x1 = cropL;
      int x2 = wordsPerLine() - cropR;
      int xbits = (x2 - x1) * 32;

      //      _slices.append(QRect(cropL*32, y1, xbits, y2-y1));
#if 1
      for (int y = y1; y < y2;) {
            //
            // skip contents
            //
            int ys = y;
            for (; y < y2; ++y) {
                  const uint* p = scanLine(y) + cropL;
                  bool bits = false;
                  for (int x = cropL; x < x2; ++x) {
                        if (*p) {
                              bits = true;
                              break;
                              }
                        ++p;
                        }
                  if (!bits)
                        break;
                  }
            if (y - ys)
                  _slices.append(QRect(cropL * 32, ys, xbits, y - ys));
            //
            // skip space
            //
            for (; y < y2; ++y) {
                  const uint* p = scanLine(y) + cropL;
                  bool bits = false;
                  for (int x = cropL; x < x2; ++x) {
                        if (*p) {
                              bits = true;
                              break;
                              }
                        ++p;
                        }
                  if (bits)
                        break;
                  }
            }
#endif
      }

//---------------------------------------------------------
//    deSkew
//---------------------------------------------------------

void OmrPage::deSkew()
      {
      int wl = wordsPerLine();
      int h = height();
      uint* db = new uint[wl * h];
      memset(db, 0, wl * h * sizeof(uint));

      for(const QRect& r : _slices) {
            double rot = skew(r);
            if (qAbs(rot) < 0.1) {
                  memcpy(db + wl * r.y(), scanLine(r.y()), wl * r.height() * sizeof(uint));
                  continue;
                  }

            QTransform t;
            t.rotate(rot);
            QTransform tt = QImage::trueMatrix(t, width(), r.height());

            double m11 = tt.m11();
            double m12 = tt.m12();
            double m21 = tt.m21();
            double m22 = tt.m22();
            double dx = tt.m31();
            double dy = tt.m32();

            double m21y = r.y() * m21;
            double m22y = r.y() * m22;
            int y2 = r.y() + r.height();

            for (int y = r.y(); y < y2; ++y) {
                  const uint* s = scanLine(y);

                  m21y += m21;
                  m22y += m22;
                  for (int x = 0; x < wl; ++x) {
                        uint c = *s++;
                        if (c == 0)
                              continue;
                        uint mask = 1;
                        for (int xx = 0; xx < 32; ++xx) {
                              if (c & mask) {
                                    int xs = x * 32 + xx;
                                    int xd = lrint(m11 * xs + m21y + dx);
                                    int yd = lrint(m22y + m12 * xs + dy);

                                    int wxd = xd / 32;
                                    if ((xd >= 0) && (wxd < wl) && (yd >= 0) && (yd < h)) {
                                          uint* d = db + wl * yd + wxd;
                                          if( d < db + wl * h) //check that we are in the bounds.
                                                *d |= (0x1 << (xd % 32));
                                          }
                                    }
                              mask <<= 1;
                              }
                        }
                  }
            }
      memcpy(_image.bits(), db, wl * h * sizeof(uint));
      delete[] db;
      }

struct ScanLine {
      int run;
      int x1, x2;
      ScanLine() { run = 0; x1 = 100000; x2 = 0; }
      };

struct H {
      int y;
      int bits;
      H(int a, int b) : y(a), bits(b) {}
      };

//---------------------------------------------------------
//   xproject
//---------------------------------------------------------

int OmrPage::xproject(const uint* p, int wl)
      {
      int run = 0;
      int w = wl - cropL - cropR;
      int x1 = cropL + w / 4;         // only look at part of page
      int x2 = x1 + w / 2;
      for (int x = cropL; x < x2; ++x) {
            uint v = *p++;
            run += Omr::bitsSetTable[v & 0xff]
               + Omr::bitsSetTable[(v >> 8) & 0xff]
               + Omr::bitsSetTable[(v >> 16) & 0xff]
               + Omr::bitsSetTable[v >> 24];
            }
      return run;
      }

//---------------------------------------------------------
//   xproject2
//---------------------------------------------------------

double OmrPage::xproject2(int y1)
      {
      int wl = wordsPerLine();
      const uint* db = bits();
      double val = 0.0;

      int w = wl - cropL - cropR;
      int x1 = (cropL + w / 4) * 32;       // only look at part of page
      int x2 = x1 + (w / 2 * 32);

      int ddx = x2 - x1;
      for (int dy = -12; dy < 12; ++dy) {
            int onRun = 0;
            int offRun = 0;
            int on = 0;
            int off = 0;
            bool onFlag = false;
            int incy = (dy > 0) ? 1 : (dy < 0) ? -1 : 0;
            int ddy = dy < 0 ? -dy : dy;
            int y = y1;
            if (y < 1)
                  y = 0;
            int err = ddx / 2;
            for (int x = x1; x < x2;) {
                  const uint* d = db + wl * y + (x / 32);
                  if (d < db + wl)
                        break;
                  if (d >= db + (wl - 1) * height()) //check that we are in the bounds.
                        break;
                  bool bit = ((*d) & (0x1 << (x % 32)));
                  bit = bit || ((*(d + wl)) & (0x1 << (x % 32)));
                  bit = bit || ((*(d - wl)) & (0x1 << (x % 32)));
                  if (bit != onFlag) {
                        if (!onFlag) {
                              //
                              // end of offrun:
                              //
                              if (offRun > 20) {
                                    off += offRun * offRun;
                                    on += onRun * onRun;
                                    onRun = 0;
                                    offRun = 0;
                                    }
                              else {
                                    onRun += offRun;
                                    offRun = 0;
                                    }
                              }
                        onFlag = bit;
                        }
                  (bit ? onRun : offRun)++;
                  if (offRun > 100) {
                        offRun = 0;
                        off = 1;
                        on = 0;
                        onRun = 0;
                        break;
                        }
                  err -= ddy;
                  if (err < 0) {
                        err += ddx;
                        y += incy;
                        if (y < 1)
                              y = 1;
                        else if (y >= height())
                              y = height() - 1;
                        }
                  ++x;
                  }
            if (offRun > 20)
                  off += offRun * offRun;
            else
                  onRun += offRun;
            on += onRun * onRun;
            if (off == 0)
                  off = 1;
            double nval = double(on) / double(off);
            if (nval > val)
                  val = nval;
            }
      return val;
      }

static bool sortLvStaves(const Lv& a, const Lv& b)
      {
      return a.line < b.line;
      }

//---------------------------------------------------------
//   getStaffLines
//---------------------------------------------------------

void OmrPage::getStaffLines()
      {
      staves.clear();
      int h = height();
      int wl = wordsPerLine();
//printf("getStaffLines %d %d  crop %d %d\n", h, wl, cropT, cropB);
      if (h < 1)
            return;

      int y1 = cropT;
      int y2 = h - cropB;
      if (y2 >= h)
            --y2;

#if (!defined (_MSCVER) && !defined (_MSC_VER))
      double projection[h];
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<double> projection(h);
#endif
      for (int y = 0; y < y1; ++y)
            projection[y] = 0;
      for (int y = y2; y < h; ++y)
            projection[y] = 0;
      for (int y = y1; y < y2; ++y)
            projection[y] = xproject2(y);
      int autoTableSize = (wl * 32) / 20;       // 1/20 page width
      if (autoTableSize > y2 - y1)
            autoTableSize = y2 - y1;
#if (!defined (_MSCVER) && !defined (_MSC_VER))
      double autoTable[autoTableSize];
      memset(autoTable, 0, sizeof(autoTable));
      for (int i = 0; i < autoTableSize; ++i) {
            autoTable[i] = covariance(projection + y1, projection + i + y1, y2 - y1 - i);
            }
#else
      // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
      //    heap allocation is slow, an optimization might be used.
      std::vector<double> autoTable(autoTableSize);      // Default-initialized, doesn't need to be cleared
      for(int i = 0; i < autoTableSize; ++i)
         {
         autoTable[i] = covariance(&(projection[y1]), &(projection[i + y1]), y2 - y1 - i);
         }
#endif

      //
      // search for first maximum in covariance starting at 10 to skip
      // line width. Staff line distance (spatium) must be at least 10 dots
      //
      double maxCorrelation = 0;
      _spatium = 0;
      for (int i = 5; i < autoTableSize; ++i) {
            if (autoTable[i] > maxCorrelation) {
                  maxCorrelation = autoTable[i];
                  _spatium = i;
                  }
            }
      if (_spatium == 0) {
//printf("*** no staff lines found\n");
            return;
            }

      //---------------------------------------------------
      //    look for staves
      //---------------------------------------------------

      QList<Lv> lv;
      int ly = 0;
      int lval = -1000.0;
      for (int y = y1; y < (y2 - _spatium * 4); ++y) {
            double val = 0.0;
            for (int i = 0; i < 5; ++i)
                  val += projection[y + i * int(_spatium)];
            if (val < lval) {
                  lv.append(Lv(ly, lval));
                  }
            lval = val;
            ly = y;
            }
      qSort(lv);

      QList<Lv> staveTop;
      int staffHeight = _spatium * 6;
      for (Lv a : lv) {
            if (a.val < 500)   // MAGIC to avoid false positives
                  continue;
            int line = a.line;
            bool ok = true;
            for (Lv b : staveTop) {
                  if ((line > (b.line - staffHeight)) && (line < (b.line + staffHeight))) {
                        ok = false;
                        break;
                        }
                  }
            if (ok)
                  staveTop.append(a);
            }
      qSort(staveTop.begin(), staveTop.end(), sortLvStaves);
      for (Lv a : staveTop) {
            staves.append(OmrStaff(cropL * 32, a.line, (wordsPerLine() - cropL - cropR) * 32, _spatium * 4));
            }
      }

//---------------------------------------------------------
//   getRatio
//---------------------------------------------------------

void OmrPage::getRatio()
      {
      double num_black;
      double num_white;
      _ratio = 0.0;

      num_black = 1;
      num_white = 1;
      for (int x = 0; x < width(); ++x) {
            for (int y = 0; y < height(); ++y) {
                  if(isBlack(x,y)) num_black++;
                  else num_white++;
                  }
            }
      _ratio = num_black / (num_black + num_white);
      }

//---------------------------------------------------------
//   searchNotes
//---------------------------------------------------------

void OmrSystem::searchNotes(QList<OmrNote*>* noteList, int x1, int x2, int y, int line)
      {
      //a simple and cheap note detector (heuristic approach)
      double _spatium = _page->spatium();
      y += line * _spatium * .5;

      QList<Peak> notePeaks;
      Pattern* pattern = Omr::quartheadPattern;
      int hh = pattern->h();
      int hw = pattern->w();
      double val;
      int step_size = 2;
      int note_thresh = 50;

      for (int x = x1; x < (x2 - hw); x += step_size) {
            val = pattern->match(&_page->image(), x, y - hh / 2, _page->ratio());
            if (val > note_thresh) {
                  notePeaks.append(Peak(x, val, 0));
                  }
            }

      int n = notePeaks.size();
      for (int i = 0; i < n; ++i) {
            OmrNote* note = new OmrNote;
            int hh1 = pattern->h();
            int hw1 = pattern->w();
            note->setRect(notePeaks[i].x, y - hh1 / 2, hw1, hh1);
            note->line = line;
            note->sym = pattern->id();
            note->prob = notePeaks[i].val;
            noteList->append(note);
            }
      return;
      }

//---------------------------------------------------------
//   staffDistance
//---------------------------------------------------------

double OmrPage::staffDistance() const
      {
      if (staves.size() < 2)
            return 5;
      return ((staves[1].y() - staves[0].y()) / _spatium) - 4.0;
      }

//---------------------------------------------------------
//   systemDistance
//---------------------------------------------------------

double OmrPage::systemDistance() const
      {
      if (staves.size() < 3)
            return 6.0;
      return ((staves[2].y() - staves[1].y()) / _spatium) - 4.0;
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void OmrPage::write(XmlWriter& xml) const
      {
      xml.stag("OmrPage");
      xml.tag("cropL", cropL);
      xml.tag("cropR", cropR);
      xml.tag("cropT", cropT);
      xml.tag("cropB", cropB);
      for(const QRect& r : staves)
            xml.tag("staff", QRectF(r));
      xml.etag();
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void OmrPage::read(XmlReader& e)
      {
      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());

            if (tag == "cropL")
                  cropL = e.readInt();
            else if (tag == "cropR")
                  cropR = e.readInt();
            else if (tag == "cropT")
                  cropT = e.readInt();
            else if (tag == "cropB")
                  cropB = e.readInt();
            else if (tag == "staff") {
                  OmrStaff r(e.readRect().toRect());
                  staves.append(r);
                  }
            else
                  e.unknown();
            }
      }
}
