/***************************************************************************
                          main.cpp  -  description
                             -------------------
    begin                : Sam Nov 22 16:04:57 CET 2003
    copyright            : (C) 2003 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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "melconv.h"
using namespace std;


static Converter theConverter;

static string getVersion()
{
   return string("2.2.2");
}

static void progName()
{
   cerr<<"melconv v"<<getVersion().c_str()<<" (c) 2003-2008 Klaus Frieler"<<endl;
}

static void usage()
{
   progName();
   cerr<<"Usage: melconv -f [csv|midi|esac] [OPTIONS] -i INFILE [-o OUTFILE]"<<endl;
   cerr<<"or     melconv -f [csv|midi|esac] [OPTIONS] -I"<<endl;
   cerr<<"where  OPTIONS can be"<<endl;
   cerr<<"-c, --cut      CUT         cut after cut "<<endl;
   cerr<<"-t, --tempo    TEMPO       use TEMPO (in bpm) for conversion, default is 120bpm"<<endl;
   cerr<<"-p, --phrase   PHRASE      convert only phrase PHRASE (only from EsAC)"<<endl;
   cerr<<"-s, --segment              write segmentation values (only to CSV)"<<endl;
   cerr<<"-q, --quantize             quantize (only from MIDI, default 24)"<<endl;
   cerr<<"-x, --ignore-tempo-changes (only from MIDI)"<<endl;
}

static FormatOption formOpt[] = {
   FormatOption(esac, "esac"),
   FormatOption(esac, "esa"),
   FormatOption(csv,  "csv"),
   FormatOption(gnu,  "gnu"),
   FormatOption(midi,  "midi"),
   FormatOption(midi,  "mid"),
   FormatOption(notes,  "notes"),

};

static FormatExt formExt[] = {
   FormatExt(esac, "esa"),
   FormatExt(esac, "esac"),
   FormatExt(csv,  "csv"),
   FormatExt(gnu,  "gnu"),
   FormatExt(midi,  "mid"),
   FormatExt(notes,  "notes")
};

static cmdLineOption cmdOpt[] = {
   cmdLineOption(optFormat, "-f", "--format"),
   cmdLineOption(optTempo,  "-t", "--tempo"),
   cmdLineOption(optInfile, "-i", "--input"),
   cmdLineOption(optOutfile,"-o", "--output"),
   cmdLineOption(optHelp,   "-?", "--help"),
   cmdLineOption(optStdIn,  "-I", "--stdin"),
   cmdLineOption(optCut,    "-c", "--cut"),
   cmdLineOption(optPhrase, "-p", "--phrase"),
   cmdLineOption(optSegment,"-s", "--segment"),
   cmdLineOption(optQuant,  "-q", "--quantize"),
   cmdLineOption(optIgnoreTempoChanges,  "-x", "--ignore-tempo-changes"),
};

static Option recogOption(const string& name)
{
   for(uint i = 0; i<sizeof(cmdOpt)/sizeof(cmdOpt[0]); ++i){
      if(cmdOpt[i].is(name)) return cmdOpt[i].m_opt;      
   }
   return noopt;
   
}
static Format recogFormat(const string& name )
{
   for(uint i = 0; i<sizeof(formOpt)/sizeof(formOpt[0]); ++i){
      if(formOpt[i].is(name)) return formOpt[i].m_format;
   }
   return unknown;   
}

static string  getFormatExt(Format format)
{
   for(uint i = 0; i<sizeof(formExt)/sizeof(formExt[0]); ++i){
      if(formExt[i].is(format)) return formExt[i].m_sExt;
   }
   return string("");
}
static bool parseOptions(int argc, char **argv, Format& format, string& inFile, string& outFile, bool &bStdInMode)
{
   bool bRet = false;
   bStdInMode = false;
   if(argc<2) return false;

   for(int i = 1; i<argc; ++i){
      Option opt = recogOption(string(argv[i]));
      switch(opt)
      {
         case noopt:
            return false;
         case optInfile:
            i++;
            if(argv[i]) inFile = string(argv[i]);
            else return bRet;
         
         break;
         case optOutfile:
            i++;
            if(argv[i]) outFile = string(argv[i]);
            else return bRet;
         break;
         case optFormat:
            i++;
            if(argv[i]) format = recogFormat(string(argv[i]));
            else return bRet;
            break;
         case optTempo:
            i++;
            if(argv[i]){
               double tempo = 60000/atof(argv[i]);
               if(tempo>0) theConverter.setTempo(tempo);
            }
            else return bRet;
            break;
         break;
         case optHelp:
            usage();
            exit(1);
            break;
         case optStdIn:
            bStdInMode = true;
            break;
         case optSegment:
            theConverter.setWriteSegmentation(true);
            break;
         case optQuant:
            i++;
            if(argv[i]) theConverter.setQuantization(atoi(argv[i]));
            break;
         case optCut:
            i++;
            if(argv[i]) theConverter.setCut(atof(argv[i]));
            else return bRet;
            break;
         case optPhrase:
            i++;
            if(argv[i]) theConverter.setPhraseIndex(atoi(argv[i]));
            else return bRet;
            break;
         case optIgnoreTempoChanges:
            theConverter.setIgnoreTempoChanges(true);
            break;
         default:
            continue;
         break;
      }
      
   }
   bRet = true;
   return bRet;
}

static void onError(const string& errorTxt, bool bExit)
{
   cerr<<errorTxt.c_str()<<endl;;
   if(bExit)
      exit(-1);
}

string   toLower(const string& str)
{
   string tmp(str);
   for(uint i = 0; i<tmp.length(); ++i){
      if(tmp[i]>='A' && tmp[i]<='Z'){
         tmp[i] = tmp[i]-'A'+'a';
      }     
   }
   return tmp;
}

static Format getFormat(const string& inFile)
{
   Format retForm = unknown;
   uint pos = inFile.find_last_of('.');
   if(pos == string::npos){
      return retForm;
   } 

   string tmp = toLower(inFile.substr(pos+1));
   if(tmp == "esa") retForm = esac;
   else if(tmp=="csv") retForm = csv;
   else if(tmp=="gnu") retForm = gnu;
   else if(tmp=="mid") retForm = midi;
   else if(tmp=="notes") retForm = notes;
   return retForm;
}

static string getOutFile(const string& inFile, Format outFormat, bool bSpaceToUnderscore)
{
   string outfile("");
   uint pos = inFile.find_last_of('.');
   string frm = getFormatExt(outFormat);
   if(pos == string::npos){
      outfile = inFile + frm;
   }
   else outfile = inFile.substr(0,pos+1) + frm;  
   if(bSpaceToUnderscore){
      for(uint i = 0; i<outfile.size(); ++i){
         if(outfile[i] == ' ') outfile[i] = '_';
      }
   }
   return outfile;
}

static int userInteraction(double tempo, Format outFormat, double cut, uint phraseIdx)
{
   char buf[1024] = "";
   string inFile(""), outFile("");
   Format inFormat;

   if(outFormat == unknown){
      onError("Unrecognized output format.", false);
      return -1;
   }
   theConverter.setTempo(tempo);
   theConverter.setCut(cut);
   theConverter.setPhraseIndex(phraseIdx);
   while(true){
      int ret = fscanf(stdin, "%s", &buf[0]);
      if(buf[0] == '\0' || ret == EOF) break;
      inFile   =   buf;
      outFile  =   getOutFile(inFile, outFormat, true);
      inFormat =   getFormat(inFile);
      cerr<<"Processing "<<inFile<<"..."<<endl;;
      
      switch(inFormat)
      {
         case esac:
         case midi:
         case csv:
         case notes:
            if(!theConverter.read(inFile, inFormat)){
               cerr<<theConverter.getErrorText()<<endl;
               continue;
            }
         break;
         default:
            onError("Unrecognized input format!", false);
         break;
      }
      switch(outFormat)
      {
         case gnu:
         case csv:
         case esac:
         case midi:
         case notes:
      
            if(!theConverter.write(outFile, outFormat)){
               cerr<<theConverter.getErrorText()<<endl;
            }
            else{
               cerr<<outFile<<" written"<<endl;
            }
         break;
         default:
            onError("Unrecognized output format!", true);
         break;
      }
      
   }
   return 1;   
}

int main(int argc, char *argv[])
{
   bool bStdInMode = false;
   bool bWriteSegmentation = false;
   double tempo = 500.0, cut = 0.0;
   uint phraseIdx = 0;
   string inFile(""), outFile("");
   Format inFormat = unknown, outFormat = unknown;
   if(!parseOptions(argc, argv, outFormat, inFile, outFile, bStdInMode)){
      usage();
      exit(-1);
   }
   if (bStdInMode){
      return userInteraction(tempo, outFormat, cut, phraseIdx);
   }
   inFormat = getFormat(inFile);
   if(outFile.length()==0){
      outFile = getOutFile(inFile, outFormat, true);
   }
   switch(inFormat)
   {
      case esac:
      case midi:
      case csv:
      case notes:
         if(!theConverter.read(inFile, inFormat)){
            onError(theConverter.getErrorText(), true);
         }
      break;
      default:
         onError("Unrecognized input format!", true);
      break;
   }
   switch(outFormat)
   {
      case gnu:
      case csv:
      case esac:
      case midi:
      case notes:
         if(!theConverter.write(outFile, outFormat)){
            onError(theConverter.getErrorText(), true);
         }
         else{
            cout<<"Successfully written "<<outFile.c_str()<<endl;
         }
      break;
      default:
         onError("Unrecognized output format!", true);
      break;
   }
   return EXIT_SUCCESS;
}
