/* $Id: feature.cpp,v 1.2 2008/05/08 15:12:52 post Exp $ */

#include <iostream>
#include <fstream>
#include <sstream>
#include "feature.h"
#include "types.h"
#include "cell.h"
#include "itggrmr.h"
#include "globals.h"
using namespace std;

string_init_function_hash FeatureList::f_init_hash;
string_merge_function_hash FeatureList::f_merge_hash;

void FeatureList::load(const string& filename) {
  ifstream in(filename.data());
  if (! in.is_open()) {
    cerr << "* FATAL: can't read feature file '" << filename << "'\n";
    exit(1);
  }
  while (! in.eof()) {
    string line;
    getline(in, line);

    double prob;
    string key, token;
    stringstream ss(line);

    ss >> prob >> key;
    while (ss >> token)
      key += " " + token;

    if (key == "")
      break;

    features_[key] = prob;
  }
  in.close();

  if (f_init_hash.size() == 0) {
    f_init_hash["default"] = feature_init_default;
    f_init_hash["lex"] = feature_init_lex;

    f_merge_hash["default"] = feature_merge_default;
    f_merge_hash["lex"] = feature_merge_lex;
  }

  loaded = true;
}

void FeatureList::dump(const string& filename) const {
  ofstream out(filename.data());
  if (! out.is_open()) {
    cerr << "* FATAL: can't write feature file '" << filename << "'\n";
    exit(1);
  }
  string_double_hash::const_iterator it;
  for (it = features_.begin(); it != features_.end(); it++)
    out << it->second << " " << it->first << endl;
  out.close();
}

double operator*(const FeatureList& l1, const FeatureList& l2) {
  const string_double_hash& small = 
    (l1.features_.size() < l2.features_.size()) ? l1.features_ : l2.features_;
  const string_double_hash& big = 
    (l1.features_.size() < l2.features_.size()) ? l2.features_ : l1.features_;

  double val = 0.0;

  string_double_hash::const_iterator it, it2;
  for (it = small.begin(); it != small.end(); it++) {
    const string& key = it->first;
    if ((it2 = big.find(key)) != big.end())
      val += it->second * it2->second;
  }

  return val;
}

FeatureList operator+(const FeatureList& l1, const FeatureList& l2) {
  FeatureList l3(l1);

  string_double_hash::const_iterator it;
  for (it = l2.features_.begin(); it != l2.features_.end(); it++) {
    const string& key = it->first;
    l3[key] += it->second;
  }

  return l3;
}

FeatureList operator-(const FeatureList& l1, const FeatureList& l2) {
  FeatureList l3(l1);

  string_double_hash::const_iterator it;
  for (it = l2.features_.begin(); it != l2.features_.end(); it++) {
    const string& key = it->first;
    l3[key] -= it->second;
  }

  return l3;
}

ostream& operator<<(ostream& out, const FeatureList& fl) {
  if (fl.features_.size()) {
    out << "[ ";

    for (string_double_hash::const_iterator it = fl.features_.begin(); it != fl.features_.end(); it++) {
      out << it->first << "=" << it->second << " ";
    }
    out << "]";
  }
  return out;
}

/* discriminative functions */

// basic/default -- uses two features (lm weight and itg weight)
FeatureList feature_init_default(Cell& cell, double phr_prob, double lm_prob) { 
  FeatureList features;
  features["itg"] = phr_prob;
  features["ngram"] = lm_prob;
  return features;
}
FeatureList feature_merge_default(Cell* new_cell, Cell* left, Cell* right, Rule* rule, double lm_prob) { 
  FeatureList features;
  features["ngram"] = lm_prob + left->features["ngram"];
  features["itg"] = left->features["itg"];
  if (rule != NULL)
    features["itg"] += rule->prob;
  if (right != NULL) {
    features["ngram"] += right->features["ngram"];
    features["itg"] += right->features["itg"];
  }
  return features;
}

// features comprising phrase pairs and ngrams
FeatureList feature_init_lex(Cell& cell, double phr_prob, double lm_prob) { 
  return FeatureList();
}
FeatureList feature_merge_lex(Cell* new_cell, Cell* left, Cell* right, Rule* rule, double lm_prob) { 
  return FeatureList();
}

// entry functions -- pick the right functions to call based on config file param
// fetches the feature function id from the configuration file
// and uses its value to call the appropriate feature leaf function
FeatureList feature_init(Cell& cell, double phr_prob, double lm_prob) {
  static feature_init_function_ptr func = NULL;
  if (func == NULL) {
    string key = G_STRING("feature_func_id");
    if (FeatureList::f_init_hash.count(key))
      func = FeatureList::f_init_hash[key];
    else {
      cout << "* FATAL: no init feature function found for id '" << key << "'\n";
      exit(1);
    }
  }

  return func(cell, phr_prob, lm_prob);
}

// fetches the feature function id from the configuration file
// and uses its value to call the appropriate feature merge function
FeatureList feature_merge(Cell* new_cell, Cell* left_cell, Cell* right_cell, Rule* rule, double lm_prob) { 
  static feature_merge_function_ptr func = NULL;
  if (func == NULL) {
    string key = G_STRING("feature_func_id");
    if (FeatureList::f_merge_hash.count(key))
      func = FeatureList::f_merge_hash[key];
    else {
      cout << "* FATAL: no merge feature function found for id '" << key << "'\n";
      exit(1);
    }
  }

  return func(new_cell, left_cell, right_cell, rule, lm_prob);
}
