/***************************************************************************
                          CSV.cpp  -  description
                             -------------------
    begin                : Die Feb 8 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 "CSV.h"
#include "MIDITime.h"

const char *CSVReader::header[] = { "onset", "onsetics", "takt", "beat", "ticks", "pitch", "durs", "durtic", "dur16"};

char *CSVReader::subchr(char *str, char from, char to)
{
   uint len = strlen(str);
   if(!len) return str;

   for(uint i = 0; i<len; ++i){
      if(str[i] == from)
         str[i] = to;
   }
   return str;
}

string CSVReader::getToken(const char *str, uint which, const char delim) const
{
   uint i = 0;
   char *tmp;
   char *start = 0, *end = 0;
   string szRet("");
   if(!str || !which) return szRet;

   tmp = new char[strlen(str)+1];
   if(!tmp) return szRet;
   strcpy(tmp, str);

   start = tmp;
   end   = strchr(start, delim);

   if(!end){
      if(which != 1){
         goto out;
      }
      else{
         szRet = str;
         goto out;
      }
   }

   for(i = 0; i<which - 1; ++i){
      end = strchr(start, delim);
      if(!end){
         if(i == which-1){
            end = start + strlen(start);
            break;
         }
         else goto out;
      }
      start = end+1;
   }
   end = strchr(start, delim);
   if(end)*end = '\0';
   szRet = string(start);

out:
   delete []tmp;
   return szRet;
}


bool CSVReader::read()
{
   bool  bRet = false;
   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;
   }
   
   Tone        tone;
   Signature   sig;
   char        buf[1024] = "";
   string      buf1, buf2;
   uint        tmpCount = 0;
   double      tempo = 0;
   int         measure = -1, startMeasure = -1;
   uint        beat = 0, ticks = 0;
   bool        bBeatAnalysis = true;
   bool        bCorrectFirstNotes = false;
   //read first line
   if(fgets(buf,sizeof(buf), fp) == NULL){
      m_sErrText = "I/O Error";
      goto out;
   }
   if(!parseSignature(buf, sig)){
      goto out;
   }
   m_pMel->setSignature(sig);

   // read 2. line with data descr
   if(fgets(buf,sizeof(buf), fp) == NULL){
      m_sErrText = m_sInFile + string(": I/O Error");
      goto out;
   }
   if(!parseDataDescr(buf)){
      goto out;
   }

   //read data lines
   while(!feof(fp)){
      if(fgets(buf,sizeof(buf), fp) == NULL){
         if(ferror(fp)){
            m_sErrText = m_sInFile + string(": I/O Error");
            goto out;
         }
         break;
      }
      //override \n
      buf[strlen(buf)-1] = '\0';
      //substitute , -> .
      subchr(buf, ',','.');
      
      //memorize two lines for guessing tempo
      tmpCount++;
      if(tmpCount == 1){
         buf1 = buf;
      }
      if(tmpCount == 2){
         buf2 = buf;
      }
      if(tmpCount == 3){
         tempo = guessTempo(buf1, buf2, sig);
         m_pMel->setTempo(tempo);
         m_dTimeUnit = guessTimeUnit(buf1, buf2);
         if(m_dTimeUnit == 1){
            m_dTimeUnit = 1000;
            bCorrectFirstNotes = true;
         }
         else m_dTimeUnit = 1;
      }
      //find first one, if any...
      if(bBeatAnalysis){
         if(parseBarposition(buf, measure, beat, ticks) != -1){
            if(startMeasure == -1){
               startMeasure = measure;
               if(beat == 1 && ticks == 0){
                  m_nFirstOneIdx = m_pMel->size();
                  bBeatAnalysis = false;
               }
            }
            else{
               if(measure != startMeasure){
                  if(beat == 1 && ticks == 0){
                     m_nFirstOneIdx = m_pMel->size();
                     bBeatAnalysis = false;
                  }
                  else{
                        bBeatAnalysis = false;
                        m_nFirstOneIdx = m_pMel->size()+1;
                     }
                  }
               }
         }
         else{
            bBeatAnalysis = false;
         }
      }
      //put data into...
      if(!parseDataLine(buf, tone)){
         goto out;
      }
      tone.setTime(tone.getTime() * m_dTimeUnit);
      tone.setDuration(tone.getDuration() * m_dTimeUnit);
      m_pMel->push_back(tone);
   }
   //due to tempo guessing possible need some correctons on the first two notes
   if(bCorrectFirstNotes){
      m_pMel->at(0).setTime(m_pMel->at(0).getTime() * m_dTimeUnit);
      m_pMel->at(0).setDuration(m_pMel->at(0).getDuration() * m_dTimeUnit);
      m_pMel->at(1).setTime(m_pMel->at(1).getTime() * m_dTimeUnit);
      m_pMel->at(1).setDuration(m_pMel->at(1).getDuration() * m_dTimeUnit);
   }
   uint key = m_pMel->calculateKey();
   m_pMel->setKey(key); 
   m_pMel->setTempo(tempo);
   m_pMel->check();

   m_pMel->setFirstOneIdx(m_nFirstOneIdx);
   //m_pMel->dump();

   bRet = true;
out:
   fclose(fp);
   return bRet;   
}

void CSVReader::dumpMelody()
{
   for(uint i = 0; i<m_pMel->size(); ++i){
      m_pMel->at(i).dump();
   }
}

bool CSVReader::parseSignature(const char * buf, Signature& sig)
{
   string str(buf);
   int ndx = -1;
   uint count = 0;
   int i  = 0;
   if((ndx = str.find("/")) == -1){
      return false;
   }

   for(i = ndx-1; i>=0 ; --i){
      if(atoi(str.substr(i).c_str()) == 0 ) break;
      count = atoi(str.substr(i).c_str());
   }
   if(count == 0){
      m_sErrText = "No signature found";
      return false;
   }
   sig.setCount(count);
   MRESULT mr = sig.setDenom(atoi( str.substr(ndx+1).c_str())); 
   if(FAIL(mr)){
      m_sErrText = "Invalid signature denominator found";
      return false;
   }

   if((ndx = str.find("Ticks")) == -1){
      m_sErrText = "No division specified";
      return false;
   }
   count = 0;

   for(i = ndx + 5; i<(int)str.length() ; ++i){
      if((count = atoi(str.substr(i).c_str())) != 0 ) break;
   }
   if(count == 0){
      m_sErrText = "No division specified";
      return false;
   }
   sig.setTicks(count);
   return true;
}


bool  CSVReader::parseDataDescr(const char* sDataDescr)
{
   bool bRet = true;
   uint  bFoundCount = 0;
   uint  headerCount = sizeof(CSVReader::header)/sizeof(CSVReader::header[0]);
   
   for(uint k = 0; k<headerCount; ++k){
      for(uint i = 1; ;++i){
         string token = getToken(sDataDescr, i, ';');
         if(token.length() == 0) break;
         if(token == header[k]){
            m_headerIdz.push_back(i);
         }
      }
   }
   if(m_headerIdz.size()>onset){
      m_iOnsetIdx = m_headerIdz[onset];
      bFoundCount++;
   }
   if(m_headerIdz.size()>pitch){
      m_iPitchIdx = m_headerIdz[pitch];
      bFoundCount++;
   }
   if(m_headerIdz.size()>durs){
      m_iDurationIdx = m_headerIdz[durs];
      bFoundCount++;
   }
   if(bFoundCount != 3){
      m_sErrText  = " Insufficient data description";
      bRet = false;
   }

   return bRet;
   
}

bool CSVReader::parseDataLine(const char *buf, Tone& tone)
{
   bool bRet = false;

   if(!buf){
      m_sErrText = "Bad line buffer";
      return bRet;
   }
   string token = getToken(buf, m_iOnsetIdx, ';');
   if(token.length() == 0){
      m_sErrText = "No onset found";
      return bRet;
   }
   tone.setTime(atof(token.c_str()));

   token = getToken(buf, m_headerIdz[CSVReader::onsetics], ';');
   if(token.length() == 0){
      m_sErrText = "No onsetics found";
   }
   tone.setTicks(atoi(token.c_str()));

   token = getToken(buf, m_iPitchIdx, ';');
   if(token.length() == 0){
      m_sErrText = "No pitch found";
      return bRet;
   }
   tone.setPitch((uint)atoi(token.c_str()));
   token = getToken(buf, m_iDurationIdx, ';');
   if(token.length() == 0){
      m_sErrText = "No duration found";
      return bRet;
   }

   tone.setDuration(atof(token.c_str()));

   BarPosition pos(m_pMel->getSignature());
   int measure = 0;
   uint   beat = 0, ticks = 0;
   if(parseBarposition(buf, measure, beat, ticks) == -1){
      m_sErrText = "No bar position found";
      return bRet;   
   }
   pos.set(measure, beat, ticks);
   tone.setBarPosition(pos);

   bRet = true;
   return bRet;   
}

int CSVReader::parseBarposition(const char *buf, int &measure, uint& beat, uint &ticks) const
{
   string token;
   if(!buf) return -1;
   token = getToken(buf, m_headerIdz[CSVReader::takt], ';');
   if(token.length() == 0){
      return -1;
   }
   measure = (uint) atoi(token.c_str());
   token = getToken(buf, m_headerIdz[CSVReader::beat], ';');
   if(token.length() == 0){
      return -1;
   }
   beat = (uint) atoi(token.c_str());
   token = getToken(buf, m_headerIdz[CSVReader::ticks], ';');
   if(token.length() == 0){
      return -1;
   }
   ticks = (uint) atoi(token.c_str());

   return (int)measure;     
}

double CSVReader::guessTempo(const string& buf1, const string& buf2, const Signature& sig) const
{
   double tempo = 500.0;
   MIDITime t1, t2;

   t1.setTicksPerBeat(sig.ticks());
   t2.setTicksPerBeat(sig.ticks());
   t1.setSignature(sig.count());
   t2.setSignature(sig.count());
   
   double onset1 =    atof(getToken(buf1.c_str(), m_headerIdz[CSVReader::onset], ';').c_str());
   double onset2 =    atof(getToken(buf2.c_str(), m_headerIdz[CSVReader::onset], ';').c_str());

   int measure = 0, beat = 0, ticks  = 0;
   measure  = atoi(getToken(buf1.c_str(), m_headerIdz[CSVReader::takt], ';').c_str());
   beat     = atoi(getToken(buf1.c_str(), m_headerIdz[CSVReader::beat], ';').c_str());
   ticks    = atoi(getToken(buf1.c_str(), m_headerIdz[CSVReader::ticks], ';').c_str());
   t1.set(measure, beat, ticks);
   measure  = atoi(getToken(buf2.c_str(), m_headerIdz[CSVReader::takt], ';').c_str());
   beat     = atoi(getToken(buf2.c_str(), m_headerIdz[CSVReader::beat], ';').c_str());
   ticks    = atoi(getToken(buf2.c_str(), m_headerIdz[CSVReader::ticks], ';').c_str());
   t2.set(measure, beat, ticks);
   
   uint diffTicks = t2.getDiffTicks(t1);
   double diffTime = onset2-onset1;
   double beatFraction = (0.0+diffTicks)/sig.ticks();
   double unit = guessTimeUnit(buf1, buf2);
   if(unit == 1) diffTime *= 1000;
   if(beatFraction>0){
      tempo = diffTime/beatFraction;
   }
   return tempo;//return it anyway...
}

double CSVReader::guessTimeUnit(const string &buf1, const string& buf2) const
{
   double duration1 = atof(getToken(buf1.c_str(), m_headerIdz[CSVReader::durs], ';').c_str());
   double duration2 = atof(getToken(buf2.c_str(), m_headerIdz[CSVReader::durs], ';').c_str());

   double    unit1 = duration1>2 ? (duration1>120.0 ? 1000.0 : 0.0) : 1.0;
   double    unit2 = duration2>2 ? (duration2>120.0 ? 1000.0 : 0.0) : 1.0;

   double unit = 1.0;
   if(unit1 != unit2){
      if(unit1 == 0) {
         if(unit2 != 0) unit = unit2; 
      }
      else if(unit2 == 0){
         if(unit1 != 0) unit = unit1; 
      }
   }
   else{
      unit = unit1;
   }
   return unit;
}

