#include <config.h>
#include <model/NodeArray.h>
#include <graph/ConstantNode.h>
#include <graph/AggNode.h>
#include <sarray/RangeIterator.h>
#include <graph/NodeError.h>

#include <string>
#include <stdexcept>

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

NodeArray::NodeArray(string const &name, Index const &dim)
  : _name(name), _range(Index(dim.size()), dim)
{
  long length = _range.length();
  _node_pointers = new Node *[length];
  _offsets = new long[length];
  for (long i = 0; i < length; i++) {
    _node_pointers[i] = 0;
    _offsets[i] = -1;
  }
}

NodeArray::~NodeArray()
{
  delete [] _node_pointers;
  delete [] _offsets;
}

bool NodeArray::isEmpty(Range const &target_range) const
{
  if (!_range.contains(target_range))
    throw logic_error("Range error in NodeArray::isEmpty");
  
  for (RangeIterator i(target_range); !i.atEnd(); i.nextLeft()) {
    if (_node_pointers[_range.leftOffset(i)] != 0)
      return false;
  }
  return true;
}

void NodeArray::insert(Node *node, Range const &target_range)
{
  if (!node) {
    throw logic_error(string("Attempt to insert NULL node at ") + name() +
		      print(target_range));
  }
  if (node->data.dim(true) != target_range.dim(true)) {
    throw runtime_error(string("Cannot insert node into ") + name() + 
			print(target_range) + ". Dimension mismatch");
  }
  if (!_range.contains(target_range)) {
    throw runtime_error(string("Cannot insert node into ") + name() + 
			print(target_range) + ". Range out of bounds");
  }
  if (!isEmpty(target_range)) {
    throw runtime_error(string("Node ") + name() + print(target_range)
			+ " overlaps previously defined nodes");
  }

  /* Set the _node_pointers array and the offset array */
  RangeIterator j(target_range);
  for (unsigned long k = 0; !j.atEnd(); j.nextLeft(), ++k)
    {
      long offset = _range.leftOffset(j);
      _node_pointers[offset] = node;
      _offsets[offset] = k;
    }

  /* Add to the graph */
  _graph.add(node);
}

Node *NodeArray::find(Range const &target_range) const
{
  if (!_range.contains(target_range)) {
    return 0;
  }

  long offset = _range.leftOffset(target_range.lower());
  Node *node = _node_pointers[offset];
  if (!node)
    return 0;

  if (node->data.dim(true) != target_range.dim(true))
    return 0;

  RangeIterator j(target_range);
  for (long k = 0; !j.atEnd(); j.nextLeft(), ++k) {
    offset = _range.leftOffset(j);
    if (_node_pointers[offset] != node || _offsets[offset] != k)
      return 0;
  }

  return node;
}

Node *NodeArray::getSubset(Range const &target_range)
{
  /* If range corresponds to a set node, then return this */
  Node *node = find(target_range);
  if (node)
    return node;

  /* If range corresponds to a previously created subset, then return this */
  map<Range, Node *>::iterator p = _generated_nodes.find(target_range);
  if (p != _generated_nodes.end()) {
    return p->second;
  }

  /* Otherwise create an aggregate node */
  vector<Node *> nodes;
  vector<unsigned long> offsets;
  for (RangeIterator i(target_range); !i.atEnd(); i.nextLeft()) {
    long offset = _range.leftOffset(i);
    if (_node_pointers[offset] == 0) {
      return 0;
    }
    nodes.push_back(_node_pointers[offset]);
    offsets.push_back(_offsets[offset]);
  }
  node = new AggNode(target_range.dim(false), nodes, offsets);
  _generated_nodes[target_range] = node;
  _graph.add(node);
  return node;
}

void NodeArray::setValue(SArray const &value, bool observed)
{
  if (!(_range == value.range())) {
    throw runtime_error(string("Dimension mismatch when setting value of node array ") + name());
  }
  
  double const *x = value.value();

  long j = 0;
  for (RangeIterator i(_range); !i.atEnd(); i.nextLeft(), ++j) {
    double fvalue = x[j];
    if (fvalue != JAGS_NA) {
      Node *node = _node_pointers[j];
      if (node == 0) {
	if (observed) {
	  node = new ConstantNode(fvalue);
	  insert(node, i);
	}
	else {
	  throw runtime_error(string("Attempt to set value of undefined node ")
			      + name() + print(i));
	}
      }
      else {
        if (!isObserved(node)) {
	  node->data.setValue(fvalue, _offsets[j]);
        }
        else {
          throw NodeError(node, "Attempt to change value of data node");
        }
      }
    }
  }
  
  //Gather all the nodes for which a data value is supplied
  set<Node*> setnodes;
  for (unsigned long i = 0; i < _range.length(); ++i) {
    if (x[i] != JAGS_NA) {
      setnodes.insert(_node_pointers[i]);
    }
  }

  // Check to see if Node's value is partially missing
  for (set<Node*>::const_iterator p = setnodes.begin(); 
       p != setnodes.end(); ++p) 
    {
      double const *member_value = (*p)->data.value();
      unsigned long member_length = (*p)->data.length();
      for (unsigned long j = 0; j < member_length; ++j) {
	if (member_value[j] == JAGS_NA) {
	  throw NodeError(*p,"Values supplied for node are partially missing");
	}
      }     
      if (observed) {
	(*p)->data.setFixed(true);
      }
    }
}


void NodeArray::getValue(SArray &value, bool observed) const
{
  if (!(_range == value.range())) {
    string msg("Dimension mismatch when getting value of node array ");
    msg.append(name());
    throw runtime_error(msg);
  }

  long array_length = _range.length();
  double *array_value = new double[array_length];
  for (int j = 0; j < array_length; ++j) {
    Node const *node = _node_pointers[j];
    if (node && node->isStochastic() && isObserved(node) == observed) {
       array_value[j] = node->data.value()[_offsets[j]];
    }
    else {
       array_value[j] = JAGS_NA;
    }
  }

  value.setValue(array_value, array_length);
  delete [] array_value;
}

void NodeArray::getAllValues(SArray &value) const
{
  if (!(_range == value.range())) {
    string msg("Dimension mismatch when getting value of node array ");
    msg.append(name());
    throw runtime_error(msg);
  }

  long array_length = _range.length();
  double *array_value = new double[array_length];
  for (int j = 0; j < array_length; ++j) {
    Node const *node = _node_pointers[j];
    if (node) {
      array_value[j] = node->data.value()[_offsets[j]];
    }
    else {
      array_value[j] = JAGS_NA;
    }
  }

  value.setValue(array_value, array_length);
  delete [] array_value;
}

string const &NodeArray::name() const
{
  return _name;
}

Range const &NodeArray::range() const
{
  return _range;
}

Graph const &NodeArray::graph() const
{
  return _graph;
}

bool NodeArray::findActiveIndices(Index &ind, unsigned int k, 
				  Index const &lower, Index const &dim) const
{
  /* 
     We pay a heavy computational price for the flexibility of
     allowing users to insert multivariate nodes in arbritary
     ways into the NodeArray.
     
     Suppose we have an array of dimension [3,4,2,5], the lower index
     is [1,2,1,2] and the dimension of the node is [3,2]. Then the
     node could be inserted in 5 different ways.

     [1:3, 2:3, 1, 2] Active indices (0,1)
     [1:3, 2, 1:2, 2]                (0,2)
     [1:3, 2, 1, 2:3]                (0,3)
     [1, 2:4, 1:2, 2]                (1,2)
     [1, 2:4, 1, 2:3]                (1,3)

     We can't have active indices (2,3) because the node won't fit
  */

  if (k == 0)
    ind[k] = 0;
  else
    ind[k] = ind[k-1] + 1;
  int m = ind.size();
  int M = _range.ndim(false);
  for (;ind[k] <= M + k - m; ind[k] = ind[k] + 1) {
    if (k == m - 1) {
      Index upper(lower);
      for (unsigned int l = 0; l < m; ++l) {
	upper[ind[l]] = upper[ind[l]] + dim[l] - 1;
      }
      Range test_range(lower, upper);
      if (_range.contains(test_range)) {
	Node *node = _node_pointers[_range.leftOffset(lower)];
	unsigned int j = 0;
	bool ok = true;
	for (RangeIterator i(test_range); !i.atEnd(); i.nextLeft(), ++j) {
	  long offset = _range.leftOffset(i);
	  if (_node_pointers[offset] != node || _offsets[offset] != j) {
	    ok = false;
	    break;
	  }
	}
	if (ok)
	  return true;
      }
    }
    else {
      if (findActiveIndices(ind, k+1, lower, dim))
	return true;
    }
  }
  return false;
}

Range NodeArray::getRange(Node const *node) const
{
  if (!_graph.contains(node)) {
    return Range();
  }

  //Look in the generated nodes first
  for (map<Range, Node *>::const_iterator p = _generated_nodes.begin(); 
       p != _generated_nodes.end(); ++p) 
    { 
      if (node == p->second)
	return p->first;
    } 
  
  /* Find the lower limit of the range. This is easy */
  unsigned int ndim = _range.ndim(false);
  Index lower(ndim), upper(ndim);
  long j = 0;
  for (; j < _range.length(); ++j) {
    if (_node_pointers[j] == node) {
      lower = _range.leftIndex(j);
      break;
    }
  }
  if (j == _range.length()) {
    return Range();
  }

  unsigned int m = node->data.ndim(false);
  Index ind(m);
  if (findActiveIndices(ind, 0, lower, node->data.dim(false))) {
    upper = lower;
    for (unsigned int l = 0; l < m; ++l) {
      upper[ind[l]] = upper[ind[l]] + node->data.dim(false)[l] - 1;
    }
    return Range(lower, upper);    
  }
  else {
    throw logic_error("Unable to find node range");
  }
}


syntax highlighted by Code2HTML, v. 0.9.1