/// McVerter.cpp
/** The command-line version of MRIConvert.
*/

#include "McVerter.h"

// Macro in place of _() since std::cerr does not like utf16 or utf32.
#define _C(string) string  //wxString(_(string)).utf8_str()

using namespace jcs;

// This permits cout to handle a vector.
template<class T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v)
{
  copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cerr, " ")); 
  return os;
}

int main(int argc, char** argv)
{
  // First make sure we can use wx stuff.
  wxInitializer initializer;
  if ( !initializer ) {
    std::cerr << "Failed to initialize the wxWidgets library, aborting.";
    return EXIT_NO_WX;
  }

  
  // Localization steps
  wxLocale m_locale;
  if ( !m_locale.Init() ) {
    wxLogError(_T("This language is not supported by the system."));
  }
  // Development only, distribution should install in standard location.
  //wxLocale::AddCatalogLookupPathPrefix(_T("l10n"));
  // Initialize the catalogs we'll be using
  m_locale.AddCatalog(_T("mriconvert"));


  // Controlling variables.
  bool showVersion = false;
  bool splitSubject = false;
  bool splitDir = false;
  int outputDimension = 3;
  bool headerOnly = false;
  bool niiOutput = false;
  bool v16Output = false;
  bool rescaleData = false;
  bool usePatientId = false;
  long skipVolumes = 0;
  wxString matchSeries = "";
  wxString fnFormat = "";
  wxString outputDir = "";
  wxString outputFormat = "";
  std::vector<wxString> inputFiles;
  
/**** 20131201cdt
      Revisiting the wxCmdLineParser, it appears that it does not crash.
      Or, the sample code is correct for version 3.0.0.
*/
    static const wxCmdLineEntryDesc cmdLineDesc[] =
    {
      { wxCMD_LINE_SWITCH, "h", "help", _C("Show this help message"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
      { wxCMD_LINE_OPTION, "o", "output", _C("Output directory for converted files"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
      { wxCMD_LINE_OPTION, "f", "format", _C("Output format: fsl, spm, meta, nifti, analyze, or bv."), wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
      { wxCMD_LINE_SWITCH, "v", "verbose", _C("Verbose mode") },
      { wxCMD_LINE_SWITCH, "q", "quiet", _C("Quiet mode") },
      { wxCMD_LINE_SWITCH, "V", "version", _C("Version information") },
      { wxCMD_LINE_SWITCH, "j", "split_subj", _C("Save each subject to a separate directory") },
      { wxCMD_LINE_SWITCH, "x", "split_dir", _C("Save each series to a separate directory") },
      { wxCMD_LINE_SWITCH, "d", "fourd", _C("Save output volumes as 4D files") },
      { wxCMD_LINE_SWITCH, "a", "ho", _C("Save header only (metaimage only)") },
      { wxCMD_LINE_SWITCH, "n", "nii", _C("Save files as .nii (nifti/fsl only)") },
      { wxCMD_LINE_SWITCH, "b", "v16", _C("Save .v16 files (BrainVoyager only)") },
      { wxCMD_LINE_SWITCH, "r", "rescale", _C("Apply rescale slope and intercept to data") },
      { wxCMD_LINE_SWITCH, "u", "patientid", _C("Use patient id instead of patient name for output file") },
      { wxCMD_LINE_OPTION, "s", "skip", _C("Skip first 'arg' volumes") },
      { wxCMD_LINE_OPTION, "m", "match", _C("Only convert files whose series descriptions include this string") },
      { wxCMD_LINE_OPTION, "F", "fnformat", "Use this format for name of output file: \n\
format = {format element} ;\n\
some text = {filesystem-legal characters} ;\n\
format element = {some text} , {\"%\" \"PN\" | \"PI\" | \"SD\" | \"ST\" | \"SI\" | \"TD\" | \"SN\" | \"SQ\" | \"PR\" | \"RD\"} , {some text} ;\n\
or \n\
format = {\"+\" | \"-\" , \
\"PatientName\" | \
\"PatientId\" | \
\"SeriesDate\" | \
\"SeriesTime\" | \
\"StudyId\" | \
\"StudyDescription\" | \
\"SeriesNumber\" | \
\"SequenceName\" | \
\"ProtocolName\" | \
\"SeriesDescription\"" },
      { wxCMD_LINE_PARAM, NULL, NULL, _C("Input directory or file(s)"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE },

      { wxCMD_LINE_NONE }
    };
    wxCmdLineParser parser(cmdLineDesc, argc, argv);
    int parseResult = parser.Parse(false);
    switch ( parseResult )
    {
      case -1:
        // help was given, terminating
        parser.Usage();
        break;

      case 0:
        // everything is ok; proceed
        break;

      default:
        break;
    }

    showVersion = parser.Found("V");
    if (parser.Found("o", &outputDir)) {}
    if (parser.Found("f", &outputFormat)) {}
    verbose = parser.Found("v");
    quiet = parser.Found("q");
    splitSubject = parser.Found("j");
    splitDir = parser.Found("x");
    outputDimension = parser.Found("d") ? 4 : 3;
    headerOnly = parser.Found("a");
    niiOutput = parser.Found("n");
    v16Output = parser.Found("b");
    rescaleData = parser.Found("r");
    usePatientId = parser.Found("u");
    if (!parser.Found("s", &skipVolumes)) { skipVolumes = 0; }
    if (!parser.Found("m", &matchSeries)) { matchSeries = ""; }
    if (parser.Found("F", &fnFormat)) {}
    for (unsigned int i = 0; i < parser.GetParamCount(); ++i) {
      inputFiles.push_back(parser.GetParam(i));
    }
    

  // Known bail-out cases.
  // Version info requested.
  if (showVersion) {
    std::cout << "mcverter " << _C(VERSION_STRING) << std::endl;
    return EXIT_NO_PROCESSING;
  }
  // Help requested.
  if (parseResult == -1) {
    return EXIT_NO_PROCESSING;
  }
  // Some kind of argument error.
  if (parseResult > 0) {
    return EXIT_ARGUMENT_ERROR;
  }
  // No input files or directories were given.
  if (inputFiles.begin() == inputFiles.end()) {
    return EXIT_NO_INPUT_SPECIFIED;
  }


  // Selector for output format.
  int found = wxNOT_FOUND;
  int format_type = 0;
  for (; format_type < OutputFactory::GetNumberOfTypes(); ++format_type) {
    found = outputFormat.Find(wxString(OutputFactory::GetShortDescription(format_type), wxConvLocal));
    if (found != wxNOT_FOUND) {
      break;
    }
  }
  if (found == wxNOT_FOUND) {
    // Get the list of permitted output formats.
    wxString allowed_formats_string;
    allowed_formats_string.append(_C("Available formats: "));
    int type = 0;
    for (; type < (OutputFactory::GetNumberOfTypes() - 1); ++type) {
      allowed_formats_string.append(wxString(OutputFactory::GetShortDescription(type), wxConvLocal));
      allowed_formats_string.append(_T(", "));
    }
    allowed_formats_string.append(wxString(OutputFactory::GetShortDescription(type), wxConvLocal));
    std::cerr << _C("Invalid format type: ") << outputFormat.utf8_str() << std::endl;
    std::cerr << allowed_formats_string.utf8_str() << std::endl;
    return EXIT_ARGUMENT_ERROR;
  }


  // Selector for output directory.
  if (!wxDir::Exists(outputDir)) {
    if (!wxFileName::Mkdir(outputDir, 0777, wxPATH_MKDIR_FULL)) {
      std::cerr << _C("Unable to create directory: ") << outputDir << std::endl;
      return EXIT_PERMISSION_ERROR;
    }
    if (verbose && !quiet) {
      std::cout << _C("Created directory ") << outputDir << std::endl;
    }
  }


  // We know enough to create our converter and outputter.
  Converter* converter = new Converter(format_type);
  OutputterBase* outputter = converter->GetOutputter();
  outputter->SetOption("skip", static_cast<int> (skipVolumes));
  outputter->mOutputList.rootDir = outputDir;
  outputter->SetOption("split_subj", splitSubject);
  outputter->SetOption("split_dir", splitDir);
  outputter->SetOption("dim", outputDimension);
  outputter->SetOption("ho", headerOnly);
  outputter->SetOption("nii", niiOutput);
  outputter->SetOption("v16", v16Output);
  outputter->SetOption("rescale", rescaleData);


  // Handle request for using PatientId rather than PatientName.
  if (usePatientId) {
    outputter->defaultNameFields[OutputterBase::PatientName].value = false;
    outputter->defaultNameFields[OutputterBase::PatientId].value = true;
  }


  // Handle request for alternate output filename format.
  // Replace any spaces with underscore. Maybe filter for filesystem-illegal characters.
  if (fnFormat.length() > 0) {
    if (fnFormat.StartsWith("+") || fnFormat.StartsWith("-")) {
      //    std::string stdfnformat(fnFormat.mb_str());
      OutputterBase::FieldMap::iterator fm_it = outputter->defaultNameFields.begin();
      OutputterBase::FieldMap::iterator fm_it_end = outputter->defaultNameFields.end();
      for (; fm_it != fm_it_end; ++fm_it) {
        // If user is requesting alternate format, turn each element off until we
        // know better.
        fm_it->second.value = false;
        //      if (stdfnformat.find("+" + fm_it->second.name) != -1) {
        if (fnFormat.find("+" + fm_it->second.name) != std::string::npos) {
          fm_it->second.value = true;
        }
        //      if (stdfnformat.find("-" + fm_it->second.name) != -1) {
        if (fnFormat.find("-" + fm_it->second.name) != std::string::npos) {
          fm_it->second.value = false;
        }
      }
    }
    else {
      outputter->SetFlexFormat(true);
      wxStringTokenizer tokenizer(fnFormat, "%");
      wxString token, rest;
      OutputterBase::FieldMap::iterator fm_it, fm_it_end;
      while (tokenizer.HasMoreTokens()) {
        bool lookupToken = false;
        token = tokenizer.GetNextToken();
        fm_it = outputter->defaultNameFields.begin();
        fm_it_end = outputter->defaultNameFields.end();
        for (; fm_it != fm_it_end; ++fm_it) {
          if (token.StartsWith(fm_it->second.abbr, &rest)) {
            lookupToken = true;
            outputter->fnAppendComponent(fm_it->second.abbr);
            outputter->fnAppendComponent(rest);
            break;
          }
        }
        if (!lookupToken) {
          outputter->fnAppendComponent(token);
        }
      }
    }
  }


  // We've processed all options and switches,
  // now add the requested files.
  if (verbose && !quiet) {
    std::cout << _C("Adding files...") << std::endl;
  }
  std::vector<wxString>::iterator it = inputFiles.begin();
  std::vector<wxString>::iterator it_end = inputFiles.end();
  for (; it != it_end; ++it) {
    wxFileName filename(*it);
    if (filename.FileExists()) {
      converter->AddFile(filename.GetFullPath(), matchSeries);
    }
    else {
      if (filename.DirExists()) {
        wxArrayString files;
        wxDir::GetAllFiles(filename.GetFullPath(), &files);
        for (unsigned int i = 0; i < files.GetCount(); ++i) {
          converter->AddFile(files.Item(i), matchSeries);
        }
      }
      else {
        std::cerr << _C("File or directory ") << *it << _C(" not found.") << std::endl;
        return EXIT_NO_INPUT_SPECIFIED;
      }
    }
  }

  if (verbose && !quiet) {
    MessageList::iterator ml_it = converter->messages.begin();
    MessageList::iterator ml_it_end = converter->messages.end();
    for (; ml_it != ml_it_end; ++ml_it) {
      Message::iterator it = ml_it->begin();
      Message::iterator it_end = ml_it->end();
      for (; it != it_end; ++it) {
        std::cout << it->mb_str(wxConvLocal) << " ";
      }
      std::cout << std::endl;
    }
  }
  converter->messages.clear();
  converter->UpdateAllOutput();

  if (verbose && !quiet) {
    std::cout << _C("Converting...") << std::endl;
  }


  // Let the real work begin.
  converter->ConvertAll();

  if (verbose && !quiet) {
    std::cout << _C("Finished") << std::endl;
  }

  delete converter;

  return EXIT_SUCCESS;
}
