#include "initialize_stack.hpp"
#include <getopt.h>

using namespace std;
using namespace IRDB_SDK;
using namespace InitStack;

//
// Print usage info
//
void usage(char *p_name) {
  cerr << "Usage: " << p_name << " <variant_id>\n";
  cerr << "\t[--functions <file> | -f <file>]       Read in the functions to "
          "auto-initialize      "
       << endl;
  cerr << "\t[--initvalue <value> | -i <value>]     Specify stack "
          "initialization value (default=0)"
       << endl;
  cerr << "\t[--verbose | -v]                       Verbose mode               "
          "                   "
       << endl;
  cerr << "\t[--help,--usage,-?,-h]                 Display this message       "
          "                   "
       << endl;
}

//
// The entry point for a stand-alone executable transform.
// Note:  Thanos-enabled transforms are easier to write, faster to execute, and
// generally preferred. Stand-alone transforms may be useful if the transform
// has issues with memory leaks and/or memory errors. Memory issues in a stand
// alone transform cannot affect correctness of other transforms.
//
int main(int argc, char **argv) {
  //
  // Sanity check that the command line has at least a variant ID, otherwise we
  // won't know what variant to operate on.
  //
  if (argc < 2) {
    usage(argv[0]);
    exit(1);
  }

  // constant parameters read from argv
  const auto program_name = string(argv[0]);
  const auto variantID = atoi(argv[1]);

  // initial values of parameters to parse
  auto verbose = false;
  auto funcs_filename = string();
  auto init_value = 0;

  // declare some options for the transform
  const char *short_opts = "f:i:v?h";
  struct option long_options[] = {{"functions", required_argument, 0, 'f'},
                                  {"initvalue", required_argument, 0, 'i'},
                                  {"verbose", no_argument, 0, 'v'},
                                  {"help", no_argument, 0, 'h'},
                                  {"usage", no_argument, 0, '?'},
                                  {0, 0, 0, 0}};

  // parse the options in a standard getopts_long loop
  while (true) {
    int c = getopt_long(argc, argv, short_opts, long_options, nullptr);
    if (c == -1)
      break;
    switch (c) {
    case 'f':
      funcs_filename = optarg;
      cout << "Reading file with function specifiers: " << funcs_filename
           << endl;
      break;
    case 'i':
      init_value = strtoll(optarg, NULL, 0);
      cout << "     Stack initialization value: " << hex << init_value << endl;
      break;
    case 'v':
      verbose = true;
      break;
    case '?':
    case 'h':
      usage(argv[0]);
      exit(1);
      break;
    default:
      break;
    }
  }

  // stand alone transforms must setup the interface to the sql server
  auto pqxx_interface = pqxxDB_t::factory();
  BaseObj_t::setInterface(pqxx_interface.get());

  // stand alone transforms must create and read a variant ID from the database
  auto pidp = VariantID_t::factory(variantID);
  assert(pidp->isRegistered() == true);

  // stand alone transforms must create and read the main file's IR from the
  // datatbase
  auto this_file = pidp->getMainFile();
  auto url = this_file->getURL();

  // declare for later so we can return the right value
  auto success = false;

  // now try to load the IR and execute a transform
  try {
    // Create and download the file's IR.
    // Note:  this is achieved differently  with thanos-enabled plugins
    auto firp = FileIR_t::factory(pidp.get(), this_file);

    // sanity
    assert(firp && pidp);

    // log
    cout << "Transforming " << this_file->getURL() << endl;

    // create and invoke the transform
    InitStack_t is(firp.get(), funcs_filename, init_value, verbose);
    success = is.execute();

    // conditionally write the IR back to the database on success
    if (success) {
      cout << "Writing changes for " << url << endl;

      // Stand alone trnasforms must manually write the IR back to the IRDB and
      // commit the transactions
      firp->writeToDB();

      // and commit the the transaction to postgres
      pqxx_interface->commit();
    } else {
      cout << "Skipping write back on failure. " << url << endl;
    }
  } catch (const DatabaseError_t &db_error) {
    // log any databse errors that might come up in the transform process
    cerr << program_name << ": Unexpected database error: " << db_error
         << "file url: " << url << endl;
  } catch (...) {
    // log any other errors
    cerr << program_name << ": Unexpected error file url: " << url << endl;
  }

  //
  // return success code to driver (as a shell-style return value).  0=success,
  // 1=warnings, 2=errors
  //
  return success ? 0 : 2;
}