#include <config.h>
#include <Console.h>
#include <compiler/Compiler.h>
#include <compiler/parser_extra.h>
#include <compiler/ParseTree.h>
#include <model/TraceMonitor.h>
#include <graph/NodeError.h>
#include <sampler/SamplerFactory.h>
#include <sampler/ConjugateFactory.h>
#include <sampler/FiniteFactory.h>
#include <sampler/SliceFactory.h>
#include <sampler/DSumFactory.h>
#include <graph/Node.h>
#include <sarray/Range.h>
#include <sarray/SArray.h>

#include <map>
#include <list>
#include <stdexcept>
#include <fstream>
#include <vector>

#include "Rmath.h"

using std::ostream;
using std::ofstream;
using std::endl;
using std::flush;
using std::vector;
using std::string;
using std::map;
using std::list;

Console::Console(ostream &out, ostream &err)
  : _out(out), _err(err), _functab(), _disttab()
{
  _pvariables = 0;
  _prelations = 0;
  _pdata = 0;
  _model = 0;
}

Console::~Console()
{
  /* FIXME: When do we delete _pvariables, _prelations, _pdata? */
  delete _model;
}

bool Console::checkModel(string const &filename)
{
  FILE *file = fopen(filename.c_str(), "r");
  if (file) {
    if (_model) {
      _out << "Replacing existing model" << endl;
      clearModel();
    }
    _model = new BUGSModel;
    _out << "Parsing model file " << filename << endl;
    int lineno =  parse_bugs(file, &_pvariables, &_pdata, &_prelations);
    if (lineno != 0) {
      _err << endl << "Parse error on line " << lineno << endl;
      clearModel();
      return false;
    }
    fclose(file);
    return true;
    /* FIXME: we should free the memory associated wth
       _pvariables, _prelations, _pdata after compiling
       the model
    */
  }
  else {
    _err << "Unable to open file " << filename << endl;
    return false;
  }
}

void Console::compile(map<string, SArray> &data_table,
		      unsigned int nchain)
{
  if (nchain == 0) {
    _err << "You must have at least one chain" << endl;
  }
  if (nchain > 1) {
    _err << "Multiple chains not currently supported. Sorry" << endl;
  }
  if (_prelations == 0) {
    _err << "No relations defined yet" << endl;
    return;
  }

  if (_pdata) {
    BUGSModel datagen_model;
    Compiler compiler(datagen_model.graph(), datagen_model.symtab(), 
		      _functab, _disttab, data_table);
    _out << "Compiling data graph" << endl;
    try {
      if (_pvariables) {
	_out << "   Declaring variables" << endl;
	compiler.declareVariables(*_pvariables);
      }
      _out << "   Resolving undeclared variables" << endl;
      compiler.undeclaredVariables(_pdata);
      _out << "   Allocating nodes" << endl;
      compiler.writeRelations(_pdata);
      _out << "   Checking graph" << endl;
      datagen_model.checkGraph();
      _out << "   Initializing" << endl;
      datagen_model.initialize();
      _out << "   Reading data back into data table" << endl;
      datagen_model.symtab().readData(data_table);
    }
    catch (NodeError except) {
      _err << "Error in node " << datagen_model.symtab().getName(except.node) << endl;
      _err << except.what() << endl;
      return;
    }
    catch (std::runtime_error except) {
      _err << "RUNTIME ERROR" << endl;
      _err << except.what() << endl;
      return;
    }
    catch (std::logic_error except) {
      _err << "LOGIC ERROR" << endl;
      _err << except.what() << endl;
      _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
      return;
    }
  }

  Compiler compiler(_model->graph(), _model->symtab(), _functab, _disttab, data_table);

  _out << "Compiling model graph" << endl;
  try {
    if (_pvariables) {
      _out << "   Declaring variables" << endl;
      compiler.declareVariables(*_pvariables);
    }
    _out << "   Resolving undeclared variables" << endl;
    compiler.undeclaredVariables(_prelations);
    _out << "   Allocating nodes" << endl;
    compiler.writeRelations(_prelations);
    _out << "   Checking graph" << endl;
    _model->checkGraph();
    _out << "   Graph Size: " << _model->graph().size() << endl;
  }
  catch (NodeError except) {
    _err << "Error in node " << _model->symtab().getName(except.node) << endl;
    _err << except.what() << endl;
    clearModel();
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR" << endl;
    _err << except.what() << endl;
    clearModel();
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    clearModel();
  }
}

bool Console::initialize()
{
  if (_model == 0) {
    _err << "Can't initialize. No model!" << endl;
    return false;
  }
  if (_model->graph().size() == 0) {
    _err << "Can't initialize. No nodes in graph (Have you compiled the model?)" 	 << endl;
    return false;
  }
  try {
    _model->initialize();

    vector<SamplerFactory const*> samplers;
    DSumFactory dfac;
    samplers.push_back(&dfac);
    ConjugateFactory cfac;
    samplers.push_back(&cfac);
    FiniteFactory ffac;
    samplers.push_back(&ffac);
    SliceFactory sfac;
    samplers.push_back(&sfac);
    _model->chooseSamplers(samplers);

    _model->addDevianceNode();
  }
  catch (NodeError except) {
    _err << "Error in node " << _model->symtab().getName(except.node) << endl;
    _err << except.what() << endl;
    return false;
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR" << endl;
    _err << except.what() << endl;
    return false;
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    return false;
  }
  return true;
}

bool Console::setParameters(map<string, SArray> const &init_table,
			    unsigned int chain)
{
  if (_model == 0) {
    _err << "Can't set initial values. No model!" << endl;    
    return false;
  }
  if (chain > 1) {
    _err << "Multiple chains not currently supported. Sorry" << endl;
    return false;
  }
  try {
    _model->symtab().writeData(init_table, false);
  }
  catch (NodeError except) {
    _err << "Error in node " << _model->symtab().getName(except.node) << endl;
    _err << except.what() << endl;
    clearModel();
    return false;
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR" << endl;
    _err << except.what() << endl;
    clearModel();
    return false;
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    clearModel();
    return false;
  }
  return true;
}

bool Console::update(long n)
{
  if (_model == 0) {
    _err << "Can't update. No model!" << endl;    
    return false;
  }
  if (!_model->isInitialized()) {
    _err << "Model not initialized" << endl;
    return false;
  }
  try {
    _model->update(n);
  }
  catch (NodeError except) {
    _err << "Error in node " << _model->symtab().getName(except.node) << endl;
    _err << except.what() << endl;
    return false;
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR" << endl;
    _err << except.what() << endl;
    return false;
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    return false;
  }
  return true;
}

unsigned long Console::iter()
{
  if (!_model) {
    return 0;
  }
  else {
    return _model->iteration();
  }
}

bool Console::setMonitor(string const &name, Range const &range,
			 unsigned int thin)
{
  if (!_model) {
    _out << "Can't set monitor. No model!" << endl;    
    return false;
  }
  
  try {
    _model->setMonitor(name, range, thin);
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR:" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    return false;
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR:" << endl;
    _err << except.what() << endl;
    return false;
  }
  return true;
}

bool Console::clearMonitor(string const &name, Range const &range)
{
  if (!_model) {
    _out << "Can't clear monitor. No model!" << endl;    
    return false;
  }

  try {
    _model->clearMonitor(name, range);	  
  }
  catch (std::logic_error except) {
    _err << "LOGIC ERROR:" << endl;
    _err << except.what() << endl;
    _err << "Please send a bug report to " << PACKAGE_BUGREPORT << endl;
    return false;
  }
  catch (std::runtime_error except) {
    _err << "RUNTIME ERROR:" << endl;
    _err << except.what() << endl;
    return false;
  }
  return true;
}

bool Console::setMonitor(string const &name, unsigned int thin)
{
  if (!_model) {
    _out << "Can't set monitor. No model!" << endl;    
    return false;
  }

  NodeArray *array = _model->symtab().getVariable(name);
  if (array) {
    return setMonitor(name, array->range(), thin);
  }
  else {
    _err << "Variable " << name << " not found" << endl;
    return false;
  }
}

bool Console::clearMonitor(string const &name)
{
  if (!_model) {
    _out << "Can't clear monitor. No model!" << endl;    
    return false;
  }

  NodeArray *array = _model->symtab().getVariable(name);
  if (array) {
    return clearMonitor(name, array->range());
  }
  else {
    _err << "Variable " << name << " not found" << endl;
    return false;
  }
}

void Console::clearModel()
{
  _out << "Deleting model" << endl;
  delete _model; _model = 0;
}

bool Console::dumpState(map<string,SArray> &data_table, unsigned int chain, 
			bool observed)
{
  if (chain > 1) {
    _err << "Multiple chains not supported. Sorry" << endl;
    return false;
  }
  if (!_model) {
    _err << "No model" << endl;
    return false;
  }
  if (_model->symtab().size() == 0) {
    _err << "Symbol table is empty" << endl;
    return false;
  }
  _model->symtab().readData(data_table, observed);
  return true;
}


bool Console::coda(ofstream &index, ofstream &output)
{
  if (!_model) {
     _err << "Can't dump CODA output. No model!" << endl;
     return false;
  }

  vector<Node const *> nodes;
  list<TraceMonitor*> const &monitors = _model->monitors();
  for (list<TraceMonitor*>::const_iterator i = monitors.begin();
       i != monitors.end(); i++) 
    {
      nodes.push_back((*i)->node());
    }
  _model->coda(nodes, index, output);
  return true;
}

bool Console::coda(string const &name, Range const &range,
		   ofstream &index, ofstream &output)
{
  if (!_model) {
     _err << "Can't dump CODA output. No model!" << endl;
     return false;
  }

  NodeArray *array = _model->symtab().getVariable(name);
  if (array) {
    if (array->range().contains(range)) {
      Node *node = array->getSubset(range);
      if (node) {
	vector<Node const *> nodes(1, array->getSubset(range));
	_model->coda(nodes, index, output);
	return true;
      }
      else {
	_err << "Node is not being monitored" << endl;
	return false;
      }
    }
    else {
      _err << "Requested invalid subset of node " << name << endl;
      return false;
    }
  }
  else {
    _err << "Node " << name << " not found" << endl;
    return false;
  }
}

bool Console::coda(string const &name, ofstream &index, ofstream &output)
{
  if (!_model) {
    _err << "Can't dump CODA output. No model!" << endl;
    return false;
  }
  
  NodeArray *array = _model->symtab().getVariable(name);
  if (array) {
    vector<Node const *> nodes(1, array->getSubset(array->range()));
    _model->coda(nodes, index, output);
    return true;
  }
  else {
    _err << "Node " << name << " not found" << endl;
    return false;
  }
}
  
BUGSModel const *Console::model()
{
  return _model;
}

void Console::setSeed(unsigned int seed)
{
  /* Given a single seed, calculates seeds for the Marsaglia
   * Multicarry random number generator
   */
  unsigned int mmseeds[2];
  for(int j = 0; j < 2; j++) {
    seed = (69069 * seed + 1);
    mmseeds[j] = seed;
  }
  /* Fix up zero seeds */
  if (mmseeds[0] == 0)
    mmseeds[0] = 1;
  if (mmseeds[1] == 0)
    mmseeds[1] = 1;

  set_seed(mmseeds[0], mmseeds[1]);
}


syntax highlighted by Code2HTML, v. 0.9.1