/* function.h */

#ifndef FUNCTION__H
#define FUNCTION__H

#include "data_classes.h"
#include <LEDA/list.h>
#include "set.h"
#include "dep_remove.h"
#include "xtype.h"

class dc_element; /* include element.h */
class dc_arg;

/* - dc_func is the abse class for function expression primitives

   - dc_funcs have virtual member functions to 
   -   evaluate the function
   -   remove element dependencies,
   -   rehash all references
   -   simplify the function

   - all functions return a temporary data value, and those that access constant
   data or elements copy the value before returning, so return values can be
   manipulated or deleted without changing the value.

   - dc_funcs that take dc_funcs as args, ex. dc_ops, dc_ucast, dc_fcast delete
   their args on destruction, so function sharing is dangerous

   - dc_elements have a list of dc_args which store default values and are
   substituted with passed values when an element call occurs

   - all dc_funcs are declared in this header file excpet for dc_arg and 
   dc_func_call which are defined in element.h
   */

class dc_func {
private:
  virtual dc_data *evaluate_f( void ) = 0;

protected:
  /* owner is set by the parser to the element using this function to ease
     tracking down run-time errors */
  dc_label *owner;

public:
  dc_func() { owner = nil; }
  virtual ~dc_func() {;}

  virtual void set_owner( dc_label *l ) { owner = l; }
  dc_label *get_owner( void ) const { return owner; }

  /* returns a temporary or nil on failure */
  dc_data *evaluate( void );/* { return evaluate_f(); }*/
  
  /* - returns return type of function.
     - return type will be one of
     -   Boolean_t, Int_t, Real_t, Distrib_t, Rect_Matrix_t, Vector_t, Triple_t,
     -   String_t, or Symbol_t from types.h
     - or on failure return type will be Undef_t */
  virtual dc_type get_rtype( void ) = 0;
  virtual void get_xtype( xtype & ) {;} // = 0;

  /* sets contents to value.  true on error */
  virtual bool assign( dc_data * ) { return true; }

  /* simplifies expression where possible. returns dc_data if expression can be
     resolved to a constant value, but returns nil otherwise */
  virtual dc_data *simplify( void ) { return nil; }

  /* source in display.cc */
  virtual ostream &display( ostream &stream = cout ) const = 0; /*{ return stream; } */

  /* given a list of tags of dependencies to remove from this function, sets any
     elvalue refs to tags in list to old and returns true if any removed */
  virtual bool remove_deps( const int, const tag[] ) { return false; }
  
  /* does 3 things 
     1 ) rehashes all element references( dc_elvalues, dc_func_calls ) based on
         stored string and origin.  
     2 ) rehashes all sets
     3 ) generates a list of all elements this function depends on.  
         dep_list defined in dep_remove.h.  dependencies in ( from, to ) form 
	 with from being tag passed to rehash call.  
     returns number of errors */
  virtual int rehash( dep_list &, const tag /*owner_tag*/ ) { return 0; }
  int rehash( void ) 
    { dep_list dl; int nerrs = rehash( dl, 0 ); dl.clear(); return nerrs; }

  /* returns a copy of the function */
  virtual dc_func *duplicate( dc_label *search_origin, 
			      list<dc_arg *> *arg_source ) const = 0;
};

typedef list<dc_func *> f_list;
typedef dc_data *( *fn )( const f_list & );
/* t_fn's return a type given a list of args. call get_rtype */
typedef dc_type ( *t_fn )( const f_list & ); 
/* set_t_fn returns a type given a type.  used to calculate return type for a 
   set operation given an arbitrary number of args of the same type */
typedef dc_type ( *set_t_fn )( const dc_type );

/* returns temporary copy of constant value */
class dc_const : public dc_func {
private:
  dc_data *D;

public:
  dc_const( dc_data &d ); /* dc_const will consider d owned if temporary */
  ~dc_const();
  
  dc_data *evaluate_f( void ); /* returns temporary copy */

  dc_type get_rtype( void ) { if( D ) return D->sub_type();else return Data_t; }
  void get_xtype( xtype &xt );

  dc_data *simplify( void ) { return evaluate(); }
  ostream &display( ostream &stream = cout ) const;

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* - returns copy of contents of an element.
   - contains hash_path so the reference can be relinked.
   - if boolean old is true then evalation will use dc_element::get_previous to
   get the old value.  this is sed in self references, explicit previous value
   queries, and after dependancies have been removed.
   */
class dc_elvalue : public dc_func {
protected:
  dc_element *E;

  string hash_path; /* label that E was parsed from, used to rehash */
  dc_label *hash_origin; /* origin E was parsed from */
  
  bool old;

  list_item el_li;

public:
  dc_elvalue( dc_element &e, const string &, dc_label *origin,
	      const bool = false ); /* will be relinkeable */
  dc_elvalue( dc_element &e, const bool = false ); /* will not be relinkable */
  dc_elvalue( const string &, dc_label *origin, const bool = false );
  ~dc_elvalue( void );

  /* returns temporary copy of updated value of E */
  dc_data *evaluate_f( void );

  dc_type get_rtype( void );
  void get_xtype( xtype &xt );

  bool assign( dc_data * );
  ostream &display( ostream &stream = cout ) const;

  void set_old( const bool Old ) { old = Old; }
  bool get_old( void ) const { return old; }

  bool remove_deps( const int, const tag[] );

  int rehash( dep_list &d, const tag T );
  bool relink( void );

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* attempts to rehash all undefined element references */
//void relink_undef_elvalues( void );
int relink_elrefs( void );

/* abstract class for operations that perform a function on a set of args */
class dc_base_op : public dc_func {
protected:
  /* index into fn_list table in parser.h that specifies operation to perform,
     and type checking functions to use */
  int info_index;
  f_list args; /* list of arguments of type dc_func* */
  int nargs; /* number of arguments */

  /* rtype is stored in T when get_rtype is called since the call usually
     requires all args rtypes to be gotten.  if op_changed is true then
     get_rtype will do work and store the value in T, otherwise T will just be
     returned */
  bool op_changed;
  dc_type T;
  xtype xt;
public:
  dc_base_op( void ) { info_index = 0; nargs = 0; op_changed = true; }
  ~dc_base_op() { clear_args(); }
  bool set_info( const int index );
  int get_nargs() const { return nargs; }

  void set_owner( dc_label *l ) 
    { owner = l; dc_func *f; forall( f, args ) if( f ) f->set_owner( l ); }

  /* delete all args.  called by destructor */
  virtual void clear_args( void );

  dc_data *evaluate_f( void ) = 0;

  bool remove_deps( const int, const tag[] );
  int rehash( dep_list &, const tag );
};

/* has to perform an operation on data */
class dc_op : public dc_base_op {
public:
  dc_data *evaluate_f( void );

  /* clears args and adds two args */
  void set_bin_args( dc_func &, dc_func & );
  /* clears arg and adds one arg */
  void set_arg( int, dc_func & );
  /* adds an arg to what is already there.  only one used by parser */
  void add_arg( dc_func & );

  dc_type get_rtype( void );
  //  void get_xtype( xtype &xt );

  dc_data *simplify( void );
  ostream &display( ostream &stream = cout ) const;

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* useless except as base class since no way to set set */
class dc_set_op : public dc_base_op {
private:
  dc_type arg_type;

protected:
  bool hashed; /* used to decide if rehash is needed before op performed */
  dc_set *set;

  void hash_set( void );
public:
  dc_set_op( void );
  ~dc_set_op( void );

  dc_type get_rtype( void );
  //  void get_xtype( xtype &xt );

  /* - arg_type is the type of the argument expected, ex. Real_t, Int_t, etc.
     - when rehashing, any data or element matching the criteria that has type
     arg_type or can be cast to arg_type will be included.  any others will nor
     be */
  void set_arg_type( const dc_type t ) { arg_type = t; }
  dc_type get_arg_type( void ) const { return arg_type; }

  ostream &display( ostream &stream = cout ) const;

  /* calls remove_deps on each arg */
  bool remove_deps( const int, const tag[] );

  /* function used to turn a set of dc_label *s into a list of dc_func *s */
  friend void add_to_args( dc_label *, void * );
};

/* owns a temprary set */
class dc_tset_op : public dc_set_op {
public:
  ~dc_tset_op( void ) { if( set != nil ) delete( set ); }

  void set_set( dc_set &S )
    { if( set != nil ) delete( set ); set = &S; }
  
  dc_data *evaluate_f( void );
  
  int rehash( dep_list &, const tag );

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* gets set from a rehashable element or dc_set rather than a set. */
class dc_pset_op : public dc_set_op {
private:
  dc_label *id;
  string label;
  dc_label *search_origin;

  //  dc_set_op::set_set;
public:
  dc_pset_op( void );
  dc_pset_op( cstring label, dc_label * );

  void set_set_info( cstring lbl, dc_label *s_o )
    { label = lbl; search_origin = s_o; hashed = false; op_changed = true; }

  dc_data *evaluate_f( void );
  
  ostream &display( ostream &stream = cout ) const;

  int rehash( dep_list &, const tag );

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* function cast
   - casts the dc_data * returned by f->evaluate_f() to type T using 
   cast( dc_data&, dc_type ) defined in data.h. */
class dc_fcast : public dc_func {
private:
  dc_type T;
  xtype xt;
  dc_func *f;
public:
  dc_fcast( dc_func &F, const dc_type t ) 
    { f = &F; F.set_owner( get_owner() ); T = t; }
  ~dc_fcast( void ) { if( f ) delete( f ); }

  /* the set functions do not check if f is castable to T.  if not then 
     evaluation will return nil */
  void set_rtype( const dc_type t ) { T = t; }
  void set_func( dc_func *F ) 
    { if( f ) delete( f ); if( ( f = F ) != nil ) f->set_owner( get_owner() ); }

  void set_owner( dc_label *l ) 
    { owner = l; if( f ) f->set_owner( l ); }

  dc_data *evaluate_f( void );
  
  dc_type get_rtype( void ) { return f ? T : Undef_t; }
  void get_xtype( xtype &xt );

  /* always simplifies if f simplifies */
  dc_data *simplify( void );

  ostream &display( ostream &stream = cout ) const;

  bool remove_deps( const int, const tag[] );
  int rehash( dep_list &dl, const tag T ) { return f->rehash( dl, T ); }

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* unit cast
   - casts the dc_data* returned by f->evaluate_f() to units uv multiplying by 
   coeff */
class dc_ucast : public dc_func {
private:
  dc_func *f;
  unit_vec uv;
  double coeff;

public:
  dc_ucast( dc_func &f, const unit_vec &uv, double coeff = 1 );
  ~dc_ucast() { if( f ) delete( f ); }

  dc_data *evaluate_f( void );

  dc_type get_rtype( void );
  void get_xtype( xtype &xt );

  void set_owner( dc_label *l ) 
    { owner = l; if( f ) f->set_owner( l ); }

  dc_data *simplify( void );

  ostream &display( ostream &stream = cout ) const;

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

/* gets the time or delta_time from an object's clock */
class dc_tquery : public dc_func {
protected:
  dc_label *L;

  bool T; /* true to get time, false to get delta_time */
public:
  dc_tquery( dc_label *l, const bool t ) { L = l; T = t; }

  dc_data *evaluate_f( void );

  dc_type get_rtype( void ) { return Real_t; }
  void get_xtype( xtype &xt );

  ostream &display( ostream &stream = cout ) const;

  dc_func *duplicate( dc_label *, list<dc_arg *> * ) const;
};

inline ostream &operator<<( ostream &stream, const dc_func &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_const &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_elvalue &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_op &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_set_op &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_fcast &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_ucast &f ) {
  return f.display( stream );
}
inline ostream &operator<<( ostream &stream, const dc_tquery &f ) {
  return f.display( stream );
}

/* takes as arg function that returned a nil value, and string describing
   function from which run_time_error was called */
void run_time_error( const dc_func &, cstring );

/* OPERATIONS
   - operations used in fn_list table in parser.h */
/* a0 + a1 + ... an
   - sums scalars, vectors, or matrices, or appends strings.
   - summing sets of sstrings is a bad idea since sets are not hashed in any
   particular order */
dc_data *sum( const f_list & );

/* +a0 - a1 - a2 - ... an */
dc_data *neg_sum( const f_list & );

/* a0 * a1 * a2 * ... an */
dc_data *product( const f_list & );

/* a0 . a1 */
dc_data *dot_product( const f_list & );

/* a0 / ( a1 * a2 * ... an ) - real division */
dc_data *inv_prod( const f_list & );

/* a0 \ ( a1 * a2 * ... an ) - integer division if both args integer */
dc_data *int_div( const f_list & ); 

/* mag( a0 ). any extra args ignored
 returns absolute value of a scalar, length of a vector, determinant of a 
 matrix, or length of a string */
dc_data *magnitude( const f_list & );

/* a0 % ( a1 % ( a2 % ... an ) ) ... ) */
dc_data *mod( const f_list & ); 

/* a0 == a1 && a1 == a2 && ... an-1 == an */
dc_data *equal( const f_list & ); 

/* a0 != a1 && a1 != a2 && ... an-1 != an */
dc_data *not_equal( const f_list & );

/* a0 < a1 && a1 < a2 && ... an-1 < an */ 
dc_data *less_than( const f_list & );

/* a0 > a1 && a1 > a2 && ... an-1 > an */
dc_data *greater_than( const f_list & ); 

/* a0 <= a1 && a1 <= a2 && ... an-1 <= an */
dc_data *less_equal( const f_list & ); 

/* a0 >= a1 && a1 >= a2 &&...an-1 >= an */
dc_data *greater_equal( const f_list & ); 

/* !a0. other args ignored */
dc_data *not( const f_list & ); 

/* uses if then else if ... else structure
   a0 ? a1 : ( a2 ? a3 : ( ... ( an-2 ? an-1 : n ) ... ) ) */
dc_data *conditional( const f_list & ); 

dc_data *and( const f_list & );
dc_data *or( const f_list & );

/* trig functions take 1 argument */
dc_data *Sin( const f_list & );
dc_data *Cos( const f_list & );
dc_data *Tan( const f_list & );
dc_data *Cot( const f_list & );
dc_data *Asin( const f_list & );
dc_data *Acos( const f_list & );
dc_data *Atan( const f_list & );

/* takes two args */
dc_data *Atan2( const f_list & );
dc_data *Hypot( const f_list & );

dc_data *Min( const f_list & );
dc_data *Max( const f_list & );

/* average */
dc_data *avg( const f_list & );

/* returns average for now */
dc_data *mean( const f_list & );

/* returns nu,ber of args only */
dc_data *count( const f_list & );

/* root mean square */
dc_data *rms( const f_list & );

/* args = { x, a0, a1, ... an }. returns a0x^n + a1x^(n-1) ... a(n-1)x^1 + an */
dc_data *poly( const f_list & ); 

/* e^arg0 */
dc_data *Exp( const f_list & );

/* e^arg0 - 1 accurate even for tiny arg0 */
dc_data *Expm1( const f_list & );

/* ln( arg0 ) */
dc_data *Log( const f_list & );

/* ln( 1 + arg0 ) even for tiny arg0 */
dc_data *Log1p( const f_list & );

/* log10( arg0 ) */
dc_data *Log10( const f_list & );

/* arg0 ^ arg1 */
dc_data *Pow( const f_list & );

/* square root of arg0 */
dc_data *Sqrt( const f_list & );

/* cube root of arg0 */
dc_data *Cbrt( const f_list & );

/* cieling, floor, or round of a real to integer */
dc_data *Ceil( const f_list & );
dc_data *Floor( const f_list & );
dc_data *Round( const f_list & );

/* used to get fields of a distribution */
dc_data *dist_min( const f_list & );
dc_data *dist_max( const f_list & );
dc_data *dist_mean( const f_list & );
dc_data *dist_stddev( const f_list & );
dc_data *dist_sample( const f_list & );

/* - returns a distribution given two or three args of type Real_t or Int_t
   - if two, Mean = arg0, Stddev = arg1
   - if three, Mean = arg0, Min = arg1, Max = arg2
   */
dc_data *gdistrib( const f_list & );

/* - returns a vector of size = number of args or triple( if three args )
   - element i = argi */
dc_data *gvec( const f_list & );

/* assignment
   - takes two args
   - arg0 = LHS, arg1 = RHS
   - LHS must be an element reference.
   - RHS is assigned to LHS */
dc_data *assignment( const f_list & );

/* for all typing function, any number of arguments means >=1 unless otherwise
   specified */
dc_type t_2rl( const f_list & ); /* takes two scalar returns a real */
dc_type t_rl( const f_list & ); /* takes one scalar returns a real */

/* takes two scalar returns a real if one or both are real, otherwise returns 
   an int */
dc_type t_mod( const f_list & ); 

/* takes any one argument except bool or symbol and if it is an int or string 
   returns an int otherwise returns a real */
dc_type t_mag( const f_list & ); 

/* takes any number of args >= 1. iterative */
dc_type t_inv_prod( const f_list & ); 

dc_type t_prod( const f_list & ); /* takes any number of args >= 1. iterative */

/* takes two triples or vectors and returns a real */
dc_type t_dot_prod( const f_list & );

/* takes any number of args >= 1. iterative */
dc_type t_int_div( const f_list & ); 

/* takes any number of args.  must be scalar or all of one type except bool,
   string or symbol. if scalar and at least one is real then result is real 
   otherwise int.  if not scalar, return that one type */
dc_type t_same( const f_list & ); 

/* takes any number of args.  must be scalar or all of one type except bool or 
   symbol. if scalar and at least one is real then result is real otherwise int.
   if not scalar return that one type */
dc_type t_sum( const f_list & ); 

dc_type t_not( const f_list & ); /* takes one bool returns bool */

/* takes any number of arguments of scalar type or same type that can be tested
   for equality and returns bool */
dc_type t_equal( const f_list & ); 

/* takes any number of scalar or any number of same non-matrix type and returns
   bool */
dc_type t_comp( const f_list & ); 

/* takes any number of bools and returns bool */
dc_type t_andor( const f_list & );

/* takes two or three scalar and returns distrib */
dc_type t_gdistrib( const f_list & ); 

/* takes any number of scalars and if number is 3 returns a triple else 
   a vector */
dc_type t_gvec( const f_list & );

/* returns any number or no args and returns an int */
dc_type t_int( const f_list & ); 

/* takes any number of scalars and returns a real */
dc_type t_sclr2rl( const f_list & ); 

/* takes one real and returns an int */
dc_type t_rl2int( const f_list & );

/* takes any number of scalar args.if one is real then result is real */
dc_type t_mnmx( const f_list & ); 

dc_type t_distop( const f_list & ); /* takes a distrib and returns a real */

/* number of arguments must be odd. every even argument must be scalar or of 
   same type.  every odd argument except last must be boolean. last argument 
   must be same type as even args */
dc_type t_cond( const f_list & ); 

/* takes two args.  2nd must be castable to first. returns type of first */
dc_type t_assignment( const f_list & ); 

/* set typing functions */
dc_type st_sum( const dc_type );
dc_type st_prod( const dc_type );
dc_type st_same( const dc_type );
dc_type st_sclr2rl( const dc_type );
dc_type st_fail( const dc_type );
dc_type st_int( const dc_type );
dc_type st_mnmx( const dc_type );

#endif
