#include "../aoluarunner/options.h"

#include "../structures/types.h"

#include "../util/logger.h"
#include "../util/stopwatch.h"
#include "../util/numberlist.h"

#include "../aoluarunner/runner.h"

#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string/case_conv.hpp>

#include <version.h>

#include <iostream>
#include <string>
#include <mutex>

#define RETURN_SUCCESS 0
#define RETURN_CMDLINE_ERROR 10
#define RETURN_STRATEGY_PARSE_ERROR 20
#define RETURN_UNHANDLED_EXCEPTION 30

void checkRelease() {
#ifndef NDEBUG
  Logger::Warn << "This version of the AOFlagger has been compiled as DEBUG "
                  "version! (NDEBUG was not defined)\n"
               << "For better performance, recompile it as a RELEASE.\n\n";
#endif
}
void generalInfo() {
  Logger::Info << "AOFlagger " << AOFLAGGER_VERSION_STR << " ("
               << AOFLAGGER_VERSION_DATE_STR
               << ") command line application\n"
                  "Author: André Offringa (offringa@gmail.com)\n\n";
}

int main(int argc, char** argv) {
  if (argc == 1) {
    generalInfo();
    Logger::Error
        << "This program will execute a Lua flagging script that can be "
           "created with the RFI gui\n"
           "and executes it on one or several observations.\n\n"
           "Usage: "
        << argv[0]
        << " [options] <obs1> [<obs2> [..]]\n"
           "  -v will produce verbose output\n"
           "  -j overrides the number of threads specified in the strategy\n"
           "     (default: one thread for each CPU core)\n"
           "  -strategy <strategy>\n"
           "     specifies a customized strategy\n"
           "  -direct-read\n"
           "     Will perform the slowest IO but will always work.\n"
           "  -indirect-read\n"
           "     Will reorder the measurement set before starting, which is "
           "normally faster but requires\n"
           "     free disk space to reorder the data to.\n"
           "  -memory-read\n"
           "     Will read the entire measurement set in memory. This is the "
           "fastest, but requires much\n"
           "     memory.\n"
           "  -auto-read-mode\n"
           "     Will select either memory or direct mode based on available "
           "memory (default).\n"
           "  -skip-flagged\n"
           "     Will skip an ms if it has already been processed by AOFlagger "
           "according to its HISTORY\n"
           "     table.\n"
           "  -uvw\n"
           "     Reads uvw values (some exotic strategies require these)\n"
           "  -column <name>\n"
           "     Specify column to flag\n"
           "  -interval <start> <end>\n"
           "     Only process the specified timesteps. Indices are zero "
           "indexed, and\n"
           "     the end is exclusive, such that -interval 10 20 selects 10, "
           "11, ... 19.\n"
           "  -chunk-size <ntimes>\n"
           "     This will split the set into intervals with the given maximum "
           "size, and flag each\n"
           "     interval independently. This lowers the amount of memory "
           "required. The flagger\n"
           "     has slightly less information per interval, but for a size of "
           "1000 timesteps there is\n"
           "     no noticable difference. With a size of 100 the difference is "
           "mostly not problematic\n"
           "     either. In some cases, splitting the data increases accuracy, "
           "in particular when the\n"
           "     statistics in the set change significantly over time (e.g. "
           "rising Galaxy).\n"
           "  -bands <list>\n"
           "     Comma separated list of (zero-indexed) band ids to process\n"
           "  -fields <list>\n"
           "     Comma separated list of (zero-indexed) field ids to process\n"
           "  -baselines < all / cross / auto >\n"
           "     Run the strategy on the given baseline types. The default is "
           "to run the strategy on\n"
           "     all cross-correlation baselines. This parameter has no effect "
           "for single-dish observations.\n"
           "  -combine-spws\n"
           "     Join all SPWs together in frequency direction before "
           "flagging\n"
           "  -preamble <statement>\n"
           "     Runs the specified Lua statement before starting to flag. "
           "This is typically used to\n"
           "     define a variable, e.g. -preamble \"bandpassfile = "
           "mybandpass.txt\".\n"
           "\n"
           "This tool supports the Casacore measurement set, the SDFITS and "
           "Filterbank formats and some more. See\n"
           "the documentation for support of other file types.\n";

    checkRelease();

    return RETURN_CMDLINE_ERROR;
  }

  Options options;

  size_t parameterIndex = 1;
  while (parameterIndex < (size_t)argc && argv[parameterIndex][0] == '-') {
    std::string flag(argv[parameterIndex] + 1);

    // If "--" was used, strip another dash
    if (!flag.empty() && flag[0] == '-') flag = flag.substr(1);

    if (flag == "j" && parameterIndex < (size_t)(argc - 1)) {
      ++parameterIndex;
      options.threadCount = atoi(argv[parameterIndex]);
    } else if (flag == "v") {
      options.logVerbosity = Logger::VerboseVerbosity;
    } else if (flag == "version") {
      Logger::Info << "AOFlagger " << AOFLAGGER_VERSION_STR << " ("
                   << AOFLAGGER_VERSION_DATE_STR << ")\n";
      return 0;
    } else if (flag == "direct-read") {
      options.readMode = DirectReadMode;
    } else if (flag == "indirect-read") {
      options.readMode = IndirectReadMode;
    } else if (flag == "memory-read") {
      options.readMode = MemoryReadMode;
    } else if (flag == "auto-read-mode") {
      options.readMode = AutoReadMode;
    } else if (flag == "strategy") {
      parameterIndex++;
      options.strategyFilename = argv[parameterIndex];
    } else if (flag == "skip-flagged") {
      options.skipFlagged = true;
    } else if (flag == "uvw") {
      options.readUVW = true;
    } else if (flag == "column") {
      parameterIndex++;
      options.dataColumn = std::string(argv[parameterIndex]);
    } else if (flag == "bands") {
      ++parameterIndex;
      NumberList::ParseIntList(argv[parameterIndex], options.bands);
    } else if (flag == "fields") {
      ++parameterIndex;
      NumberList::ParseIntList(argv[parameterIndex], options.fields);
    } else if (flag == "combine-spws") {
      options.combineSPWs = true;
    } else if (flag == "preamble") {
      ++parameterIndex;
      options.preamble.emplace_back(argv[parameterIndex]);
    } else if (flag == "interval") {
      options.startTimestep = atoi(argv[parameterIndex + 1]);
      options.endTimestep = atoi(argv[parameterIndex + 2]);
      parameterIndex += 2;
    } else if (flag == "chunk-size" || flag == "max-interval-size") {
      ++parameterIndex;
      options.chunkSize = atoi(argv[parameterIndex]);
    } else if (flag == "baselines") {
      ++parameterIndex;
      std::string bTypes = argv[parameterIndex];
      if (bTypes == "all")
        options.baselineSelection = BaselineSelection::All;
      else if (bTypes == "cross")
        options.baselineSelection = BaselineSelection::CrossCorrelations;
      else if (bTypes == "auto")
        options.baselineSelection = BaselineSelection::AutoCorrelations;
      else {
        Logger::Error << "Incorrect usage; baselines parameter should be set "
                         "to 'all', 'cross' or 'auto'.\n";
        return RETURN_CMDLINE_ERROR;
      }
    } else {
      Logger::Error << "Incorrect usage; parameter \"" << argv[parameterIndex]
                    << "\" not understood.\n";
      return RETURN_CMDLINE_ERROR;
    }
    ++parameterIndex;
  }

  try {
    Logger::SetVerbosity(
        options.logVerbosity.value_or(Logger::NormalVerbosity));
    generalInfo();

    checkRelease();

    Stopwatch watch(true);

    std::stringstream commandLineStr;
    commandLineStr << argv[0];
    for (int i = 1; i < argc; ++i) {
      commandLineStr << " \"" << argv[i] << '\"';
    }
    options.commandLine = commandLineStr.str();

    for (int i = parameterIndex; i < argc; ++i)
      options.filenames.emplace_back(argv[i]);

    boost::filesystem::path strategyPath = options.strategyFilename;
    if (boost::to_lower_copy(strategyPath.extension().string()) == ".rfis") {
      Logger::Error << "An old .rfis file was specified. AOFlagger version 3 "
                       "supports only Lua scripts and can\n"
                       "not run the old .rfis-style files. Example Lua "
                       "strategies can be found in the aoflagger\n"
                       "source directory under data/strategies.\n";
      return RETURN_CMDLINE_ERROR;
    }

    Runner runner(options);
    runner.Run();

    Logger::Debug << "Time: " << watch.ToString() << "\n";

    return RETURN_SUCCESS;
  } catch (std::exception& exception) {
    std::cerr
        << "An unhandled exception occured: " << exception.what() << '\n'
        << "If you think this is a bug, please contact offringa@gmail.com\n";
    return RETURN_UNHANDLED_EXCEPTION;
  }
}
