#include <config.h>
#include <model/SymTab.h>
#include <graph/MixtureNode.h>

#include <cfloat>
#include <string>
#include <stdexcept>
#include <utility>
#include <set>

using std::vector;
using std::map;
using std::pair;
using std::string;
using std::runtime_error;
using std::logic_error;
using std::set;

SymTab::SymTab()
{
}

void SymTab::addVariable(string const &name, Index const &dim)
{
  if (_varTable.find(name) != _varTable.end()) {
    string msg("Name ");
    msg.append(name);
    msg.append(" already in use in symbol table");
    throw runtime_error(msg);
  }

  NodeArray *array = new NodeArray(name, dim);
  _varTable[name] = array;
}

NodeArray* SymTab::getVariable(string const &name) const
{
  map<string, NodeArray*>::const_iterator p =  _varTable.find(name);
  
  if (p == _varTable.end()) {
    return 0;
  }
  else {
    return p->second;
  }
}

void SymTab::getNodes(std::vector<Node*> &nodes)
{
  map<string, NodeArray*>::const_iterator p(_varTable.begin());

  for (; p != _varTable.end(); ++p) {
    Graph const &graph = p->second->graph();
    graph.getNodes(nodes);
  }
}


void SymTab::writeData(std::map<std::string, SArray> const &data_table,
		       bool observed)
{
  for(map<string, SArray>::const_iterator p(data_table.begin()); 
      p != data_table.end(); ++p) {
    NodeArray *array = getVariable(p->first);
    if (array) {
      if (array->range().dim(false) != p->second.dim(false)) {
	string msg("Dimension mismatch in values supplied for ");
	msg.append(p->first);
	throw runtime_error(msg);
      }
      array->setValue(p->second, observed);
    }
  }
}

void SymTab::readData(map<string, SArray> &data_table, bool observed) const
{
  for (std::map<string, NodeArray*>::const_iterator p(_varTable.begin());
       p != _varTable.end(); ++p) {
    string const &name = p->first;
    if (data_table.find(name) != data_table.end()) {
      data_table.erase(name);
    }
    NodeArray const *array = p->second;
    SArray sarray(array->range().dim(false));
    array->getValue(sarray, observed);
    /* If array consists completely of missing values, we don't bother
       to write it out */
    bool missing = true;
    for (unsigned long i = 0; i < sarray.length(); ++i) {
      if (sarray.value()[i] != JAGS_NA) {
	missing = false;
	break;
      }
    }
    if (!missing) {
      data_table.insert(pair<string,SArray>(name, sarray));
    }
  }
}

void SymTab::readData(std::map<std::string, SArray> &data_table) const
{
  for (std::map<string, NodeArray*>::const_iterator p(_varTable.begin());
       p != _varTable.end(); ++p) {
    string const &name = p->first;
    if (data_table.find(name) != data_table.end()) {
      data_table.erase(name);
    }
    NodeArray const *array = p->second;
    SArray sarray(array->range().dim(false));
    array->getAllValues(sarray);
    // If array consists completely of missing sarrays, we don't
    // bother to write it out
    bool missing = true;
    for (unsigned long i = 0; i < sarray.length(); i++) {
      if (sarray.value()[i] != JAGS_NA) {
	missing = false;
	break;
      }
    }
    if (!missing) {
      data_table.insert(pair<string,SArray>(name, sarray));
    }
  }
}

unsigned int SymTab::size() const
{
  return _varTable.size();
}

void SymTab::clear()
{
  _varTable.clear();
}

static vector<string> cutBUGSSubsetName(string const &name)
{
  /* 
     Takes the BUGS language name of an array subset, e.g. "foo[a,b,c]",
     and cuts it up into substrings ("foo" "[" "a" "," "b" "," "c" "]")
  */
  vector<string> cut;
  char const *c = name.c_str();
  unsigned int i = 0;
  for (unsigned int j = 1; j < name.size(); ++j) {
    if (c[j] == '[' || c[j] == ']' || c[j] == ',') {
      cut.push_back(name.substr(i,j-i));
      cut.push_back(name.substr(j,1));
      i = j+1;
    }
  }
  return cut;
}


static string makeMixtureName(MixtureNode const *mnode,
			      SymTab const &symtab)
{
  /* 
     Making a name for a mixture node is a complex business. We rely
     on the fact that, currently, mixture nodes can only be created by
     nested indexing. For example "foo[1,X,4]" where X can take values
     1,2,3, will create a mixture node with X as an index and
     parameters "foo[1,1,4]", "foo[1,2,4]", "foo[1,3,4]".

     The BUGS language name of a mixture node can be reconstructed
     by finding the parts of the parameter names that match, e.g.
     "foo[1," ... ",4]"
     and filling in the gaps with the names of the indices, e.g.
     "foo[1," "X" ",4]"
  */

  vector<Node*> const &index = mnode->index();

  //Create a set of parents, excluding index nodes
  //FIXME: we are assuming that none of the indices are also parameters
  set<Node*> parents = mnode->parents();
  for (unsigned int i = 0; i < index.size(); ++i) {
    parents.erase(index[i]);
  }
  
  if (parents.empty())
    return("<unknown mixture node>");

  //Create a vector of parameter names
  vector<string> param_names;
  for (set<Node*>::const_iterator i = parents.begin(); i != parents.end(); ++i)
    {
      param_names.push_back((*i)->name(symtab));
    }

  //Cut up the name of the first parameter
  string const &name0 = param_names[0];
  vector<string> cut_name0 = cutBUGSSubsetName(name0);

  //Now compare the parameter names with the first one, and mark the
  //parts that don't match
  unsigned int nparts = cut_name0.size();
  vector<bool> variable_part(nparts, false);

  for (unsigned int j = 1; j < param_names.size(); ++j) {
    vector<string> cut_name = cutBUGSSubsetName(param_names[j]);
    if (cut_name.size() != nparts) {
      /* 
	 Something went wrong here, but it's too late to throw an exception
	 because we normally only want to know a node name after catching
	 one.
      */
      return "";
    }
    for (unsigned int k = 0; k < nparts; ++k) {
      if (cut_name[k] != cut_name0[k]) {
	variable_part[k] = true;
      }
    }
  }
    
  //Check that number of variables parts is the same as the number
  //of indices of the mixture node
  unsigned int nvar = 0;
  for (unsigned int k = 0; k < nparts; ++k) {
    if (variable_part[k])
      ++nvar;
  }
  if (nvar != index.size()) {
    /* Something went wrong - see above */
    return "";
  }
   
  //Now we are ready
  string mnode_name;
  unsigned int l = 0;
  for (unsigned int k = 0; k < nparts; ++k) {
    if (variable_part[k]) {
      mnode_name.append(index[l++]->name(symtab));
    }
    else {
      mnode_name.append(cut_name0[k]);
    }
  }

  return mnode_name;
}


string SymTab::getName(Node const *node) const
{
  //Special rules for mixture nodes
  if (isMixture(node)) {
    return makeMixtureName(asMixture(node), *this);
  }

  map<string, NodeArray*>::const_iterator p;
  for (p = _varTable.begin(); p != _varTable.end(); ++p) {
    NodeArray *array = p->second;
    if (array->graph().contains(node)) {
      if (array->getRange(node) == array->range()) {
         return p->first;
      }
      else {
         return p->first + print(array->getRange(node));
      }
    }
  }

  return("");
}


syntax highlighted by Code2HTML, v. 0.9.1