/* element.c */

#include "element.h"
#include "function.h"
#include "signal_change.h"
#include "component.h"
#include <math.h>
#include "root.h"
#include "display.h"

dc_element::dc_element( void ) {
  value = nil;
  old_value = nil;
  initial_value = nil;
  dynamic = true;

  eval = nil;
  rtype = Undef_t;
  nargs = 0;
  transparent = false;
}

dc_element::dc_element( cstring label, dc_component *parent = nil ) {
  value = nil;
  old_value = nil;
  initial_value = nil;
  dynamic = true;

  eval = nil;
  rtype = Undef_t;
  nargs = 0;
  transparent = false;

  set_both( label, ( dc_node * )parent );
}

dc_element::~dc_element( void ) {
  delete( eval );

  if( nargs ) {
    for( list_item it = args.first() ; it ; it = args.succ( it ) ) {
      delete( args.inf( it ) );
    }
  }
  args.clear();

  if( value ) delete( value );
  if( old_value ) delete( old_value );

  if( clock ) clock->remove( *( ( dc_label * )this ) );

  dc_clock *clk;
  forall( clk, secondary_clocks ) {
    list_item it = clk->secondary_targets.search( this );
    clk->secondary_targets.del_item( it );
  }
  secondary_clocks.clear();
}

bool dc_element::force_update( void ) {
  if( !dynamic || status != Active_s || eval == nil ) return false;

  dc_trace( TRACE_ALL ) {
    cerr << "dc_element::force_update -- updating " << full_type() << "\n";
  }

  if( nargs ) {
    list_item li = args.first();
    while( li ) { /* set value to nil. default will be used */
      dc_arg *f = args.inf( li );
      if( f->set( nil ) ) return true;
      li = args.succ( li );
    }
  }
  
  dc_data *val = eval->evaluate();
  if( val != nil ) {
    dc_trace( TRACE_ALL ) {
      cerr << "dc_element::force_update -- updated " << full_type() << "\n";
    }
    if( set( *val ) ) {
      cerr << "SET OF " << full_type() << " FAILED\n";
      return true;
    }
  } else {
     dc_trace( TRACE_WARNING ) {
       cerr << "dc_element::force_update -- update of " <<full_type()
	    << " failed\n";
     }
#ifdef EXIT_ON_NIL_FUNCTION_RETURN
     exit( 1 );
#endif
    return true;
  }
  return false;
}

bool dc_element::update( void ) {  
  if( !clock ) return false;

  if( clock->t() <= time ) return false;
  
  if( force_update() ) {
    signal_update_error( this );
    return true;
  }
  time = clock->t();
  signal_update( this );

  return false;
}

bool dc_element::refresh( void ) {  
  //cerr << "REFRESH " << full_type() << "\n";

  if( !dynamic || status != Active_s ) return false;

  if( eval == nil ) { /* simplified function */
    return false;
  }

  dc_trace( TRACE_ALL ) {
    cerr << "dc_element::refresh -- refreshing " << full_type() << "\n";
  }

  if( nargs ) {
    list_item li = args.first();
    while( li ) { /* set value to nil. default will be used */
      dc_arg *f = args.inf( li );
      if( f->set( nil ) ) return true;
      li = args.succ( li );
    }
  }
  
  dc_data *val = eval->evaluate();

  if( val != nil ) {

    //    if( check_monitored( this, "refresh" ) ) {
    //      cerr << *val << "\n";
    //    }

    dc_trace( TRACE_ALL ) {
      cerr << "dc_element::refresh -- refreshed " << full_type() << "\n";
    }
    /*    if( value ) delete( value );
    value = val;*/
    set( *val );
  } else {
    dc_trace( TRACE_WARNING ) {
      cerr << "dc_element::refresh -- refresh of " <<full_type() << " failed\n";
    }
#ifdef EXIT_ON_NIL_FUNCTION_RETURN
    exit( 1 );
#endif
    return true;
  }
  return false;
}

dc_data *dc_element::get( void ) {
  if( update() ) return nil;
  return value;
}

dc_data *dc_element::get_previous( void ) const  { 
  if( clock && clock->t() > time ) {
    return value;
  } else {
    return old_value;
  }
}

bool dc_element::set( dc_data &d ) {
  //  if( status != Active_s ) { return true; }

  if( check_monitored( this, "set" ) ) {
    cerr << d << "\n";
  }

# ifdef FLAG_NANs
  if_trc( ( d.sub_type() == Real_t && 
	   isnan( ( ( dc_real * )&d )->get() ) ), 
	 TRACE_WARNING ) {
    cerr << "dc_element::set -- NaN found in " << full_type() << "\n";
  }
# endif

  if( old_value ) delete( old_value );
  old_value = value;
  value = &d;
  return false;
}

bool dc_element::init( dc_data &d, bool set_back = false ) {
  if( d.has_units() )
    set_default_units( *( ( dc_udata * )&d ), local_label(), get_parent() );
    
  dc_data *temp_d = &d;
  if( d.sub_type() != rtype ) {
    if( !castable( d.sub_type(), rtype ) ) {
      return true;
    }
    temp_d = cast( d, rtype );
  }

  if( check_monitored( this, "init" ) ) {
    cerr << *temp_d << "\n";
  }

  if( initial_value ) delete( initial_value );
  initial_value = temp_d;

  reset();
  if( set_back ) time = -1;
  
  signal_update( this );
  return false;
}

void dc_element::unset_func( void ) {
  if( eval ) delete( eval );
  eval = nil;
  nargs = 0;
  dc_arg *a;
  forall( a, args ) delete( a );
  args.clear();
  dynamic = false;
}

void dc_element::reset( void ) {
  if( get_status() != Active_s && value != nil && old_value != nil ) return;

  time = -1;
  if( initial_value ) {

    if( value ) delete( value );
    value = ( dc_data * )allocate( rtype );
    if( set_data( *value, *initial_value ) ) {
      delete( value ); 
      value = nil;
      //      time = -1;
    }
    
    if( old_value ) delete( old_value );
    old_value = ( dc_data * )allocate( rtype );
    if( set_data( *old_value, *initial_value ) ) {
      delete( old_value ); 
      old_value = nil;
      //      time = -1;
    }
  } else {

    if( value ) {
      delete( value );
      value = nil;
    }
    if( old_value ) {
      delete( old_value );
      old_value = nil;
    }
    //    time = -1;
  }
}

bool dc_element::set_func( dc_func &f ) {
  if( eval ) {
    delete( eval );
    eval = nil;
  }
  dc_data *d = f.simplify();
  if( d ) {
    if_trc( initial_value != nil, TRACE_MANY ) {
      cerr << "dc_element::set_func -- simple function erases initial value " 
	   <<" of " << full_type() << "\n";
    }
    init( *d );
    delete( &f );
    dynamic = false;
  } else {
    eval = &f;
    f.set_owner( this );
    dynamic = true;
    dc_arg *arg;
    forall( arg, args ) {
      if( !arg->has_default ) {
	dynamic = false;
	break;
      }
    }
  }

  // reset();
  return false;
}

dc_data *dc_element::call( const f_list &user_args ) {
  if( !eval ) {
    if( value != nil ) {
      dc_trace( TRACE_MANY ) {
	cerr << "dc_element::call -- call of element " << full_type() << " with nil function. returning value\n";
      }
      return ( dc_data * )value->duplicate( nil );
    } else {
      dc_trace( TRACE_ERROR ) {
	cerr << "dc_element::call -- call of element " << full_type() << " with nil function failed\n";
      }
      return nil;
    }
  }

  list_item li, user_li;
  int n = 1;
  for( li = args.first(), user_li = user_args.first() ; li && user_li ; 
       li = args.succ( li ), user_li = user_args.succ( user_li ), n++ ) {
    if( ( args.inf( li ) )->set( user_args.inf( user_li ) ) ) {
      dc_trace( TRACE_ERROR ) {
	cerr << "dc_element::call -- error in substitution of arg " << n 
	     << "\n";
      }
      return nil;
    }
  }
  while( li ) { /* set value to nil. default will be used */
    dc_arg *f = args.inf( li );
    if( f->set( nil ) ) {
      dc_trace( TRACE_WARNING ) {
	cerr << "dc_element::call -- no value passed to arg " << n << " of "
	     << full_type() << " which has no default value\n";
      }
      return nil;
    }
    li = args.succ( li );
    n++;
  }

  dc_data *val = eval->evaluate();
  if( !val ) {
    /*    dc_trace( TRACE_WARNING ) {
	  cerr << "dc_element::call -- call of " << full_type() << " failed\n";
	  } */
  }
  return val;
}

dc_arg *dc_element::match( cstring test_name ) const {
  list_item it = args.first();
  if( !it ) return nil;
  do {
    dc_arg *arg = args.inf( it );
    if( arg->match( test_name ) ) {
      return arg;
    }
  } while( ( it = args.succ( it ) ) != nil );
  return nil;
}

void dc_element::add_arg( dc_arg &a ) { 
  args.append( &a );
  nargs++; 
  if( !a.has_default() )
    dynamic = false; 
}

int dc_element::arg_rank( cstring test_name ) const {
  int n = 0;
  /* list_item it = args.first();
     if( !it ) return nil;
     do {
     dc_arg *arg = args.inf( it );
     if( arg->match( test_name ) ) {
     return n;
     }
     n++;
     } while( ( it = args.succ( it ) ) != nil ); */

  dc_arg *arg;
  forall( arg, args ) {
    if( arg->match( test_name ) ) return n;
    n++;
  }

  return -1;
}

bool dc_element::is_arg( cstring test_name ) const {
  list_item it = args.first();
  if( !it ) return nil;
  do {
    dc_arg *arg = args.inf( it );
    if( arg->match( test_name ) ) {
      return true;
    }
  } while( ( it = args.succ( it ) ) != nil );
  return false;
}

dc_type dc_element::get_arg_type( int rank ) const {
  if( rank >= nargs || rank < 0 ) return Undef_t;
  list_item li;
  if( rank < ( nargs >> 1 ) ) { /* scan from head */
    li = args.first();
    for( ; rank > 0 ; rank -- ) {
      li = args.succ( li );
    }
  } else { /* scan from tail */
    li = args.last();
    for( ; rank < nargs - 1 ; rank ++ ) {
      li = args.pred( li );
    }
  }
  return args[li]->get_rtype();
}

ostream &dc_element::display_fn( ostream &stream = cout ) const {
  if( eval ) {
    stream << *eval;
  } else {
    stream << "<nil>";
  }
  return stream;
}

bool dc_element::remove_deps( const int n, const tag tags[] ) {
  bool success = false;
  if( eval ) success = eval->remove_deps( n, tags );
  if( nargs ) {
    dc_arg *f;
    forall( f, args ) {
      success |= f->remove_deps( n, tags );
    }
  }
  return success;
}

int dc_element::rehash( dep_list &dl ) {
  /* if parent component is dormant then do not rehash */
  if( is_dormant() ) return 0;

  reset();
  int nerrors = 0;
  if( eval ) {
    nerrors = eval->rehash( dl, get_tag() );

    if( nerrors ) {
      dc_trace( TRACE_ERROR ) {
	cerr << "dc_element::rehash -- error rehashing eval fn for " 
	     << full_type() << "\n";
      }
      return nerrors;
    }

    /* check return type of function */
    dc_type func_t = eval->get_rtype();
    if( func_t != rtype ) {
      if( castable( func_t, rtype ) ) {
	eval = new dc_fcast( *eval, rtype );
      } else {
	nerrors += 1;
	dc_trace( TRACE_ERROR ) {
	  cerr << "dc_element::rehash -- rehash failed.  cannot cast "
	       << dc_type_string[func_t] << " ( " << *eval << " ) to " 
	       << dc_type_string[rtype] << " for " << full_type() << "\n";
	}
      }
    }
  }

  /* rehash clock link */
  if( clock_hash_lbl.length() ) {
    dc_clock *c = ( dc_clock * )t_search( clock_hash_lbl, Clock_t, 
					  clock_hash_origin );
    if( c ) c->add( *this );
    else {
      if( clock ) clock->remove( *this );
      nerrors += 1;
    }
  }

  return nerrors;
}

void dc_element::set_frozen( bool set, tag inheritance_src ) {
  dc_status old_status = status;

  if( set ) { /* FREEZE */
    switch( status ) {
    case Active_s :
      status = Frozen_s;
      frozen_inh = inheritance_src;
      break;
    case Dormant_s :
      status = Dormant_Frozen_s;
      frozen_inh = inheritance_src;
      break;
    default : 
      if( frozen_inh != tag_error )
	frozen_inh = inheritance_src;
      return;
    }
  } else { /* UNFREEZE */
    if( inheritance_src == tag_error || frozen_inh == inheritance_src ) {
      switch( status ) {
      case Frozen_s :
	status = Active_s;
	break;
      case Dormant_Frozen_s :
	status = Dormant_s;
	break;
      default : 
	return;
      }
    }
  }
  signal_status_change( this, old_status );
}

void dc_element::set_dormant( bool set, bool inherited ) {
  dc_status old_status = status;

  if( set ) { /* MAKE DORMANT */
    switch( status ) {
    case Active_s :
      status = Dormant_s;
      break;
    case Frozen_s :
      status = Dormant_Frozen_s;
      break;
    default : 
      if( !inherited ) 
	dormant_inh = false;
      return;
    }
    dormant_inh = inherited;
  } else { /* MAKE NON_DORMANT */
    /* only make non-dormant if the non-dormant status is !inherited or the
       dormant status was inherited */
    if( dormant_inh || !inherited ) {
      switch( status ) {
      case Dormant_s :
	status = Active_s;
	break;
      case Dormant_Frozen_s :
	status = Frozen_s;
	break;
      default : 
	return;
      }
    }
  }
  signal_status_change( this, old_status );
}

dc_label *dc_element::duplicate( dc_node *parent ) const {
  dc_element *dupe = new dc_element();
  if( !is_temporary() ) {
    if( dupe->set_both( local_label(), parent ) ) {
      delete( dupe );
      return nil;
    }
  }
  dupe->set_visible( is_visible() );

  dupe->set_type( get_rtype() );

  
  dupe->set_transparent( transparent );
  
  if( initial_value != nil ) {
    dc_data *dupe_val = ( dc_data * )allocate( initial_value->sub_type() );
    set_data( *( dupe_val ), *initial_value );
    dupe->init( *dupe_val );
  }

  if( args.length() ) {
    dc_arg *arg;
    forall( arg, args ) {
      dc_arg *dupe_arg = ( dc_arg * )arg->duplicate( dupe, nil );
      if( dupe_arg == nil ) {
	dc_trace( TRACE_ERROR ) {
	  cerr << "dc_element::duplicate -- in duplicate of " << full_type() 
	       << " failed to copy arg ( " << *arg << " )\n";
	}
	delete( dupe );
	return nil;
      }
      dupe->add_arg( *dupe_arg );
    }
  }

  if( eval ) {
    dc_func *dupe_fn = eval->duplicate( dupe, &( dupe->args ) );
    if( dupe_fn ) {
      dupe->eval = dupe_fn;
      dupe->eval->set_owner( dupe );
    } else {
      dc_trace( TRACE_ERROR ) {
	cerr << "dc_element::duplicate -- failed to duplicate eval fn of "
	     << full_type() << " for copy to " << dupe->full_type() << "\n";
      }
      delete( dupe );
      return nil;
    }
  }
  if( clock_hash_lbl.length() ) {
    dupe->clock_hash_lbl = clock_hash_lbl;
    dupe->clock_hash_origin = parent;
    root.iter_counter->add_secondary( *dupe );
  } else {
    root.iter_counter->add( *dupe );
  }
  
  return ( dc_label * )dupe;
}
