/* Created by Ghost Gloomy on 2018/11/8. */

#include <algorithm>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <stdexcept>
#include <iomanip>
#include <stack>
#include "util_log.h"
#include "util_score.h"
#include "XmlReader.h"
#include "Note.h"
#include "Dynamics.h"
#include "Key.h"
#include "Syllabic.h"
#include "ScorePosition.h"
#include "xml_tags.h"
#include "util_score.h"

namespace sinsy
{

namespace
{
typedef size_t WedgeType;
WedgeType WEDGE_NONE = 0;
WedgeType WEDGE_CRESCENDO = 1;
WedgeType WEDGE_DECRESCENDO = 2;

/*!
 NoteSetter
 */
class NoteSetter
{
public:
   typedef void (*Func)(Note& note, const XmlData*);

   //! constructor
   NoteSetter(Note& n, Func f) : note(n), func(f) {}

   //! copy constructor
   NoteSetter(const NoteSetter& obj) : note(obj.note), func(obj.func) {}

   //! destructor
   virtual ~NoteSetter() {}

   //! ...
   void operator()(const XmlData* data) {
      (*func)(note, data);
   }

private:
   //! assignment operator (donot use)
   NoteSetter& operator=(const NoteSetter&);

   //! note
   Note& note;

   //! function
   Func func;
};

/*!
 convert string to type T
 */
template <class T>
T to(const std::string& str)
{
   std::istringstream iss(str);
   T ret(0);
   iss >> ret;
   return ret;
}

/*!
 set tie
 */
void setTie(Note& note, const XmlData* data)
{
   std::string type(data->getAttribute("type"));
   if (type.empty()) {
      return;
   }
   if (0 == type.compare("start")) {
      note.setTieStart(true);
   } else if (0 == type.compare("stop")) {
      note.setTieStop(true);
   } else {
      throw std::runtime_error("<tie> tag has unexpected attribute(type)");
   }
}

/*!
 set slur
 */
void setSlur(Note& note, const XmlData* data)
{
   std::string type(data->getAttribute("type"));
   int number(to<int>(data->getAttribute("number")));
   if (type.empty()) {
      return;
   }
   if (0 == type.compare("start")) {
      note.getSlur().addStart(number);
      note.setSlurStart(true);
   } else if (0 == type.compare("stop")) {
      note.getSlur().addStop(number);
      note.setSlurStop(true);
   } else if (0 == type.compare("continue")) {
      // do nothing
   } else {
      throw std::runtime_error("<slur> tag has unexpected attribute(type)");
   }
}

/*!
 set pitch
 */
void setPitch(Note& note, const XmlData* data)
{
   XmlData::Children::const_iterator itr(data->childBegin());
   XmlData::Children::const_iterator itrEnd(data->childEnd());
   std::string step("C");
   Pitch::Octave octave(Pitch::DEFAULT_OCTAVE);
   int alter = 0;
   for (; itr != itrEnd; ++itr) {
      const XmlData* child(*itr);
      std::string tag(child->getTag());
      if (0 == tag.compare(TAG_STEP)) {
         step = child->getData();
      } else if (0 == tag.compare(TAG_ALTER)) {
         std::string value(child->getData());
         if (!value.empty()) {
            alter = to<int>(value);
         }
      } else if (0 == tag.compare(TAG_OCTAVE)) {
         std::string value(child->getData());
         if (!value.empty()) {
            octave = to<Pitch::Octave>(value);
         }
      }
   }
   Pitch p(step, octave, alter);
   note.setPitch(p);
}

/*!
 convert children of lyric tag
 */
void convertLyricChildren(Note& note, const XmlData* data)
{
   std::string tag(data->getTag());
   if (0 == tag.compare(TAG_SYLLABIC)) {
      Syllabic s(data->getData());
      note.setSyllabic(s);
   } else if (0 == tag.compare(TAG_TEXT)) {
      note.setLyric(data->getData());
   }
}

/*!
 convert children of articulations tag
 */
void convertArticulationsChildren(Note& note, const XmlData* data)
{
   std::string tag(data->getTag());
   if (0 == tag.compare(TAG_BREATH_MARK)) {
      note.setBreathMark(true);
   } else if (0 == tag.compare(TAG_ACCENT)) {
      note.setAccent(true);
   } else if (0 == tag.compare(TAG_STACCATO)) {
      note.setStaccato(true);
   }
}

/*!
 convert children of technical tag
 */
void convertTechnicalChildren(Note& note, const XmlData* data)
{
   std::string tag(data->getTag());
   if (0 == tag.compare(TAG_UP_BOW)) {
      note.setBreathMark(true);
   }
}

/*!
 convert children of notations tag
 */
void convertNotationsChildren(Note& note, const XmlData* data)
{
   std::string tag(data->getTag());
   if (0 == tag.compare(TAG_TIE)) {
      setTie(note, data);
   } else if (0 == tag.compare(TAG_TIED)) {
      setTie(note, data);
   } else if (0 == tag.compare(TAG_SLUR)) {
      setSlur(note, data);
   } else if (0 == tag.compare(TAG_ARTICULATIONS)) {
      std::for_each(data->childBegin(), data->childEnd(), NoteSetter(note, &convertArticulationsChildren));
   } else if (0 == tag.compare(TAG_TECHNICAL)) {
      std::for_each(data->childBegin(), data->childEnd(), NoteSetter(note, &convertTechnicalChildren));
   }
}

/*!
 convert children of note tag
 */
void convertNoteChildren(Note& note, const XmlData* data)
{
   std::string tag(data->getTag());
   if (0 == tag.compare(TAG_DURATION)) {
      note.setDuration(to<size_t>(data->getData()));
   } else if (0 == tag.compare(TAG_PITCH)) {
      note.setRest(false);
      setPitch(note, data);
   } else if (0 == tag.compare(TAG_REST)) {
      note.setRest(true);
   } else if (0 == tag.compare(TAG_LYRIC)) {
      std::for_each(data->childBegin(), data->childEnd(), NoteSetter(note, &convertLyricChildren));
   } else if (0 == tag.compare(TAG_TIE)) {
      setTie(note, data);
   } else if (0 == tag.compare(TAG_SLUR)) {
      setSlur(note, data);
   } else if (0 == tag.compare(TAG_NOTATIONS)) {
      std::for_each(data->childBegin(), data->childEnd(), NoteSetter(note, &convertNotationsChildren));
   }
}

class MeasureScore : public TempScore
{
public:
   //! constructor
   MeasureScore() {}

   //! destructor
   virtual ~MeasureScore() {
      clear();
   }

   //! clear
   void clear() {
      TempScore::clear();
      noteAdders.clear();
   }

   //! add note
   void addNote(const Note& note) {
      XmlNoteAdder* n(new XmlNoteAdder(note));
      tempList.push_back(n);
      noteAdders.push_back(n);
   }

   //! adjust duration
   void adjustDuration(size_t duration) {
      size_t totalDur(0);
      NoteAdders::iterator itrEnd(noteAdders.end());
      for (NoteAdders::iterator itr(noteAdders.begin()); itrEnd != itr; ++itr) {
         // measure cannot conclude this note, because duration of notes in this measure is too long
         if (duration == totalDur) {
            WARN_MSG("Number of notes in a measure is too large");
            (*itr)->setDuration(0);
         } else {
            size_t dur((*itr)->getDuration());
            totalDur += dur;
            // note duration is too long
            if (duration < totalDur) {
               WARN_MSG("Duration of notes in a measure is too long");
               (*itr)->setDuration(dur + duration - totalDur);
               totalDur = duration;
            }
         }
      }

      // duration of notes in this measure is too short
      if (totalDur < duration) {
         WARN_MSG("Duration of notes in a measure is too short");
         if (noteAdders.empty()) {
            Note note;
            note.setRest(true);
            this->addNote(note);
         }
         XmlNoteAdder* n(noteAdders.back());
         size_t dur(n->getDuration());
         n->setDuration(dur + duration - totalDur);
      }
   }

private:
   //! copy constructor (donot use)
   MeasureScore(const MeasureScore&);

   //! assignment operator (donot use)
   MeasureScore& operator=(const MeasureScore&);

   class XmlNoteAdder : public ITempData
   {
   public:
      //! constructor
      XmlNoteAdder(const Note& n) : note(n) {}

      //! destructor
      virtual ~XmlNoteAdder() {}

      //! write to score
      virtual void write(IScoreWritable& sm) const {
         if (0 < note.getDuration()) {
            sm.addNote(note);
         }
      }

      //! get duration
      size_t getDuration() const {
         return note.getDuration();
      }

      //! set duration
      void setDuration(size_t d) {
         note.setDuration(d);
      }

   private:
      //! note
      Note note;
   };

   typedef std::vector<XmlNoteAdder*> NoteAdders;

   //! list of note adders
   NoteAdders noteAdders;
};

class XmlContainer
{
public:
   //! constructor
   XmlContainer(IScoreWritable& writable) : scoreWritable(writable), divisions(1), wedge(WEDGE_NONE), tempo(0.0) {}

   //! destructor
   virtual ~XmlContainer() {}

   //! write from xml data
   void write(const XmlData* xmlData) {
      // empty
      if (NULL == xmlData) {
         return;
      }

      if (0 != xmlData->getTag().compare(TAG_SCORE_PARTWISE)) {
         ERR_MSG("First tag of XML file is not equal to <score_partwise>");
         throw std::runtime_error("First tag of XML file is not equal to <score_partwise>");
      }
      XmlContainer container(scoreWritable);
      std::for_each(xmlData->childBegin(), xmlData->childEnd(), Adapter(container, &XmlContainer::convertScorePartwiseChildren));
   }

private:
   //! copy constructor (donot use)
   XmlContainer(const XmlContainer&);

   //! assignment operator (donot use)
   XmlContainer& operator=(const XmlContainer&);

   //! set sound
   void setSound(const XmlData* data) {
      std::string tempoStr(data->getAttribute("tempo"));
      if (tempoStr.empty()) {
         return;
      }
      double t(to<double>(tempoStr));
      if (tempo != t) {
         measureScore.changeTempo(t);
         tempo = t;
      }
   }

   //! set wedge
   void setWedge(const XmlData* data) {
      std::string type(data->getAttribute("type"));
      if (type.empty()) {
         return;
      }
      if (0 == type.compare("crescendo")) {
         measureScore.startCrescendo();
         wedge = WEDGE_CRESCENDO;
      } else if (0 == type.compare("diminuendo")) {
         measureScore.startDiminuendo();
         wedge = WEDGE_DECRESCENDO;
      } else if (0 == type.compare("stop")) {
         if (WEDGE_CRESCENDO == wedge) {
            measureScore.stopCrescendo();
         } else if (WEDGE_DECRESCENDO == wedge) {
            measureScore.stopDiminuendo();
         } else {
            WARN_MSG("wedge stop tag is appeared, but crescendo and decrescendo are not set");
         }
         wedge = WEDGE_NONE;
      }
   }

   //! set key
   void setKey(const XmlData* data) {
      XmlData::Children::const_iterator itr(data->childBegin());
      XmlData::Children::const_iterator itrEnd(data->childEnd());
      bool changed(false);
      for (; itr != itrEnd; ++itr) {
         const XmlData* child(*itr);
         std::string tag(child->getTag());
         std::string value(child->getData());
         if (0 == tag.compare(TAG_FIFTHS)) {
            if (value.empty()) {
               WARN_MSG("<fifths> tag in XML file has no value");
            }
            key.setFifths(to<int>(value));
            changed = true;
         } else if (0 == tag.compare(TAG_MODE)) {
            if (value.empty()) {
               WARN_MSG("<mode> tag in XML file has no value");
            }
            key.setMode(Mode(value));
            changed = true;
         }
      }
      if (changed) {
         measureScore.changeKey(key);
      }
   }

   //! set time
   void setTime(const XmlData* data) {
      XmlData::Children::const_iterator itr(data->childBegin());
      XmlData::Children::const_iterator itrEnd(data->childEnd());
      bool changed(false);
      for (; itr != itrEnd; ++itr) {
         const XmlData* child(*itr);
         std::string tag(child->getTag());
         std::string value(child->getData());
         if (0 == tag.compare(TAG_BEATS)) {
            if (value.empty()) {
               WARN_MSG("<beats> tag in XML file has no value");
            }
            beat.setBeats(to<size_t>(value));
            changed = true;
         } else if (0 == tag.compare(TAG_BEAT_TYPE)) {
            if (value.empty()) {
               WARN_MSG("<beat_type> tag in XML file has no value");
            }
            beat.setBeatType(to<size_t>(value));
            changed = true;
         }
      }
      if (changed) {
         measureScore.changeBeat(beat);
      }
   }

   //! set note
   void setNote(const XmlData* data) {
      Note note;
      std::for_each(data->childBegin(), data->childEnd(), NoteSetter(note, &convertNoteChildren));

      note.setDuration(note.getDuration() * BASE_DIVISIONS / divisions); // divisions : 960

      measureScore.addNote(note);
   }

   //! convert attributes children
   void convertAttributesChildren(const XmlData* data) {
      std::string tag(data->getTag());
      if (0 == tag.compare(TAG_DIVISIONS)) {
         divisions = to<size_t>(data->getData());
      } else if (0 == tag.compare(TAG_KEY)) {
         setKey(data);
      } else if (0 == tag.compare(TAG_TIME)) {
         setTime(data);
      } else if (0 == tag.compare(TAG_SOUND)) {
         setSound(data);
      }
   }

   //! convert dynamics children
   void convertDynamicsChildren(const XmlData* data) {
      std::string tag(data->getTag());
      Dynamics d(tag);
      measureScore.changeDynamics(d);
   }

   //! convert direction-type children
   void convertDirectionTypeChildren(const XmlData* data) {
      std::string tag(data->getTag());
      if (0 == tag.compare(TAG_WEDGE)) {
         setWedge(data);
      } else if (0 == tag.compare(TAG_DYNAMICS)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertDynamicsChildren));
      }
   }

   //! convert direction children
   void convertDirectionChildren(const XmlData* data) {
      std::string tag(data->getTag());
      if (0 == tag.compare(TAG_DIRECTION_TYPE)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertDirectionTypeChildren));
      } else if (0 == tag.compare(TAG_SOUND)) {
         setSound(data);
      }
   }

   //! convert measure children
   void convertMeasureChildren(const XmlData* data) {
      std::string tag(data->getTag());
      if (0 == tag.compare(TAG_NOTE)) {
         setNote(data);
      } else if (0 == tag.compare(TAG_ATTRIBUTES)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertAttributesChildren));
      } else if (0 == tag.compare(TAG_SOUND)) {
         setSound(data);
      } else if (0 == tag.compare(TAG_DIRECTION)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertDirectionChildren));
      }
   }

   //! convert pert children
   void convertPartChildren(const XmlData* data) {
      if (0 == data->getTag().compare(TAG_MEASURE)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertMeasureChildren));
         measureScore.adjustDuration(getMeasureDuration(beat));
         scoreWritable << measureScore;
         measureScore.clear();
      }
   }

   //! convert score-partwise children
   void convertScorePartwiseChildren(const XmlData* data) {
      if (0 == data->getTag().compare(TAG_PART)) {
         std::for_each(data->childBegin(), data->childEnd(), Adapter(*this, &XmlContainer::convertPartChildren));
      }
   }

   typedef ForEachAdapter<XmlContainer, const XmlData*> Adapter;

   // writable score
   IScoreWritable& scoreWritable;

   //! key
   Key key;

   //! beat
   Beat beat;

   //! divisions
   size_t divisions;

   //! measure score
   MeasureScore measureScore;

   //! wedge
   WedgeType wedge;

   //! tempo
   double tempo;
};

}; // namespace

/*!
 constructor
 */
XmlReader::XmlReader() :
   xmlData(NULL), part(NULL)
{
   initXmlData();
}

/*!
 destructor
 */
XmlReader::~XmlReader()
{
   delete xmlData;
}

/*!
 clear
 */
void XmlReader::clear()
{
   delete xmlData;
   initXmlData();
}

/*!
 write score
 */
void XmlReader::write(IScoreWritable& writable) const
{
   writable.setEncoding(encoding);
   XmlContainer container(writable);
   container.write(xmlData);
}

/*!
 @internal

 initialize xml data
 */
void XmlReader::initXmlData()
{
   XmlData* scorePart = new XmlData("score-part");
   scorePart->addAttribute("id", "P1");
   scorePart->addChild(new XmlData("part-name", "MusicXML Part"));

   XmlData* partList = new XmlData("part-list");
   partList->addChild(scorePart);

   part = new XmlData(TAG_PART);
   part->addAttribute("id", "P1");

   xmlData = new XmlData(TAG_SCORE_PARTWISE);
   xmlData->addAttribute("version", "2.0");
   xmlData->addChild(partList);
   xmlData->addChild(part);
}

/*!
 read xml from stream

 @param stream input stream
 @return if success, true
 */
bool XmlReader::readXml(IReadableStream& stream)
{
   try {
      delete xmlData;
      xmlData = NULL;

      xmlData = parser.read(stream, encoding);
   } catch (const StreamException& ex) {
      ERR_MSG("XML file reading error : " << ex.what());
      return false;
   }
   return true;
}

/*!
 get xml data
 */
const XmlData* XmlReader::getXmlData() const
{
   return this->xmlData;
}

};  // namespace sinsy
