#include <config.h>
#include <graph/Graph.h>
#include <graph/StochasticNode.h>
#include <graph/NodeError.h>
#include <distributions/Distribution.h>
#include <sarray/Index.h>

#include <vector>
#include <stdexcept>
#include <string>
//#include <stdexcept>

using std::vector;
using std::string;
//using std::logic_error;

StochasticNode::StochasticNode(Distribution const *dist, Index const &dim)
  : Node(dim), _dist(dist), _parameters()
{
  if (dist->isDiscreteValued()) {
    data.setDiscreteValued(true);
  }
}

StochasticNode::~StochasticNode()
{
}

bool StochasticNode::isBounded() const
{
  if (_parameters.empty()) {
    return false;
  }
  else {
    return _dist->lowerBound(_parameters) || _dist->upperBound(_parameters);
  }
}

SArray const *StochasticNode::lowerBound()
{
  return _dist->lowerBound(_parameters);
}

SArray const *StochasticNode::upperBound()
{
  return _dist->upperBound(_parameters);
}

void StochasticNode::setBounds(Node *lBound, Node *uBound)
{
  Index const &dim = data.dim(true);

  if (!_dist->canBound()) {
    throw NodeError(this, string("Distribution ") + _dist->name()
                    + " cannot be bounded");
  }
  if (_parameters.empty()) {
    throw NodeError(this, "Cannot set bounds before parameters");
  }
  if ((lBound && lBound->data.dim(true) != dim) || 
      (uBound && uBound->data.dim(true) != dim)) 
    {
      throw NodeError(this,"Dimension mismatch when setting bounds");
    }

  if (lBound) {
    SArray const *lb = _parameters[_parameters.size() - 2];
    if (lb != 0) {
      throw NodeError(this, "Attempt to reset lower bound");
    }
    _parameters[_parameters.size() - 2] = &lBound->data;
    addParent(lBound);      
  }
  if (uBound) {
    SArray const *ub = _parameters[_parameters.size() - 1];
    if (ub != 0) {
      throw NodeError(this, "Attempt to reset upper bound");
    }
    _parameters[_parameters.size() - 1] = &uBound->data;
    addParent(uBound);
  }
}


Distribution const *StochasticNode::distribution() const
{
    return _dist;
}

void StochasticNode::setParameters(vector<Node *> const &parameters)
{
  if (_parameters.size() != 0) {
    throw NodeError(this, "Attempt to reset parameters");
  }

  for (unsigned int i = 0; i < parameters.size(); ++i) {
    _parameters.push_back(&(parameters[i]->data));
    this->addParent(parameters[i]);
  }
  // Push back zeros for the upper and lower bounds
  if (_dist->canBound()) {
    _parameters.push_back(0);
    _parameters.push_back(0);
  }
  
  if (_parameters.size() != _dist->npar()) {
    throw NodeError(this, "Incorrect number of parameters for distribution");
  }
  if (!_dist->checkParameterDim(_parameters)) {
    throw NodeError(this,"Invalid parameter dimensions for distribution");
  }
  if (_dist->dim(_parameters) != data.dim(true)) {
    throw NodeError(this, "Dimension mismatch between parameters and Node");
  }
}

double StochasticNode::logDensity() const
{
  return _dist->logLikelihood(data, _parameters);
}

vector<SArray const *> const &StochasticNode::parameters() const
{
  return _parameters;
}

void StochasticNode::forwardSample() 
{
  _dist->randomSample(data, _parameters);
}  

StochasticNode const *asStochastic(Node const *node)
{
  return dynamic_cast<StochasticNode const*>(node);
}

bool StochasticNode::isStochastic() const
{
  return true;
  /*
  if (_parameters.empty())
    throw logic_error("Cannot determine if node with no parameters is stochastic");

  if (_dist->df(_parameters) == 0) {
    //Deterministic distributions are considered stochastic only
    //if they are observed, in which case they generate a likelihood.
     return isObserved(this);
  }
  else {
     return true;
  }
  */
}



syntax highlighted by Code2HTML, v. 0.9.1