/***************************************************************************
                          NotesReader.cpp  -  description
                             -------------------
    begin                : Fre Feb 11 2005
    copyright            : (C) 2005 by klaus frieler
    email                : kgf@omniversum.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "NoteReader.h"
#include "Melody.h"

bool NotesReader::parseLine(const char *line, readMode mode, iData& vals)
{
   uint val1 = 0, val2 = 0, val3 = 0; 
   vals.clear();
   if(mode == modeNotes ){
      if(sscanf(line, "Note %d %d %d", &val1, &val2, &val3) != 3){
         if(sscanf(line, "Rest %d", &val1) != 1){
            m_sErrText =" Invalid line";
            return false;
         }
         else{
         }
      }
   }
   if(mode == modeNoteOnOff){
      if(sscanf(line, "Note-on %d %d", &val1, &val3) != 2){
         if(sscanf(line, "Note-off %d %d", &val2, &val3) != 2){
            m_sErrText =" Invalid line";
            return false;
         }
         else{
            val1 = nopos;
         }
      }
      else{
         val2 = nopos;
      }
   }
   vals.push_back(val1);
   vals.push_back(val2);
   vals.push_back(val3);
   return true;
}

bool NotesReader::readNotes()
{
   bool  bRet = false;
   bool  bFirstLine = true;
   if(!m_pMel){
      return bRet;
   }
 
   FILE *fp = fopen(m_sInFile.c_str(),"r");
   if(!fp){
      m_sErrText = string("Could not open ") + m_sInFile;
      return bRet;
   }
   uint iPitch = 0, iLastPitch = 0;
   uint iOnset = 0, iLastOnset = 0;
   uint iOffset = 0, iLastOffset = 0;
   double t    =  0.0;
   double dur  =  0.0;

   Tone tone;
   iData vals;
   char buf[4096] = "";
   while(true){
      fgets(buf, sizeof(buf), fp);
      if(feof(fp)) break;
      if(ferror(fp)){
         m_sErrText = "I/O error during read.";
         break;
      }
      if(buf[0] == '%' || buf[0] == 0) continue; //comment
      if(!parseLine(buf, modeNotes, vals)){
         goto out;
      }
      iLastOnset   = iOnset;
      iLastOffset  = iOffset;
      iLastPitch   = iPitch;

      iOnset       = vals[0];
      iOffset      = vals[1];
      iPitch       = vals[2];
      if(!bFirstLine){
         if(iLastOffset>iOnset){
            iLastOffset = iOnset;
         }
         if(iOnset<=iLastOnset){
            m_sErrText = "Polyphonic data";
            goto out;
         }
         t    =  iLastOnset;
         dur  =  (iLastOffset-iLastOnset);
         tone.set(nopos, t, iLastPitch, dur);
         tone.dump();
         m_pMel->push_back(tone);
      }
      else{
         bFirstLine = false;
      }
   }
   t    = iOnset;
   dur  = (iOffset-iOnset);
   tone. set(invalidTick, t, iPitch, dur);
   m_pMel->push_back(tone);

   bRet = true;
out:
   fclose(fp);
   return bRet;
}
#define STATE_UNDEFINED 0
#define STATE_NOTEON    1
#define STATE_NOTEOFF   2

bool NotesReader::readNoteOnOff()
{
   bool  bRet = false;
   if(!m_pMel){
      return bRet;
   }
 
   FILE *fp = fopen(m_sInFile.c_str(),"r");
   if(!fp){
      m_sErrText = string("Could not open ") + m_sInFile;
      return bRet;
   }
   uint iPitch = 0, iLastPitch = 0;
   uint iOnset = 0;
   uint iOffset = 0;
   uint state = STATE_UNDEFINED, lastState = STATE_UNDEFINED;

   Tone tone;
   iData vals;
   char buf[4096] = "";
   while(true){
      fgets(buf, sizeof(buf), fp);
      if(feof(fp)) break;
      if(ferror(fp)){
         m_sErrText = "I/O error during read.";
         break;
      }
      if(!parseLine(buf, modeNoteOnOff, vals)){
         continue;
      }
      lastState = state;
      iLastPitch = iPitch;
      if(vals[0] == nopos){
         state = STATE_NOTEOFF;
         iOffset = vals[1];
      }
      else if(vals[1] == nopos){
         state = STATE_NOTEON;
         iOnset = vals[0];
      }
      if(lastState == STATE_UNDEFINED && state != STATE_NOTEON){
         m_sErrText = "First event note-off.";
         goto out;
      }
      if(lastState == state){
         m_sErrText = "Polyphonic data.";
         goto out;
      } 
      iPitch = vals[2];
      if(state == STATE_NOTEOFF){
         if(iLastPitch != iPitch){
            m_sErrText = "Unmatched note off.";
            goto out;
         }
         if(iOffset<= iOnset){
            m_sErrText = "Invalid offset.";
            goto out;
         }
      }
      if(state == STATE_NOTEOFF){
         tone.set(nopos, iOnset, iPitch, iOffset-iOnset);
         tone.dump();
         m_pMel->push_back(tone);
      }
   }
   bRet = true;
out: 
   fclose(fp);
   return bRet;
}

bool NotesReader::read()
{
   bool  bRet = false;
   char buf[4096] = "";
   string line;
   readMode  mode = unknown;
   if(!m_pMel){
      m_sErrText = "No melody object";
      return bRet;
   }

   FILE *fp = fopen(m_sInFile.c_str(),"r");
   if(!fp){
      m_sErrText = string("Could not open ") + m_sInFile;
      return bRet;
   }
   //get file format
   while(!feof(fp) && mode ==unknown){
      fgets(buf, sizeof(buf), fp);
      if(ferror(fp)){
         m_sErrText = "I/O error during read.";
         break;
      }
      if(strstr(buf, "Note-on") != 0 || strstr(buf, "Note-off") != 0){
         mode = modeNoteOnOff;
      }
      else if(strstr(buf, "Note") != 0){
         mode = modeNotes;
      }
   }
   fclose(fp);
   m_pMel->setKey(0);
   m_pMel->setSignature(4,4,192);
   m_pMel->setTempo(500.0);

   switch(mode){
      case modeNotes:
         bRet = readNotes();
         break;
      case modeNoteOnOff:
         bRet = readNoteOnOff();
         break;
      default:
         m_sErrText = "Bad file format";
      break;
   }
   m_pMel->setIOIUnit(m_pMel->getMinIOITicks());
   m_pMel->setDurationUnit(m_pMel->getMinDurationTicks());
   m_pMel->check();

   return bRet;
}
