//=============================================================================
//  BWW to MusicXML converter
//  Part of MusE Score
//  Linux Music Score Editor
//  $Id: parser.cpp 4873 2011-10-19 19:33:04Z lvinken $
//
//  Copyright (C) 2010 Werner Schweer and others
//
//  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.
//=============================================================================

/**
 \file
 A simple parser for bww files. The file header is handled one line at a time,
 until a line starting with "&" is found. The parser then builds measures from
 uinterrupted sequences of notes.
 */

// #include <iostream>

#include <QtCore/QStringList>
#include <QtCore/QtDebug>
#include <QtCore/QMap>

#include "lexer.h"
#include "parser.h"
#include "writer.h"

// Duration of a whole measure in ticks
static const int WHOLE_MEASURE_DURATION = 192;

/**
 Determine if symbol is part of a note sequence
 */

static bool isNote(Bww::Symbol sym)
{
  return (sym == Bww::NOTE
          || sym == Bww::TIE
          || sym == Bww::TRIPLET
          || sym == Bww::GRACE);
}

/**
 Determine if symbol is part of a non-note sequence
 */

static bool isNonNote(Bww::Symbol sym)
{
  return (sym == Bww::CLEF
          || sym == Bww::KEY
          || sym == Bww::TSIG
          || sym == Bww::PART
          || sym == Bww::BAR);
}

/**
 Dump beams
 */

static void dumpBeams(QList<Bww::MeasureDescription> const& measures)
{
  for (int j = 0; j < measures.size(); ++j)
  {
    QString beams;
    QString beamStates;
    QVector<QString> beamList(3);
    for (int i = 0; i < measures.at(j).notes.size(); ++i)
    {
      QString beam = measures.at(j).notes.at(i).beam;
      if (beam == "")
        beam = " ";

      beams += beam;
      switch (measures.at(j).notes.at(i).beamState)
      {
      case Bww::ST_NONE:     beamStates += " "; break;
      case Bww::ST_START:    beamStates += "["; break;
      case Bww::ST_CONTINUE: beamStates += "_"; break;
      case Bww::ST_STOP:     beamStates += "]"; break;
      default:               beamStates += " ";
      }
      for (int k = 0; k < 3; k++)
        switch (measures.at(j).notes.at(i).beamList.at(k))
        {
        case Bww::BM_NONE:          beamList[k] += " "; break;
        case Bww::BM_BEGIN:         beamList[k] += "b"; break;
        case Bww::BM_CONTINUE:      beamList[k] += "c"; break;
        case Bww::BM_END:           beamList[k] += "e"; break;
        case Bww::BM_FORWARD_HOOK:  beamList[k] += ">"; break;
        case Bww::BM_BACKWARD_HOOK: beamList[k] += "<"; break;
        default:                    beamList[k] += "?";
        }
    }
    qDebug() << "beams measure #" << j + 1 << beams;
    qDebug() << "beams measure #" << j + 1 << beamStates;
    for (int k = 0; k < 3; k++)
      qDebug() << "beams measure #" << j + 1 << beamList.at(k);
  }
}

/**
 Dump measure contents
 */

static void dumpMeasures(QList<Bww::MeasureDescription> const& measures)
{
  qDebug() << "dumpMeasures #measures" << measures.size()
      ;
  for (int j = 0; j < measures.size(); ++j)
  {
    qDebug() << "measure #" << j + 1;
    qDebug() << "Measure contents:";
    qDebug() << "mbf:"
        << "repeatBegin" << measures.at(j).mbf.repeatBegin
        << "endingFirst" << measures.at(j).mbf.endingFirst
        << "endingSecond" << measures.at(j).mbf.endingSecond
        << "firstOfSystem" << measures.at(j).mbf.firstOfSystem
        << "irregular" << measures.at(j).mbf.irregular
        ;
    for (int i = 0; i < measures.at(j).notes.size(); ++i)
    {
      qDebug()
          << measures.at(j).notes.at(i).pitch
          << measures.at(j).notes.at(i).beam
          << measures.at(j).notes.at(i).type
          << measures.at(j).notes.at(i).dots
          << measures.at(j).notes.at(i).tieStart
          << measures.at(j).notes.at(i).tieStop
          << measures.at(j).notes.at(i).triplet
          << measures.at(j).notes.at(i).grace
          ;
    }
    qDebug() << "mef:"
        << "repeatEnd" << measures.at(j).mef.repeatEnd
        << "endingEnd" << measures.at(j).mef.endingEnd
        << "lastOfSystem" << measures.at(j).mef.lastOfSystem
        << "lastOfPart" << measures.at(j).mef.lastOfPart
        ;
    qDebug() << "duration:" << measures.at(j).duration;
  }
}

/**
 Calculate measure durations
 */

static void calculateMeasureDurations(QList<Bww::MeasureDescription> & measures)
{
  for (int j = 0; j < measures.size(); ++j)
  {
    int measureDuration = 0;
    for (int i = 0; i < measures.at(j).notes.size(); ++i)
    {
      int ticks = WHOLE_MEASURE_DURATION / measures.at(j).notes.at(i).type.toInt();
      if (measures.at(j).notes.at(i).dots) ticks = 3 * ticks / 2;
      if (measures.at(j).notes.at(i).triplet != Bww::ST_NONE) ticks = 2 * ticks / 3;
      if (measures.at(j).notes.at(i).grace) ticks = 0; // grace notes don't count
      measureDuration += ticks;
      qDebug()
          << measures.at(j).notes.at(i).pitch
          << measures.at(j).notes.at(i).beam
          << measures.at(j).notes.at(i).type
          << measures.at(j).notes.at(i).dots
          << measures.at(j).notes.at(i).tieStart
          << measures.at(j).notes.at(i).tieStop
          << measures.at(j).notes.at(i).triplet
          << measures.at(j).notes.at(i).grace
          << "->" << ticks
          ;
    }
    qDebug() << "measureDuration:" << measureDuration;
    measures[j].duration = measureDuration;
  }
}

/**
 Determine time signature
 */

static void determineTimesig(QList<Bww::MeasureDescription> const& measures, int & beats, int & beat)
{
  QMap<int, int> map;
  for (int j = 0; j < measures.size(); ++j)
  {
    int dur = measures[j].duration;
    if (map.contains(dur))
      map[dur]++;
    else
      map.insert(dur, 1);
  }
  // determine most common duration
  int commonDur = 0;
  int max = 0;
  QMap<int, int>::const_iterator i = map.constBegin();
  while (i != map.constEnd())
  {
    qDebug() << "measureDurations:" << i.key() << i.value() << endl;
    if (i.value() > max)
    {
      commonDur = i.key();
      max = i.value();
    }
    ++i;
  }
  qDebug() << "measureDuration commonDur:" << commonDur << "max:" << max;
  // determine time signature
  beat = 4;
  beats = 0;
  int divisor = WHOLE_MEASURE_DURATION / 4;
  for (; beat < 64; beat *= 2, divisor /= 2)
  {
    if ((commonDur % divisor) == 0)
    {
      beats = commonDur / divisor;
      qDebug()
          << "measureDuration found beat:" << beat
          << "beats:" << beats
          << "divisor:" << divisor
          ;
      return;
    }
  }
  // could not determine time signature, set default
  beat = 4;
  beats = 4;
}

/**
 Find irregular measures
 */

static void findIrregularMeasures(QList<Bww::MeasureDescription> & measures, int beats, int beat)
{
  qDebug() << "findIrregularMeasures" << measures.size()
      << "beats" << beats
      << "beat" << beat
      ;

  int normalDuration = WHOLE_MEASURE_DURATION * beats / beat;

  // need at least one measure
  if (measures.size() == 0) return;

  // if the first measure is shorter that normal, it is irregular
  if (measures.at(0).duration < normalDuration) measures[0].mbf.irregular = true;

  for (int j = 1; j < measures.size(); ++j)
  {
    // the second measure of a pair where the sum of their duration adds up
    // to the normal duration is also irregular
    const int d1 = measures.at(j - 1).duration;
    const int d2 = measures.at(j).duration;
    if (d1 > 0 && d2 > 0 && (d1 + d2) == normalDuration)
      measures[j].mbf.irregular = true;
  }
}

/**
 Set mef.lastOfPart flag on last measure
 */

static void setLastOfPart(QList<Bww::MeasureDescription> & measures)
{
  qDebug() << "dumpMeasures #measures" << measures.size()
      ;

  // need at least one measure
  if (measures.size() == 0) return;

  // set lastOfPart flag on last measure
  int j = measures.size() - 1;
  measures[j].mef.lastOfPart = true;
}

static QString findNextNextNoteBeam(QList<Bww::MeasureDescription> const& measures, int measureNr, int noteNr)
{
  for (int i = noteNr + 1; i < measures.at(measureNr).notes.size(); ++i)
  {
    if (measures.at(measureNr).notes.at(i).grace)
      // ignore grace notes
      continue;
    return measures.at(measureNr).notes.at(i).beam;
  }
  return " "; // no next non-grace note found
}

static int type2beams(QString type)
{
  if (type == "8") return 1;
  else if (type == "16") return 2;
  else if (type == "32") return 3;
  else return 0;
}

static void calculateHigherBeamStates(Bww::MeasureDescription & m)
{
  qDebug() << "calculateHigherBeamStates";
  for (int i = 0; i < m.notes.size(); ++i)
  {
    qDebug()
        << m.notes.at(i).pitch
        << m.notes.at(i).beam
        << m.notes.at(i).type
        << m.notes.at(i).dots
        << m.notes.at(i).tieStart
        << m.notes.at(i).tieStop
        << m.notes.at(i).triplet
        << m.notes.at(i).grace
        ;

    if (m.notes.at(i).grace)
    {
      // just copy beamList[0] into beamList[1] and beamList[2]
      m.notes[i].beamList[1] = m.notes.at(i).beamList[0];
      m.notes[i].beamList[2] = m.notes.at(i).beamList[0];
    }
    else
    {
      int blp = -1; // beam level previous chord
      int blc = -1; // beam level current chord
      int bln = -1; // beam level next chord

      // find beam level current note
      blc = type2beams(m.notes.at(i).type);
      if (blc == 0)
        continue; // note does not have a beam

      // find beam level previous note
      if (m.notes.at(i).beamList[0] == Bww::BM_CONTINUE
          || m.notes.at(i).beamList[0] == Bww::BM_END)
      {
        for (int j = i - 1; blp == -1 && j >= 0; --j)
        {
          if (m.notes.at(j).grace)
            continue; // ignore grace notes
          blp = type2beams(m.notes.at(j).type);
        }
      }

      // find beam level next note
      if (m.notes.at(i).beamList[0] == Bww::BM_BEGIN
          || m.notes.at(i).beamList[0] == Bww::BM_CONTINUE)
      {
        for (int j = i + 1; bln == -1 && j < m.notes.size(); ++j)
        {
          if (m.notes.at(j).grace)
            continue; // ignore grace notes
          bln = type2beams(m.notes.at(j).type);
        }
      }

      qDebug()
          << "blp" << blp
          << "blc" << blc
          << "bln" << bln;
      for (int j = 2; j <= blc; ++j)
      {
        Bww::BeamType bt = Bww::BM_NONE;
        if (blp < j && bln >= j) bt = Bww::BM_BEGIN;
        else if (blp < j && bln < j) {
          if (bln > 0) bt = Bww::BM_FORWARD_HOOK;
          else if (blp > 0) bt = Bww::BM_BACKWARD_HOOK;
        }
        else if (blp >= j && bln < j) bt = Bww::BM_END;
        else if (blp >= j && bln >= j) bt = Bww::BM_CONTINUE;
        m.notes[i].beamList[j - 1] = bt;
        qDebug() << "beamList" << j - 1 << "=" << bt;
      }
    } // else
  } // for (int i = 0; i < m.notes.size(); ++i)
}

/**
 Determine all beam states.
 First for normal notes convert "r" and "l" in notes.beam into BM_BEGIN,
 BM_CONTINUE and BM_END in notes.beamList[0]. For grace notes "b", "c" and
 "e" can be directly converted into the corresponding beam states in
 notes.beamList[0].
 Then calculate the higher level beams.
 */

static void determineBeamStates(QList<Bww::MeasureDescription> & measures)
{
  enum State { NONE, LEFT, RIGHT };
  for (int j = 0; j < measures.size(); ++j)
  {
    State state = NONE;
    for (int i = 0; i < measures.at(j).notes.size(); ++i)
    {
      QString beam = measures.at(j).notes.at(i).beam;
      // handle normal notes
      if (beam == "")
      {
        measures[j].notes[i].beamState = Bww::ST_NONE;
        measures[j].notes[i].beamList[0] = Bww::BM_NONE;
        state = NONE;
      }
      else if (beam == "r")
      {
        if (state == NONE)
        {
          measures[j].notes[i].beamState = Bww::ST_START;
          measures[j].notes[i].beamList[0] = Bww::BM_BEGIN;
          state = LEFT; // now in left part of beam
        }
        else if (state == LEFT)
        {
          measures[j].notes[i].beamState = Bww::ST_CONTINUE;
          measures[j].notes[i].beamList[0] = Bww::BM_CONTINUE;
        }
        else if (state == RIGHT)
        {
          // shouldn't happen TODO report (internal?) error
        }
      }
      else if (beam == "l")
      {
        if (state == NONE)
        {
          // shouldn't happen TODO report error
        }
        else if (state == LEFT || state == RIGHT)
        {
          // if the beam does not end here (next note has beam "l")
          // then beamState is CONTINUE else STOP
          if (findNextNextNoteBeam(measures, j, i) == "l")
          {
            measures[j].notes[i].beamState = Bww::ST_CONTINUE;
            measures[j].notes[i].beamList[0] = Bww::BM_CONTINUE;
            state = RIGHT; // now in right part of beam
          }
          else
          {
            measures[j].notes[i].beamState = Bww::ST_STOP;
            measures[j].notes[i].beamList[0] = Bww::BM_END;
            state = NONE; // now in right part of beam
          }
        }
      }
      // handle grace notes
      else if (beam == "b") measures[j].notes[i].beamList[0] = Bww::BM_BEGIN;
      else if (beam == "c") measures[j].notes[i].beamList[0] = Bww::BM_CONTINUE;
      else if (beam == "e") measures[j].notes[i].beamList[0] = Bww::BM_END;
    }
    calculateHigherBeamStates(measures[j]);
  }
}

namespace Bww {

  /**
   Parser constructor, using Lexer \a l and Writer \a w.
   */

  Parser::Parser(Lexer& l, Writer& w)
    : lex(l),
    wrt(w),
    tempo(0),
    //inMeasure(false),
    //measureNr(0),
    tieStart(false),
    inTie(false),
    tripletStart(false),
    inTriplet(false),
    tsigFound(false)
  {
    qDebug() << "Parser::Parser()";
  }

  /**
   Parse the input stream and write result.
   */

  void Parser::parse()
  {
    // read the header, handling only strings and tune tempo
    while (lex.symType() == COMMENT
           || lex.symType() == STRING
           || lex.symType() == TEMPO)
    {
      if (lex.symType() == STRING)
        parseString();
      else if (lex.symType() == TEMPO)
        parseTempo();
      else if (lex.symType() == COMMENT)
        lex.getSym();
    }
    qDebug() << "Parser::parse()"
        << "title:" << title
        << "type:" << type
        << "composer:" << composer
        << "footer:" << footer
        ;
    wrt.header(title, type, composer, footer, tempo);

    // read the actual music
    if (lex.symType() != CLEF)
      errorHandler("clef ('&') expected");
    while (lex.symType() != NONE)
    {
      if (isNonNote(lex.symType()))
        parseSeqNonNotes();
      else if (isNote(lex.symType()))
        parseSeqNotes();
      else if (lex.symType() == UNKNOWN)
      {
        errorHandler("unknown symbol '" + lex.symValue() + "'");
        lex.getSym();
      }
      else
      {
        ; // others not implemented yet: silently ignored
        lex.getSym();
      }
    }

    qDebug() << "Parser::parse() finished, #measures" << measures.size()
        ;

    calculateMeasureDurations(measures);
    if (!tsigFound)
    {
      determineTimesig(measures, beats, beat);
      wrt.tsig(beats, beat);
    }
    findIrregularMeasures(measures, beats, beat);
    determineBeamStates(measures);
    setLastOfPart(measures);
    dumpMeasures(measures);
    dumpBeams(measures);

    for (int j = 0; j < measures.size(); ++j)
    {
      wrt.beginMeasure(measures.at(j).mbf);
      for (int i = 0; i < measures.at(j).notes.size(); ++i)
      {
        wrt.note(measures.at(j).notes.at(i).pitch,
                 measures.at(j).notes.at(i).beamList,
                 measures.at(j).notes.at(i).type,
                 measures.at(j).notes.at(i).dots,
                 measures.at(j).notes.at(i).tieStart,
                 measures.at(j).notes.at(i).tieStop,
                 measures.at(j).notes.at(i).triplet,
                 measures.at(j).notes.at(i).grace
                 );
      }
      wrt.endMeasure(measures.at(j).mef);
    }

    // trailer
    wrt.trailer();
  }

  /**
   Display error \a s.
   */

  void Parser::errorHandler(QString /*s*/)
  {
#if 0 // WS
    std::cerr << "Parse error line "
        << lex.symLineNumber() + 1
        << ": "
        << qPrintable(s)
        << std::endl;
#endif
  }

  /**
   Parse a bww bar symbol.
   */

  void Parser::parseBar(Bww::MeasureEndFlags& mef)
  {
    qDebug() << "Parser::parseBar() value:" << qPrintable(lex.symValue());
    if (lex.symValue() == "!!t") mef.doubleBarLine = true;
    lex.getSym();
  }

  /**
   Parse a bww note.
   */

  void Parser::parseNote()
  {
    qDebug() << "Parser::parseNote() value:" << qPrintable(lex.symValue());

    QRegExp rNotes("(LG|LA|[B-F]|HG|HA)([lr]?)_(1|2|4|8|16|32)");

    QStringList caps;
    if (rNotes.exactMatch(lex.symValue()))
    {
      caps = rNotes.capturedTexts();
      qDebug() << " match" << caps.size();
      if (caps.size() == 4)
      {
        qDebug()
            << "caps[1]" << caps.at(1)
            << "caps[2]" << caps.at(2)
            << "caps[3]" << caps.at(3)
            ;
      }
    }
    lex.getSym();

    int dots = 0;
    bool tieStop = false;
    if (tieStart) inTie = true;
    bool tripletStop = false;
    if (tripletStart) inTriplet = true;
    if (lex.symType() == DOT)
    {
      qDebug() << " dot" << qPrintable(lex.symValue());
      ++dots;
      lex.getSym();
    }
    else if (lex.symType() == TIE)
    {
      qDebug() << " tie" << qPrintable(lex.symValue());
      if (lex.symValue() == "^ts")
      {
        if (inTie) errorHandler("tie start ('^ts') unexpected");
      }
      else
      {
        if (!inTie) errorHandler("tie end ('^te') unexpected");
        else
        {
          tieStop = true;
          inTie = false;
          lex.getSym();
        }
      }
    }
    else if (lex.symType() == TRIPLET)
    {
      qDebug() << " triplet" << qPrintable(lex.symValue());
      if (lex.symValue() == "^3s")
      {
        if (inTriplet) errorHandler("triplet start ('^3s') unexpected");
      }
      else
      {
        if (!inTriplet) errorHandler("triplet end ('^3e') unexpected");
        else
        {
          tripletStop = true;
        }
      }
      lex.getSym();
    }

    StartStop triplet = ST_NONE;
    if (inTriplet)
    {
      if (tripletStart) triplet = ST_START;
      else if (tripletStop) triplet = ST_STOP;
      else triplet = ST_CONTINUE;
    }
    qDebug() << " tie start" << tieStart << " tie stop" << tieStop;
    qDebug() << " triplet start" << tripletStart << " triplet stop" << tripletStop;
    NoteDescription noteDesc(caps[1], caps[2], caps[3], dots, tieStart, tieStop, triplet);

    if (measures.isEmpty())
    {
      errorHandler("cannot append note: no measure");
    }
    else
    {
      measures.last().notes.append(noteDesc);
    }
    tieStart = false;
    tripletStart = false;
    if (tripletStop)
    {
      inTriplet = false;
      tripletStop = false;
    }
  }

  /**
   Determine beam for grace note \a index in a group of \a size grace notes.
   */

  static QString graceBeam(const int size, const int index)
  {
    if (size <= 1) return " ";                // no beam
    if (index < 0) return " ";                // no beam (but should not happen)
    if (index == 0) return "b";               // begin
    else if (index == (size - 1)) return "e"; // end
    else if (index >= size) return " ";       // no beam (but should not happen)
    else return "c";                          // continue
  }

  /**
   Parse a bww embellishment.
   */

  void Parser::parseGraces()
  {
    qDebug() << "Parser::parseGraces() value:" << qPrintable(lex.symValue());

    const QString type = "32";
    const int dots = 0;
    QStringList graces = lex.symValue().split(" ");
    for (int i = 0; i < graces.size(); ++i)
    {
      const QString beam = graceBeam(graces.size(), i);
      NoteDescription noteDesc(graces.at(i), beam, type, dots, false, false, ST_NONE, true);
      if (measures.isEmpty())
      {
        errorHandler("cannot append note: no measure");
      }
      else
      {
        measures.last().notes.append(noteDesc);
      }
    }
    lex.getSym();
  }

  /**
   Parse a bww part symbol.
   */

  void Parser::parsePart(Bww::MeasureBeginFlags& mbf, Bww::MeasureEndFlags& mef)
  {
    qDebug() << "Parser::parsePart() value:" << qPrintable(lex.symValue());
    if (lex.symValue() == "I!''")
    {
      mbf.repeatBegin = true;
    }
    else if (lex.symValue() == "'1")
    {
      mbf.endingFirst = true;
    }
    else if (lex.symValue() == "'2")
    {
      mbf.endingSecond = true;
    }
    else if (lex.symValue() == "''!I")
    {
      mef.repeatEnd = true;
    }
    else if (lex.symValue() == "_'")
    {
      mef.endingEnd = true;
    }
    else
    {
      ; // other silently ignored
    }
    lex.getSym();
  }

  /**
   Parse a sequence of non-notes.
   */

  void Parser::parseSeqNonNotes()
  {
    qDebug() << "Parser::parseSeqNonNotes() value:" << qPrintable(lex.symValue());
    MeasureBeginFlags mbfl;
    MeasureEndFlags mefl;
    while (isNonNote(lex.symType()))
    {
      if (lex.symType() == CLEF)
      {
        mbfl.firstOfSystem = true;
        mefl.lastOfSystem = true;
        lex.getSym();
      }
      else if (lex.symType() == KEY)
        lex.getSym(); // ignore
      else if (lex.symType() == TSIG)
        parseTSig();
      else if (lex.symType() == PART)
        parsePart(mbfl, mefl);
      else if (lex.symType() == BAR)
        parseBar(mefl);
    }
    // First end the previous measure
    if (!measures.isEmpty())
    {
      measures.last().mef = mefl;
    }
    // Then start a new measure, if necessary
    if (isNote(lex.symType()))
    {
      MeasureDescription md;
      md.mbf = mbfl;
      measures.append(md);
    }
  }

  /**
   Parse a sequence of notes.
   Includes handling ties and triplets, but without extensive error checking.
   May break on invalid input.
   */

  void Parser::parseSeqNotes()
  {
    qDebug() << "Parser::parseSeqNotes() value:" << qPrintable(lex.symValue());
    while (lex.symType() == GRACE || lex.symType() == NOTE || lex.symType() == TIE || lex.symType() == TRIPLET)
    {
      if (lex.symType() == GRACE) parseGraces();
      else if (lex.symType() == NOTE) parseNote();
      else if (lex.symType() == TIE)
      {
        if (lex.symValue() == "^ts")
        {
          if (inTie) errorHandler("tie start ('^ts') unexpected");
          else tieStart = true;
        }
        else
        {
          errorHandler("tie end ('^te') unexpected");
        }
        lex.getSym();
      }
      else if (lex.symType() == TRIPLET)
      {
        if (lex.symValue() == "^3s")
        {
          if (inTriplet) errorHandler("triplet start ('^3s') unexpected");
          else tripletStart = true;
        }
        else
        {
          errorHandler("triplet end ('^3e') unexpected");
        }
        lex.getSym();
      }
    }
  }

  /**
   Parse a bww string. Extract text and type.
   Example: "Air",(Y,C,0,0,Times New Roman,14,400,0,0,18,0,0,0)
   */

  void Parser::parseString()
  {
    qDebug() << "Parser::parseString() value:" << qPrintable(lex.symValue());

    QRegExp rString("\\\"(.*)\\\",\\(([A-Z]),.*\\)");

    if (rString.exactMatch(lex.symValue()))
    {
      QStringList caps = rString.capturedTexts();
      if (caps.size() == 3)
      {
        if (caps.at(2) == "T") title = caps.at(1);
        if (caps.at(2) == "Y") type = caps.at(1);
        if (caps.at(2) == "M") composer = caps.at(1);
        if (caps.at(2) == "F") footer = caps.at(1);
      }
    }
    lex.getSym();
  }

  /**
   Parse a bww tempo symbol.
   */

  void Parser::parseTempo()
  {
    qDebug() << "Parser::parseTempo() value:" << qPrintable(lex.symValue());

    QRegExp rTempo("^TuneTempo,(\\d+)");

    if (rTempo.exactMatch(lex.symValue()))
    {
      QStringList caps = rTempo.capturedTexts();
      if (caps.size() == 2)
      {
        tempo = caps.at(1).toInt();
      }
    }
    lex.getSym();
  }
  /**
   Parse a bww tsig symbol.
   */

  void Parser::parseTSig()
  {
    qDebug() << "Parser::parseTSig() value:" << qPrintable(lex.symValue());

    QRegExp rTSig("(\\d+)_(1|2|4|8|16|32)");

    if (rTSig.exactMatch(lex.symValue()))
    {
      QStringList caps = rTSig.capturedTexts();
      if (caps.size() == 3)
      {
        beats = caps.at(1).toInt();
        beat  = caps.at(2).toInt();
        tsigFound = true;
        wrt.tsig(beats, beat);
      }
    }
    lex.getSym();
  }

} // namespace Bww
