
#include "preact/react.h"
#include <algorithm>
#include "preact/iutil_str.h"

// -------------- React ----------------
React::React(aReact* q) : p(q) { 
  // if (p==0) TRANSP_THROW("?preact::React(): Attempted to construct a React with a null pointer") ; 
  if (p) ++(p->count) ; 
} ;

React::~React() { 
  if (p && --(p->count)==0) delete p ; 
} ;

React::React(const React& q) : p(q.p) { if (p) ++(p->count) ; } ;

React& React::operator=(const React& q) { 
  if (q.p) ++(q.p->count) ; 
  if (p && --(p->count)==0) delete p ; 
  p=q.p ; 
  return *this ; 
} ;

bool React::operator()(const ap_64& xargs, ap_64& f) {
  // check arguments
  if (xargs.rank()!=1)       TRANSP_THROW("?preact::React::operator(): Argument must be a rank one array") ;
  if (xargs.xsize(0)!=p->nx) TRANSP_THROW("?preact::React::operator(): Wrong number of arguments in array") ;
  if (!insame_shape(p->shape,f.shape())) TRANSP_THROW("?preact::React::operator(): Result array has the wrong shape") ;

  Range::range_error r ;
  p->call(xargs,f,r) ;     // fill result array

  // range check?
  bool warn=false ;        // true when WARN 
  if (r != Range::RANGE_OK) {
    for (size_t i=0 ; i<p->nx ; i++) warn = p->xrange[i].check(xargs(i),f) || warn ;  // check each argument
  } ;

  return warn ;
} ;
 
bool React::vector1(const ap_64& x, ap_64& fv, bool skip_arg_check) {  
  if (!skip_arg_check) {
    // check arguments
    if (x.rank()!=1) TRANSP_THROW("?preact::React::vector1(): Arguments must be rank one arrays") ;
    if (fv.rank()<1) TRANSP_THROW("?preact::React::vector1(): Result must be at least a rank one array") ;
    size_t n = x.xsize(0) ;  // number of function evaluations
  
    if (fv.xlast_size()!=n) TRANSP_THROW("?preact::React::vector1(): Arguments have inconsistent dimensions") ;
    if (!insame_last_shape(p->shape,fv.shape())) TRANSP_THROW("?preact::React::vector1(): Result array has the wrong shape") ;
  } ;
  Range::range_error r ;
  p->vcall1(x,fv,r) ;      // fill result array
  
  // range check?
  bool warn=false ;        // true when WARN 
  if (r != Range::RANGE_OK) {
    warn = p->xrange[0].check(x,fv) ;
  } ;
  
  return warn ;
} ;

void React::set_min_policy(size_t i, Range::policy pmin, fp_64 clear_value, fp_64 clear_scale) {
  p->set_min_policy(i,pmin,clear_value,clear_scale) ;
} ;

void React::set_max_policy(size_t i, Range::policy pmax, fp_64 clear_value, fp_64 clear_scale) {
  p->set_max_policy(i,pmax,clear_value,clear_scale) ;
} ;

bool React::vector2(const ap_64& x, const ap_64& y, ap_64& fv, bool skip_arg_check) {  
  if (!skip_arg_check) {
    // check arguments
    if (x.rank()!=1 || y.rank()!=1) TRANSP_THROW("?preact::React::vector2(): Arguments must be rank one arrays") ;
    if (fv.rank()<1) TRANSP_THROW("?preact::React::vector2(): Result must be at least a rank one array") ;
    
    size_t n = x.xsize(0) ;  // number of function evaluations
    
    if (y.xsize(0)!=n || fv.xlast_size()!=n)     TRANSP_THROW("?preact::React::vector2(): Arguments have inconsistent dimensions") ;
    if (!insame_last_shape(p->shape,fv.shape())) TRANSP_THROW("?preact::React::vector2(): Result array has the wrong shape") ;
  } ;

  Range::range_error r ;
  p->vcall2(x,y,fv,r) ;    // fill result array
  
  // range check?
  bool warn=false ;        // true when WARN 
  if (r != Range::RANGE_OK) {
    bool warnx = p->xrange[0].check(x,fv) ;
    bool warny = p->xrange[1].check(y,fv) ;
    warn = warnx || warny ;                // separate calls to avoid short circuiting || operator
  } ;
  
  return warn ;
} ;


ostream& operator<<(ostream& c, const React& r) {
  return c << r.print() ;
} ;

// -------------- aReact ----------------
const unsigned aReact::MAX_LINK ;

aReact::~aReact() {
  // free React* in list
  for(list<React*>::iterator pr = lreact.begin() ; pr != lreact.end() ; ++pr) {
    delete (*pr) ;
  }  ;
} ;

aReact::aReact(string name, size_t num_args, Shape fshape) : fname(name), nx(num_args),
							     shape(fshape), xrange(num_args), lreact(0),
							     count(0) { 
  if (num_args==0) TRANSP_THROW("?preact::aReact(): Need at least one argument to function") ;

  // set the name of the Ranges
  for (size_t i=0 ; i<nx ; i++) {
    ostringstream c ;
    c << "arg " << i << ": " << name ;
    xrange[i].set_name(ss2string(c)) ;
  } ;
} ;

fp_64  aReact::min(size_t i) const {
  if (i>=nx) TRANSP_THROW("?preact::aReact::min(): Argument is out of bounds") ;
  return xrange[i].min() ;
} ;

fp_64  aReact::max(size_t i) const {
  if (i>=nx) TRANSP_THROW("?preact::aReact::max(): Argument is out of bounds") ;
  return xrange[i].max() ;
};

const Range& aReact::range(size_t i) const { 
  if (i>=nx) TRANSP_THROW("?preact::aReact::range(): Argument is out of bounds") ;
  return xrange[i] ;
} ;  

void aReact::attach(const React& r) {
  bool found = false ;
  for(list<React*>::iterator pr = lreact.begin() ; pr != lreact.end() ; ++pr) {
    if (*(*pr)==r) found=true ;
  }  ;
  if (!found) lreact.push_back(new React(r)) ;
} ;

void aReact::detach(const React& r) {
  for(list<React*>::iterator pr = lreact.begin() ; pr != lreact.end() ; ++pr) {
    if (*(*pr)==r) {
      delete (*pr) ;      // free memory
      lreact.erase(pr) ;  // remove from list
      return ;
    } ;
  } ;
} ;

void aReact::notify(unsigned cnt) {
  update() ;
  unsigned inext = cnt+1 ;
  if (inext >= MAX_LINK) TRANSP_THROW("?preact::aReact::notify(): Possible circular React reference in observer list -- MAX_LINK exceeded") ;
  for (list<React*>::iterator pr = lreact.begin() ; pr != lreact.end() ; ++pr) (*pr)->p->notify(inext) ;
} ;

void aReact::set_scale(size_t i, Range::scaling s) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::set_scale(): Argument is out of bounds") ;
  xrange[i].set_scale(s) ;
  notify() ;
} ;

void aReact::set_min(size_t i, fp_64 min) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::set_min(): Argument is out of bounds") ;
  xrange[i].set_min(min) ;
  notify() ;
} ;

void aReact::set_max(size_t i, fp_64 max) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::set_max(): Argument is out of bounds") ;
  xrange[i].set_max(max) ;
  notify() ;
} ;

void aReact::set_min_policy(size_t i, Range::policy pmin, fp_64 clear_value, fp_64 clear_scale) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::set_min_policy(): Argument is out of bounds") ;
  xrange[i].set_min_policy(pmin,clear_value,clear_scale) ;
  notify() ;
} ;

void aReact::set_max_policy(size_t i, Range::policy pmax, fp_64 clear_value, fp_64 clear_scale) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::set_max_policy(): Argument is out of bounds") ;
  xrange[i].set_max_policy(pmax,clear_value,clear_scale) ;
  notify() ;
} ;

aReact* aReact::cast(const React& r) { 
  return r.p ;
} ;

Range& aReact::range(size_t i) {
  if (i>=nx) TRANSP_THROW("?preact::aReact::range(): Argument is out of bounds") ;
  return xrange[i] ;
} ;

void aReact::call(const ap_64& x, ap_64& f, Range::range_error& rerr) {
  TRANSP_THROW("?preact::aReact::call(ap_64): Not implemented") ;
} ;
void aReact::vcall1(const ap_64& x, ap_64& f, Range::range_error& rerr){
  TRANSP_THROW("?preact::aReact::vcall1(ap_64): Not implemented") ;
}  ;
void aReact::vcall2(const ap_64& x, const ap_64& y, ap_64& f, Range::range_error& rerr) {
  TRANSP_THROW("?preact::aReact::vcall2(ap_64): Not implemented") ;
} ;
void aReact::update(){
  return ;    // do nothing by default
}  ; 

state aReact::current() const {
  state s ;
  s.vs.resize(1) ;
  s.vs[0] = fname ;  // name

  s.vi.resize(2+shape.size()) ;
  s.vi[0] = nx ;            // number of args
  s.vi[1] = shape.size() ;  // rank result
  for (size_t i=0 ; i<shape.size() ; i++) s.vi[i+2] = shape[i] ;   // argument length and result shape

  s.vresize(nx) ;
  for (size_t i=0 ; i<nx ; i++) s.vstate(i)=xrange[i].current() ;  // store Range states

  // do not need to save lreact or count
  return s ;
} ;

string aReact::dprint() const {
  return string(" ") ;
} ;

string aReact::print() const {
  ostringstream c ;
  c << " ------ React ------ " << endl ;
  c << " Name: " << fname << endl ;
  c << endl ;
  c << " Number Args: " << nx << "  with ranges: " << endl ;
  c.precision(5) ; c.setf(IOS_BASE::scientific, IOS_BASE::floatfield) ;
  for (size_t i=0 ; i<nx ; i++) {
    c << "   [" << i << "] " << min(i) << "  to  " << max(i) << endl ;
  } ;
  c << endl ;
  c << " Result: " ;
  if (isscalar()) {
    c << "scalar" << endl ;
  }
  else {
    c << "rank " << shape.size() << "  with dimensions: " << endl ;
    for (size_t i=0 ; i<shape.size() ; i++) {
      c << "   ["<<i<<"] " << shape[i] << endl ;
    } ;
  } ;
  c << endl ;
  c << " Reference Count: " << count << endl ;

  // derived class information
  c << endl ;
  c << dprint() ;

  c << endl ;
  c << " Observers: " << endl ;
  if (lreact.size()==0) {
    c << "   none" << endl ;
  }
  else {
    for(list<React*>::const_iterator pr = lreact.begin() ; pr != lreact.end() ; ++pr) {
      c << "   " << (*pr)->name() << endl ;
    } ;
  }  ;

  c << endl ;
  for (size_t i=0 ; i<nx ; i++) {
    c << " Range[ " << i << "] ---> " << endl ;
    c << xrange[i] ;
  } ;
  c << endl ;

  c << " ------ End React ------ " << endl ;
  return ss2string(c) ;
} ;

//
// ----------------- tReact ---------------------
//
tReact::~tReact() {} ;

tReact::tReact(string name, size_t num, Shape fshape) : aReact(string("tReact test -- ")+name, num, fshape) { 
  // setup Range objects
  range(0).set_scale(Range::LOG) ; 
  range(0).set_min(1.) ;
  range(0).set_max(10.) ;
  range(0).set_min_policy(Range::IGNORE_CLEAR, 2.,  1.);
  range(0).set_max_policy(Range::WARN_CLEAR, 47., 1.);
  range(1).set_scale(Range::LINEAR) ; 
  range(1).set_min(2.) ;
  range(1).set_max(8.) ;
  range(1).set_min_policy(Range::WARN, 2.,  1.);
  range(1).set_max_policy(Range::HALT, 47., 1.);
} ;

React tReact::create(string name) {
  size_t num=2 ;     // number of arguments
  Shape fshape(1) ;  // result is rank=1
  fshape[0] = 3 ;    // size of result is 3
  return React(new tReact(name, num, fshape)) ; 
} ;

void tReact::update() {
  cout << name() << " has been updated" << endl ;
} ;

state tReact::current() const {
  state s ;
  s.vresize(1) ;
  s.vstate(0) = aReact::current() ;
  return s ;
} ;

string tReact::dprint() const {
  return string("I am a tReact\n") ;
} ;

void tReact::call(const ap_64& xarg, ap_64& f, Range::range_error& rerr) {
  fp_64 x = xarg(0) ;
  fp_64 y = xarg(1) ;

  fp_64 xlow  = xmin(0) ;
  fp_64 xhigh = xmax(0) ;
  fp_64 ylow  = xmin(1) ;
  fp_64 yhigh = xmax(1) ;

  rerr = Range::RANGE_OK ;

  range_check<fp_64>(x,xlow,xhigh,rerr) ;
  range_check<fp_64>(y,ylow,yhigh,rerr) ;

  f(0) = x+y ;
  f(1) = x-y ;
  f(2) = x*y ;

  return ;
} ;

void tReact::vcall2(const ap_64& xarg, const ap_64& yarg, ap_64& f, Range::range_error& rerr) {
  fp_64 xlow  = xmin(0) ;
  fp_64 xhigh = xmax(0) ;
  fp_64 ylow  = xmin(1) ;
  fp_64 yhigh = xmax(1) ;

  rerr = Range::RANGE_OK ;
  size_t n = xarg.size(0) ;

  for (size_t i=0 ; i<n ; i++) { 
    fp_64 x = xarg(i) ;
    fp_64 y = yarg(i) ;
    range_check<fp_64>(x,xlow,xhigh,rerr) ;
    range_check<fp_64>(y,ylow,yhigh,rerr) ;

    f(0,i) = x+y ;
    f(1,i) = x-y ;
    f(2,i) = x*y ;
  } ;
  return ;
} ;
