/* root.cc */

#include "root.h"
#include <time.h>
#include "signal_change.h"
#include "id_lookup.h"
#include "link.h"
#include "cor_syn_iface.h"
#include "simple3d.h"
#include "component.h"
#include "agent_circuit.h"
#include "user_types.h"

node nil_node;

void dc_root::alloc( void ) {
  iter_counter = new dc_clock( iter_counter_lbl, nil, 1 );
  
  default_list = new dc_node( "~default" );
  default_list->set_visible( false );

  invalids = new dc_element( "invalids", nil );
  invalids->set_type( Int_t );
  invalids->init( (* new dc_real( 0 ) ) );

  //#ifdef COMPILE_CIRCUIT_AGENT
  //  circuit_agent.set_utype( define_u_type( circuit_agent.agent_circuit_name ) );
  //#endifoo
}

void dc_root::dealloc( void ) {
  dic_item it;
  forall_items( it, label_list ) {
    dc_label *t = label_list.inf( it );
    delete( t );
  }

  clear_link_graph();

  label_list.clear();
  tag_list.clear();
  mod_list.clear();

  undef_all_u_types();

  iter_counter = nil;
  default_list = nil;
  invalids = nil;
}

dc_root::dc_root( void ) {
  srand48( time( nil ) );

  /* create nil node in link_graph */
  nil_node = link_graph.new_node( ( dc_component * )nil );
  
  alloc();
  ub.load_unit_file( "unittab" );
  
  init_coriolis_agent();
  init_view();

  dc_trace( TRACE_FEW ) {
    cerr << "dc_root::dc_root -- root initialized\n";
  }

  signal_root_open();
}

dc_root::~dc_root( void ) {
  close_coriolis_agent();
  close_view();

  dealloc();

  dc_trace( TRACE_FEW ) {
    cerr << "dc_root::~dc_root -- root closed\n";
  }

  signal_root_death();
}



void dc_root::expand( list<dc_label *> &l ) {
  dic_item it;
  forall_items( it, label_list ) {
    l.append( label_list.inf( it ) );
  }
}

void dc_root::clear( void ) {
  dealloc();

  alloc();

  dc_trace( TRACE_FEW ) {
    cerr << "dc_root::~dc_root -- root cleared\n";
  }

  signal_root_reset();
}

void dc_root::delete_templated( void ) {
  list_item li;
  while( !root.templated_list.empty() ) {
    li = root.templated_list.first();
    delete( root.templated_list.inf( li ) );
  }
}

int dc_root::do_mods( void ) const {
  int nerrors = 0;
  dc_modification *mod;
  forall( mod, mod_list ) {
    nerrors += mod->modify();
  }
  return nerrors;
}

void dc_root::end_advance( void ) {
 int num = 0;
  list_item it = constraint_list.first();
  while( it ) {
    dc_element *e = ( dc_element * )lookup( constraint_list.inf( it ) );
    if( e ) {
      if( e->get_rtype() == Boolean_t ) {
	dc_boolean *b = ( dc_boolean * )( e->get() ); /* calls update */
	dc_boolean *old_b = ( dc_boolean * )( e->get_previous() );
	if( b != nil ) {
	  if( !( b->get() ) ) {
	    num++;
	    dc_trace( TRACE_MANY ) {
	      cerr << "Constraint \"" << e->local_label() 
		   << "\" unsatisfied\n";
	    }
	    if( !old_b || old_b->get() ) {
	      signal_constraint_flagged( e, false );
	    }
	  } else if( old_b && !old_b->get() ) {
	    signal_constraint_flagged( e, true );
	  }
	}
      } else {
	num++;
	signal_constraint_error( e );
      }
      it = constraint_list.succ( it );
    } else {
      list_item temp_it = it;
      it = constraint_list.succ( it );
      constraint_list.del_item( temp_it );
    }
  }
  
  invalids->set( *( new dc_int( num ) ) );
}

dc_root root;

void exec_link( dc_link &l, void * ) {
  l.exec();
}

void list_labels( ostream &stream = cout, bool see_all = false ) {
  stream << "There are " << root.label_list.size() << " root label"
	 << ( ( root.label_list.size() == 1 ) ? "\n" : "s\n" );
  dic_item it;
  forall_items( it, root.label_list ) {
    dc_label *id = root.label_list.inf( it );
    if( id && id->is_visible() || see_all ) {
      stream << *id << "\n";
    }
  }
}

void list_tags( ostream &stream = cout, bool see_all = false ) {
  dic_item it;
  forall_items( it, root.tag_list ) {
    dc_label *id = root.tag_list.inf( it );
    if( id->is_visible() || see_all ) 
      stream << root.tag_list.key( it ) << " : " << id->full_type() << " = " 
	     << id << "\n";
  }
}

dc_default *get_default( cstring label, dc_node *origin ) {
  string default_label = default_prefix + label;
  while( origin != nil ) {
    dc_label *d = origin->lookup_child( default_label, true );
    if( d != nil ) {
      return ( dc_default * )d;
    }
    origin = origin->get_parent();
  }
  dic_item child_it = root.label_list.lookup( default_label );
  if( child_it == nil )
    return nil;
  return ( dc_default * )( root.label_list.inf( child_it ) );
}

dc_element *add_constraint( cstring label, dc_component *parent ) {
  dc_element *e = new dc_element( label, parent );
  e->init( *( new dc_boolean( true ) ) );

  root.constraint_list.append( e->get_tag() );
  root.iter_counter->add( *e );
  return e;
}

dc_element *add_constraint( dc_component *parent ) {
  dc_element *e = new dc_element;  
  tag parent_tag = parent ? parent->get_tag() : tag_error;
  tag e_tag = next_tag();
  if( e->set_both( string( "constraint%d_%d", parent_tag, e_tag ), parent ) ) {
    delete( e );
    return nil;
  }
  e->set_visible( false );
  e->init( *( new dc_boolean( true ) ) );

  root.constraint_list.append( e_tag );
  root.iter_counter->add( *e );
  return e;
}

bool remove_constraint( const tag T ) {
  list_item it = root.constraint_list.search( T );
  if( !it ) return true;
  root.constraint_list.del_item( it );
  return false;
}

int flag_constraints( void ) {
  int num = 0;
  list_item it = root.constraint_list.first();
  while( it ) {
    dc_element *e = ( dc_element * )lookup( root.constraint_list.inf( it ) );
    if( e ) {
      if( e->get_rtype() == Boolean_t ) {
	dc_boolean *b = ( dc_boolean * )( e->get() ); /* calls update */
	dc_boolean *old_b = ( dc_boolean * )( e->get_previous() );
	if( b == nil ) { 
	  num = -1; 
	  signal_constraint_error( e );
	} else {
	  if( !( b->get() ) ) {
	    if( num >= 0 ) num++;
	    dc_trace( TRACE_FEW ) {
	      cerr << "Constraint \"" << e->local_label() 
		   << "\" unsatisfied\n";
	    }
	    if( !old_b || old_b->get() ) {
	      signal_constraint_flagged( e, false );
	    }
	  } else if( old_b && !old_b->get() ) {
	    signal_constraint_flagged( e, true );
	  }
	}
      } else {
	num = -1;
	signal_constraint_error( e );
      }
      it = root.constraint_list.succ( it );
    } else {
      list_item temp_it = it;
      it = root.constraint_list.succ( it );
      root.constraint_list.del_item( temp_it );
    }
  }
  
  return num;
}

void list_constraints( ostream &stream = cout ) {
  list_item it = root.constraint_list.first();
  while( it ) {
    dc_element *e = ( dc_element * )lookup( root.constraint_list.inf( it ) );
    if( e && !( ( ( dc_element * )e )->is_dormant() ) ) {
      stream << *e << "\n";
      it = root.constraint_list.succ( it );
    } else {
      list_item temp_it = it;
      it = root.constraint_list.succ( it );
      root.constraint_list.del_item( temp_it );
    }
  }
}

void list_failed_constraints( ostream &stream = cout ) {
  list_item it = root.constraint_list.first();
  while( it ) {
    dc_element *e = ( dc_element * )lookup( root.constraint_list.inf( it ) );
    if( e && !( ( ( dc_element * )e )->is_dormant() ) ) {
      dc_data *v = ( ( dc_element * )e )->get();
      if( v && v->type() == Boolean_t && !( ( ( dc_boolean * )v )->get() ) ) {
	stream << *e << "\n";
      }
      it = root.constraint_list.succ( it );
    } else {
      list_item temp_it = it;
      it = root.constraint_list.succ( it );
      root.constraint_list.del_item( temp_it );
    }
  }
}

int rehash( void ) {
  dep_list to_remove;
  return rehash( to_remove );
}

void rehash_link( dc_link &l, void *nerrors ) {
  *( ( int * )nerrors ) += l.rehash();
}

int rehash( dep_list &to_remove ) {
  int errors = 0;

  root.delete_templated();
  
  dc_modification *mod; forall( mod, root.mod_list ) mod->set_undone();

  // MAKE THIS LOOP UNTIL LACK OF PROGRESS
  errors += root.do_mods();

  errors += rehash_templates();
  cerr << "TEMPLATES REHASHED\n";

  errors += root.do_mods();


  /* rehash data objects.  must be done before function or element rehashes */
  errors += rehash_pointers();
  cerr << "POINTERS REHASHED\n";

  errors += rehash_sets();
  cerr << "SETS REHASHED\n";

  errors += relink_elrefs();
  cerr << "ELREFS REHASHED\n";

  /* generate a list of all dependencies */
  dep_list deps;
  dic_item it;
  forall_items( it, root.tag_list ) {
    dc_label *id = root.tag_list.inf( it );
    switch( id->sub_type() ) {
    case Element_t : /* rehash evaluation function and args, and reset */
      errors += ( ( dc_element *)id )->rehash( deps );
      break;
    case Component_t :
      errors += ( ( dc_component * )id )->rehash();
      break;
    default :;
    }
  }
  
  /* remove deps */
  if( remove_dependencies( deps, to_remove ) ) {
    dc_trace( TRACE_FEW ) {
      cout << "\n\nrehash -- REMOVING " << to_remove.size() / 2 
	   << " DEPENDENCIES\n";
      list_item it_from = deps.first(), it_to = deps.succ( it_from );
      tag dep_from, dep_to;
      while( it_from ) {
	dep_from = deps.inf( it_from );
	if( ( it_to = deps.succ( it_from ) ) == nil ) {
	  cerr << "rehash -- invalid dep_list\n";
	  exit( 1 );
	}
	dep_to = deps.inf( it_to );
	dc_label *from = lookup( dep_from ),
	  *to = lookup( dep_to );
	if( from && to ) {
	  dc_trace( TRACE_ALL ) {
	    cout << "DEPENDENCY ( " << from->full_type() << " -> "
		 << to->full_type() << " )\n";
	  }
	} else {
	  cout << "INVALID DEP TAGS ( " << dep_from << ", " << dep_to << " )\n";
	}
	it_from = deps.succ( it_to );
      }
    }

    list_item li_pos = to_remove.first();
    while( li_pos ) {
      /* should be sorted */
      tag T = to_remove.inf( li_pos );

      int n = 0;
      list_item li_end = li_pos;
      while( li_end && ( to_remove.inf( li_end ) ) == T ) {
	li_end = to_remove.succ( to_remove.succ( li_end ) ); 
	n++; }

      tag *tags = new tag[n];
      for( int i = 0 ; i < n ; i++ ) {
	li_pos = to_remove.succ( li_pos );
	tags[i] = to_remove.inf( li_pos );
	li_pos = to_remove.succ( li_pos );
      }
    
      dc_label *id = lookup( T );
      if( id && id->type() == Element_t ) {
	if( ( ( dc_element * )id )->remove_deps( n, tags ) ) {
	  dc_trace( TRACE_WARNING ) {
	    cerr << "rehash -- removed deps from " << id->full_type() <<" to\n";
	    for( int i = 0 ; i < n - 1 ; i++ ) {
	      dc_label *id = lookup( tags[i] );
	      if( id ) cerr << "\t" << id->full_type() << ", \n"; 
	      else cerr << "\t<nil tag " << tags[i] << ">, \n";
	    }
	    dc_label *id = lookup( tags[n-1] );
	    if( id ) cerr << "\t" << id->full_type() << "\n"; 
	    else cerr << "\t<nil tag " << tags[n-1] << ">\n";;
	  }
	} else {
	  dc_trace( TRACE_ERROR ) {
	    cerr << "rehash -- failed to remove deps from " << id->full_type() 
		 << "\n";
	  }
	}
      } else {
	dc_trace( TRACE_ERROR ) {
	  if( id ) {
	  cerr << "rehash -- cannot remove dependencies from " 
	       << id->full_type() << " since it is not an element\n";
	  } else {
	    cerr << "rehash -- cannot remove dependencies from tag " << T 
		 << " since it does not exist\n";
	  }
	}
      }
      delete( tags );
    }
  } else 
    dc_trace( TRACE_FEW ) { cerr << "\nNO DEPENDENCIES REMOVED\n"; }

  to_remove.clear();
  deps.clear();

  /* reconnect links */
  int link_errors = 0;
  forall_links( rehash_link, ( void * )&link_errors );
  errors += link_errors;

  if_trc( link_errors != 0, TRACE_ERROR ) {
    cerr << "regash -- " << link_errors << " errors rehashing links\n";
  }
  
  return errors;
}

void reset( void ) {
  dic_item it;
  forall_items( it, root.tag_list ) {
    dc_label *l = root.tag_list.inf( it );
    switch( l->type() ) {
    case Element_t :
      ( ( dc_element * )l )->reset();
      break;
    case Clock_t :
      ( ( dc_clock * )l )->reset();
      break;
    default :;
    }
  }
  dc_trace( TRACE_FEW ) {
    cerr << "ROOT RESET\n";
  }
}
