/* globals.cpp
 *
 * $Id: globals.cpp,v 1.39 2008/05/20 15:21:24 gildea Exp $
 */

#include "globals.h"
#include <fstream>
#include <sstream>

using namespace std;

Globals* Globals::_instance = 0;

/** Globals::Instance
 *
 * Returns the single instance.
 */
Globals* Globals::Instance() {
  return _instance;
}

/** Globals::Globals()
 *
 * Constructor.  Reads in the configuration file, overrides parameter
 * values given on the command line, and ensures that the minimal set
 * of parameters have been specified.
 */
Globals::Globals(int argc, char **argv) {

  /* 0. THE COMPLETE LIST OF PERMISSIBLE PARAMETERS */

  list["allow_deletions"] = 1;
  list["allow_insertions"] = 1;
  list["beamwidth"] = 1;
  list["bin_size"] = 1;
  list["cloning"] = 1;
  list["configfile"] = 1;
  list["countfilelist"] = 0;
  list["debug_dump_charts"] = 1;
  list["debuglevel"] = 1;
  list["decoder"] = 1;
  list["defaultprob"] = 1;
  list["delpenalty"] = 1;
  list["delpenalty2"] = 1;
  list["drop_lowcount_phrases"] = 1;
  list["feature_file"] = 1;
  list["feature_func_id"] = 1;
  list["grammarfile"] = 1;
  list["ins_credit"] = 1;
  list["iterations"] = 1;
  list["k"] = 1;
  list["l1corpus"] = 1;
  list["l1vocabfile"] = 1;
  list["l2corpus"] = 1;
  list["l1period"] = 1;
  list["l2vocabfile"] = 1;
  list["length_bonus"] = 1;
  list["length_bonus2"] = 1;
  list["lm_weight"] = 1;
  list["lm_weight2"] = 1;
  list["logratiobeam"] = 1;
  list["maxedges"] = 1;
  list["maxins"] = 1;
  list["maxkbufsize"] = 1;
  list["maxphrlen"] = 1;
  list["maxphrlen_smooth"] = 1;
  list["maxsentlen"] = 1;
  list["maxtrs"] = 1;
  list["ngrammodel"] = 1;
  list["pins"] = 1;
  list["probfile"] = 1;
  list["prune_agenda"] = 1;
  list["prune_method"] = 1;
  list["runid"] = 1;
  list["rundir"] = 1;
  list["runmode"] = 1;
  list["sentence_from"] = 1;
  list["sentence_to"] = 1;
  list["suppress_itgscore"] = 1;
  list["threshold"] = 1;
  list["ngram_n"] = 1; // sets N for ngrams
  list["use_agenda"] = 1;
  list["variational_bayes"] = 0;
  list["variational_alpha"] = 0;
  list["two_pass"] = 0;
  list["lazy_kbest"] = 0; 
  list["cyk_first_pass"] = 0; 
  list["verbosity"] = 1;
  list["use_hooks"] = 0; 
  list["cyk_hooks"] = 0;
  list["epass"] = 0;
  list["mixture_DP"] = 0; 
  list["distributed_EM"] = 0;

  /* 0. SETUP PARAMETER REQUIREMENTS FOR EACH OF THE MODES */
  /* These values must be specified at runtime. */

  vector<string> values;
  values.push_back("runid");
  values.push_back("rundir");
  values.push_back("grammarfile");
  values.push_back("probfile");
  values.push_back("l2corpus");
//   values.push_back("l2vocabfile");
  deps["d"] = values;

  // VITERBI and TRAINING
  values.clear();
  values.push_back("runid");
  values.push_back("rundir");
  values.push_back("grammarfile");
  values.push_back("probfile");
  values.push_back("l1corpus");
  values.push_back("l2corpus");
  deps["v"] = values;
  deps["t"] = values;

  /* 0. SET DEFAULT VALUES FOR CERTAIN PARAMETERS */
  /* These parameters have default values and don't need to be
     specified at run-time */

  // first create the object.
  // second, fill in default values
  // third, read in file
  params = new ConfigFile();

  /* First, load in all of the default values.
   * These can be overwritten from the configuration file
   * or from the command line.
   */

  // GENERAL PARAMETERS
  params->add<double>("defaultprob",         1e-10);
  params->add<int>("debug_dump_charts",      0);
  params->add<int>("debuglevel",             0);
  params->add<string>("feature_file",        "features"); // location of feature file
  params->add<string>("feature_func_id",     "default"); // feature method
  params->add<double>("threshold",           1e-10);
  params->add<int>("verbosity",              0);
  params->add<int>("maxphrlen",              7);
  params->add<int>("maxsentlen",             25);
  params->add<int>("iterations",             1);
  params->add<int>("sentence_from",          0);
  params->add<int>("sentence_to",            0);

  // DECODING
  params->add<bool>("allow_deletions",       true);
  params->add<bool>("allow_insertions",      true);
  params->add<int>("beamwidth",              100);
  params->add<int>("bin_size",               10);
  params->add<string>("decoder",             "cyk");
  params->add<WORDTYPE>("l1period",          0);  
  params->add<double>("logratiobeam",        5.0); // ~ log(100)
  params->add<double>("length_bonus",        0.0);
  params->add<double>("length_bonus2",       0.0);
  params->add<double>("lm_weight",           0.5);
  params->add<double>("lm_weight2",          0.5);
  params->add<double>("inscredit",           1.0);
  params->add<double>("delpenalty",          2.0);
  params->add<double>("delpenalty2",         2.0);
  params->add<bool>("prune_agenda",          false);
  params->add<bool>("prune_method",          "ratio");
  params->add<int>("maxins",                 5);
  params->add<int>("maxtrs",                 5);
  params->add<int>("maxedges",               2);
  params->add<int>("maxkbufsize",            250);
  params->add<bool>("suppress_itgscore",     false);
  params->add<int>("ngram_n",                0);
  params->add<bool>("use_agenda",            false);
  params->add<bool>("two_pass",              false);
  params->add<bool>("lazy_kbest",            false);
  params->add<bool>("cyk_first_pass",        false);
  params->add<bool>("use_hooks",             false);
  params->add<bool>("cyk_hooks",             false);
  params->add<bool>("epass",                 false);

  // PARSING
  params->add<bool>("cloning",               false);
  params->add<double>("pins",                0.5);  

  // TRAINING
  params->add<bool>("drop_lowcount_phrases", true);
  params->add<bool>("variational_bayes",     false);
  params->add<double>("variational_alpha",    0.0);
  params->add<int>("maxphrlen_smooth",       1);

  params->add<bool>("mixture_DP", false);
  params->add<bool>("distributed_EM", false);

  // HUB count combination
  params->add<string>("countfilelist",             "/dev/null");

  /* 1. PROCESS COMMAND-LINE ARGUMENTS */
  // first look for -configfile option, and load that
  // afterward, load the remaining parameters

  argv++;  // first value is program name

  char **ptr = argv;

  // first, pass over the args to see if there is a config file specified
  while (*argv) {
    string key(*argv);

    if (key[0] != '-') {
      throw load_error("keys must be preceded by a hyphen");
    }

    key = key.substr(1);
    if (key == "configfile")
      params->add<string>("configfile", string(*(argv+1)));

    if (! *(argv+1)) {
      string err = (*argv[0] == '-')
        ? ("you supplied a key (" + string(*argv) + ") without a value")
        : ("you supplied a value (" + string(*argv) + ") without a key");
      throw load_error(err);
    }
    argv += 2;
  }
  argv = ptr;

  /* LOAD CONFIG FILE (if found) */
  if (params->keyExists("configfile")) {
    string file = params->read<string>("configfile");
//     cout << "Reading configuration file '" << file << "'...";
    ifstream in(file.data());
    if (! in.is_open())
      throw load_error("couldn't read configuration file '" + params->read<string>("configfile") + "'");
    in >> (*params);
    in.close();
  } 
  /* IF NOT FOUND, LOOK FOR DEFAULT CONFIG FILE 'config' IN CURRENT DIR */
  else {
    ifstream in("config");
    if (! in.is_open())
      throw load_error("couldn't find default config file 'config'");
    in >> (*params);
    in.close();
  }

  /* OVERRIDE CONFIG FILE VALUES WITH COMMAND LINE */

  while (*argv) {
    string key(*argv);

    // make sure params start with a dash
    if (key[0] != '-')
      throw load_error("first character of key '" + key + "' must be a dash");

    key = key.substr(1);

    if (key == "configfile") { // skip this one
      argv += 2;
      continue;
    }

    // make sure key exists in list of possible parameters
    vector<string> matches = find_keys(key);
    if (matches.size() == 0) 
      throw load_error("no key matches pattern '" + key + "'");
    else if (matches.size() >= 2) {
      string err = "pattern '" + key + "' matches multiple keys (";
      for (unsigned i = 0; i < matches.size(); i++)
        err += matches[i] + ",";
      err[err.size()-1] = ')';
      throw load_error(err);
    }

    key = matches[0];

    // make sure key has a value
    argv++;
    if (! *argv)
      throw load_error("key " + key + " has no value");

    string value(*argv);

    // TODO: what type to use!?  hoping that storing as a string, can
    // be read correctly regardless
    params->add<string>(key, value);
    if (get<int>("verbosity") >= 1) 
      cout << "PARAMS: changing " << key << " = " << value << endl;

    argv++;
  }

  /* MAKE SURE WE HAVE A QUORUM */
  vector<string> reqs;
  try {
    reqs = deps[params->read<string>("runmode")];
    for (unsigned i = 0; i < reqs.size(); i++)
      params->read<string>(reqs[i]);
  } catch (ConfigFile::key_not_found knf) {
    if (knf.key == "runmode")
      throw load_error("you must specify a runmode");

    string str = "runmode '" + params->read<string>("runmode") 
      + "' requires values for keys ";
    for (unsigned i = 0; i < reqs.size(); i++)
      str += reqs[i] + ",";
    str[str.size()-1] = ' ';

    throw load_error(str + " (you're missing '" + knf.key + "')");
  }
}

void Globals::Load(int argc, char **argv) {
  if (_instance == 0)
    _instance = new Globals(argc, argv);
}

/* Globals::find_key()
 *
 * Takes a string and returns a list of keys containing that portion.
 * This allows command-line key values to be any substring of the keys
 * that uniquely identifies them.
 */
vector<string> Globals::find_keys(string part) {
  map<string,int>::iterator iter;
  vector<string> v;
  for (iter = list.begin(); iter != list.end(); iter++) {
    string key = iter->first;
    if (key == part) { // exact match
      v.clear();
      v.push_back(key);
      break;
    }
    if (key.find(part) != string::npos)
      v.push_back(key);
  }

  return v;
}

ostream& operator<<( std::ostream& os, const Globals& g )
{
  os << *g.params;
  return os;
}

string itos(int i)
{
  stringstream s;
  s << i;
  return s.str();
}

string mytime(const time_t t)
{
  string s;

  time_t u = t;

  if (u / 86400)
    s += itos(u %= 86400) + "d ";

  s += itos(u / 3600) + "h ";   u %= 3600;
  s += itos(u / 60) + "m ";     u %= 60;
  s += itos(u) + "s";

  return s;
}

ostream& operator<<(ostream& out, const WORDTYPE* phrase) {
  if (*phrase) {
    out << *phrase;
    while (*++phrase)
      out << " " << *phrase;
  }
  return out;
}

/* these two functions transliterate between vocabulary IDs and words */
string transliterate(WORDTYPE* phr, Vocab* vocab) {
  if (phr == NULL)
    return "<NULL>";

  string phrase = "";
  while (*phr) {
    string word;
    if (vocab)
      word = (*vocab)[*phr];
    else
      word = itos(*phr);
    phrase += (phrase.length() ? " " : "") + word;
    phr++;
  }
  return phrase;
}


vector<string> split(const string& str, const string& delim) {
  vector<string> tokens;

  string::size_type pos = 0;
  int lastpos = 1 - delim.length();
  while (pos != string::npos) {
    pos = str.find(delim,lastpos > 0 ? lastpos : 0);
    int l = lastpos + delim.length() - 1;
    string token = str.substr(l, pos - l);
    tokens.push_back(token);
    lastpos = pos + 1;
  }

  return tokens;
}

void print_header(const string& str, const char symbol) {
  cout << "==";
  if (str.length()) cout << " " << str << " ";
  if (str.length() < 76)
    for (unsigned i = 0; i < 76 - str.length(); i++)
      printf("%c", symbol);
  printf("\n");
}

// Sometimes, we want to skip comment lines.
// A comment line starts with \b*#.
bool is_comment_or_empty_line(string& line)
{
  for (string::iterator it = line.begin(); it != line.end(); ++it) {
    if (*it == '\t') {
      continue;
    }
    if (*it == ' ') {
      continue;
    } 
    // we have skipped all the spaces, 
    // we can tell if it is a comment line from the next character.
    if (*it != '#') {
      return false;
    } else {
      return true; // comment
    }
  }
  return true; // empty
}

/* convenience functions */
string G_STRING(const string& key) { return Globals::Get<string>(key); }
int    G_INT(const string& key)    { return Globals::Get<int>(key); }
bool   G_BOOL(const string& key)   { return Globals::Get<bool>(key); }
double G_DOUBLE(const string& key) { return Globals::Get<double>(key); }
