namespace t0c_cont {

  void
  create_observations_when( const When& w, When& w_p1, string mod, int (*f)( int lit ), int sign = 0 )
  {
    string keep = mod + "C -> " + mod + "L & -" + mod + "-L     (" + mod + "_0)"; 
    w_p1.comment = keep;
    
    for( std::set<int>::const_iterator ai = w.prec.begin(); ai != w.prec.end(); ++ai )
      w_p1.prec.insert(f(*ai));   // Mod C_i
    
    for( std::set<int>::const_iterator ai = w.eff.begin(); ai != w.eff.end(); ++ai )
      {
	if( sign >= 0 )
	  {
	    if( f == &k ) // OJO: ¿ramifica KL -> KL/t? no tiene efecto
	      assert_lit( *ai, w_p1.eff, false, true, false );
	    else
	      w_p1.eff.insert(f(*ai));   // Mod L_i
	    
	  }
	if( sign <= 0 )
	  {
	    if( f == &k ) // OJO: ¿ramifica KL -> KL/t? no tiene efecto
	      assert_lit( -*ai, w_p1.eff, false, true, false );
	    else
	      w_p1.eff.insert(f(-*ai)); // Mod -L_i
	  }
      }


  }

  void 
  create_observations( string maybe, int (*m)( int lit ), string know, int (*k)( int lit ) )
  {
    if( !use_contingent_obs_nondeter &&
	!use_contingent_obs_determ )
      return;

    // For propagate: KL -> KL/tag
    const bool do_propagation = true;
    // For each observation, create a new one
    observations_p.resize( observations.size() );
    for( size_t i = 1; i < observations.size(); i++ )
      {
	Act& a = observations[i];
	Act& a_p = observations_p[i];
	a_p.index = a.index;
	a_p.name = a.name;
	a_p.is_observation = true;
	assert( a.is_observation );
      }

    for( size_t i = 1; i < observations.size(); i++ )
      {
	Act& a = observations[i];
	Act& a_p = observations_p[i];
	
	for( std::set<int>::const_iterator ai = a.prec.begin(); ai != a.prec.end(); ++ai )
	  if( use_k_preconds )
	    a_p.prec.insert(k(*ai));
	  else if ( use_contingent_m )
	    a_p.prec.insert(m(*ai));

	if( use_contingent_obs_determ )
	  {
	    for( std::set<int>::const_iterator ai = a.eff.begin(); ai != a.eff.end(); ++ai )
	      {
		When w;
		w.comment = "-K x & -K -x =>  Mx & M-x & Ox";
		w.prec.insert(-k(*ai));
		w.prec.insert(-k(-*ai));
		w.eff.insert(m(*ai));
		w.eff.insert(m(-*ai));
		if( use_contingent_k0_prop &&
		    static_atoms.count( *ai) > 0
		   || static_atoms.count( - *ai ) > 0 )
		  {
		    w.eff.insert(m0(*ai));
		    w.eff.insert(m0(-*ai));
		  }
		if( use_obs_variables ) 
		  w.eff.insert(obs(*ai));
		a_p.addWhen(w);
	      }
	  }

	if( use_contingent_obs_nondeter )
	  {
	    if( verbose > LEVEL1 )
	      cout << "HLP creando... obs nonder" << endl;
	    
	    set<int> new_eff1;
	    set<int> new_eff2;
	    Conds new_conds1;
	    Conds new_conds2;
	    if(k)
	      {
		for( std::set<int>::const_iterator ai = a.eff.begin(); ai != a.eff.end(); ++ai )
		  {
		    assert_lit( *ai, new_eff1, false, do_propagation, false );
		    assert_lit( -*ai, new_eff2, false, do_propagation, false );
		  }
	      }
	    
	    for( std::vector<When>::const_iterator wi = a.conds.begin(); wi != a.conds.end(); ++wi )
	      {
		const When& w = *wi;
		
		// Maybe C -> Maybe L & -Maybe -L
		When w_p1;
		create_observations_when( w, w_p1, maybe, m );
		a_p.addWhen(w_p1);
		
		if(k)
		  {
		    // Ojo: no parecen estar haciendo ramificaciones!!
		    // usar assert_lit
		    
		    // Know C -> Know L & -Know -L
		    When wb;
		    create_observations_when( w, wb, know, k, 1 );
		    new_conds1.addWhen(wb);
		    When wb2;
		    create_observations_when( w, wb2, know, k, -1 );
		    new_conds2.addWhen(wb2);
		  }
	      }
	    a_p.addBranch(new_eff1, new_conds1);
	    a_p.addBranch(new_eff2, new_conds2);
	  }
      }
  }

  void create_observations_prog( )
  {
    assert( 0 != 0 );
    //create_observations( "k", &k );
  }

  void create_observations_heur( )
  {
    create_observations( "m", &m, "k", &k  );
  }


  void
  create_assumptions()
  {
    string name = "ASSUMING_";
    assert( use_assumptions_on_tags );

    if( verbose > LEVEL1 )
      cout << "AA  ASSUMPTIONS on all tags " << t0c::all_tags.size() << endl;
    FOR( set<int>::const_iterator, tag, t0c::all_tags )
      {
	cout << "assuming_"+atoms[abs(*tag)] << "(" << *tag << ") ";
	set<int> prec, eff;
	Act a_p;

	a_p.ground = 0;
	a_p.index = actions_p.size();
	a_p.name = string(name+atoms[abs(*tag)]);
	a_p.is_observation = false;
	a_p.cost = ACTION_COST*t0c::all_tags.size();

	a_p.prec.insert(-k(*tag));
	a_p.prec.insert(-k(-*tag));
	a_p.eff.insert( k(-*tag) );
	a_p.eff.insert( -k(*tag) );

	actions_p.push_back(a_p);
      }
    cout << endl;
  }

  void
  do_merge_imply( int l, const set<int>& tag, 
		  int (*f_l)(int), int (*f_tag)(int), bool is_k, 
		  bool do_from_knl, bool do_from_tag, 
		  int extra_cond = 0 )
  {
    if( !do_from_knl && !do_from_tag )
      return;
    int t = *tag.begin();
    static map<string,int> merge_done;
		    
    string name = "closure_merge_imply_";
    if(l < 0 )
      name.append("-");
    name += atoms[abs(l)];
    name += "__";
    if(t < 0 )
      name.append("-");
    name += atoms[abs(t)];

    map<string,int>::iterator it = merge_done.find(name);
    Act* a_p = 0;
    if( it == merge_done.end() )
      {
	a_p = new Act();
	
	a_p->ground = 0;
	a_p->index = actions_p.size();
	a_p->name = string(name);
	merge_done[name] = a_p->index;

	if (use_no_cost_closures)
	  a_p->cost = 0.0;
	else
	  a_p->cost = CLOSURE_COST;

	// l/tag
	int p = t0c::tagged_t0c( l, tag );
	assert( static_cast<unsigned int>(p) < atoms_p.size() );
	a_p->prec.insert( p );
	atoms_p_not_todel.insert( p );
      }
    else
      {
	a_p = &actions_p[it->second];
      }

    if( do_from_tag )
      { // K_0 tag -> Kl
	When w_p;
	w_p.comment = "merge ktag";
	w_p.prec.insert( f_tag(t) );
	if( extra_cond )
	  w_p.prec.insert( extra_cond );
	assert( static_cast<unsigned int>(f_tag(t)) < atoms_p.size() );
	w_p.eff.insert( f_l(l) );
	if( is_k ) // OJO: viola encapsulamiento. Esto asume que f_l() = k()
	  // OJO HLP: esta ramificacion puede ser incompleta. Usar true?!?!?!?!?
	  //assert_lit( l, w_p.eff, false ); 
	  assert_lit( l, w_p.eff, false, use_full_propagation, false ); 
	w_p.eff.erase( t0c::tagged_t0c( l, tag ) );
	a_p->addWhen(w_p);
      }
    if( do_from_knl )
      { // K-l -> K_0 -tag
	When w_p;
	w_p.comment = "merge k0-l";
	w_p.prec.insert( f_l(-l) );
	if( extra_cond )
	  w_p.prec.insert( extra_cond );
	w_p.eff.insert( f_tag(-t) );
	// If static, k0 -> k, m0 -> m
	if( is_k && 
	    use_contingent_k0_prop &&
	    static_atoms.count( abs(t) ) ) // OJO!!! viola encapsulamiento!!!!!
	  w_p.eff.insert( m0(-t) );
	a_p->addWhen(w_p);
      }
    // Deshabilitada, parece que casi nunca aplica
    if( false && t < 0 &&
	static_atoms.count( abs(t) ) > 0)
      {
	When w_p;
	w_p.comment = "merge k0-l-oneof";
	w_p.prec.insert( f_l(-l) );
	set<int> res;
	t0c::oneof_with( -t, res );
	// Ojo, borrar ahora -t de res
	// cambio oneof_with
	FOR( set<int>::iterator, ti, res )
	  {
	    w_p.eff.insert( f_tag(-*ti) );
	    // If static, k0 -> k, m0 -> m
	    if( static_atoms.count( abs(t) ) )
	      w_p.eff.insert( f_l(-*ti) );
	  }
	a_p->addWhen(w_p);
	
      }
    if( it == merge_done.end() )
      {
	actions_p.push_back(*a_p);
	delete a_p;
      }
  }

  void
  merge_imply_l_tag( t0c::lit2tags_t& tags, int (*f_l)(int), int (*f_tag)(int), bool is_k, 
		     bool do_from_knl, bool do_from_tag, int extra_cond = 0 )
  {
    if( !use_contingent ) return;
    FOR( t0c::lit2tags_t::iterator, it, tags )
      {
	int l = it->first;
	set<set<int> >& ts = it->second;
	
	// From l/tag into set of iterator processed
	FOR( set<set<int> >::iterator, tagp, ts )
	  {
	    if( tagp->size() > 1 ) continue;
	    do_merge_imply( l, *tagp, f_l, f_tag, is_k, do_from_knl, do_from_tag, extra_cond );
	  }
      }
  }  
  
  /* 
     Create map from merge name to action_index.
     If already created, just add rules
   */
  void
  merge_imply_l_tags( )
  {
    if( !use_contingent ) return;
    int active = 0;
    if( use_contingent_active_pred )
      active = k(active_pred_n);
    t0c_cont::merge_imply_l_tag( t0c::tags, &k, &k0, true, 
				 use_contingent_knl_imply, use_contingent_kt_imply, active );
    if( use_contingent_heuristic && use_contingent_m )
      {
	t0c_cont::merge_imply_l_tag( t0c::tags, &m, &m0, false, 
				     use_contingent_mnl_imply && !use_obs_variables, 
				     use_contingent_mt_imply );
	if( use_obs_variables )
	  {
	    FOR( t0c::lit2tags_t::iterator, it, t0c::tags )
	      {
		int l = it->first;
		if( !is_obs(l) ) continue;
		set<set<int> >& ts = it->second;
		
		// From l/tag into set of iterator processed
		FOR( set<set<int> >::iterator, tagp, ts )
		  {
		    if( tagp->size() > 1 ) continue;
		    do_merge_imply( l, *tagp, &obs, &m0, false, true, false );
		  }
	      }
	  } // aqui
      }
  }
  
  void
  create_one_action( string name, set<int>& prec, set<int>& eff )
  {
    Act a_p;
    a_p.ground = 0;
    a_p.index = actions_p.size();
    
    a_p.name = name;
    if (use_no_cost_closures)
      a_p.cost = 0.0;
    else
      a_p.cost = CLOSURE_COST;
	    
    a_p.prec = prec;
    a_p.eff = eff;
    actions_p.push_back(a_p);
  }

  void
  create_flag_actions()
  {
    assert( use_contingent_active_pred );

    {
      set<int> prec, eff;
      prec.insert( k(all_end_pred_n) );
      eff.insert( k(active_pred_n) );
      eff.insert( -k(-active_pred_n) );
      eff.insert( -t0_fail_pred_n );
      create_one_action( "HABILCL", prec, eff );
    }
    {
      set<int> prec, eff;
      prec.insert( k(active_pred_n) );
      eff.insert( k(-active_pred_n) );
      eff.insert( -k(active_pred_n) );
      create_one_action( "UNHABIL", prec, eff );
    }
    {
      set<int> prec, eff;
      FOR( set<int>::iterator, gl, goal_literals )
	prec.insert( k(*gl) );
      eff.insert( k(all_end_pred_n) );
      create_one_action( "COLLECT_GOAL", prec, eff );
    }
  }


  // Copy actions to conditions
  void prepare_action( Act& a )
  {
//     if( !use_contingent || !use_contingent_heuristic ) return;
    if(!(use_contingent||observation_detected)) return;
    if( only_check_new_width ) return;
    if( a.prec.empty() ) return;
    When w;
    FOR( set<int>::iterator, it, a.eff )
      w.eff.insert( *it );
    a.eff.clear();
    if( !w.eff.empty() )
      a.addWhen(w);
    FOR( vector<When>::iterator, wi, a.conds )
      wi->prec.insert( a.prec.begin(), a.prec.end() );
    if( use_contingent_obs_determ && use_contingent_m ) 
      ; // Keep preconds
    else
      ;
      //a.prec.clear(); HLP
    //return;
    vector<size_t> index_to_del;
    Conds new_conds;
    FOR_0( wi, a.conds )
      {
	When& w = a.conds[wi];
	// OJO: For having the previous translation...
	bool addit = true;
	FOR( set<int>::iterator, p, w.prec )
	  if( w.prec.count( -*p ) > 0 )
	    {
	      addit = false;
	      break;
	    }
	if( addit )
	  new_conds.addWhen(w);
      }
    a.conds = new_conds;    
  }

  set<string> compiled_acts;
  size_t action_comp_mutex()
  { 
    cerr << "Don't use action compilation" << endl;
    assert( 0 != 0 );
    vector<pair<size_t, When> > add_them;
    size_t nwhens = 0;
    const bool debugit = verbose > LEVEL1;

    registerEntry( "action_comp_mutex()" );
    FOR_1( i, actions )
      {
	Act& a = actions[i];
	if(debugit)
	  cout << "considering action: " << a.name << endl;
	
	// Collect candidates z
	map<int,set<int> > static_cause_z;
	set<int> doesnt_has_static_hitting_cause;

	FOR( vector<When>::iterator, w, a.conds )
	  {
	    if(!w->used) continue;
	    if(!set_consistent(w->prec)) continue;
	    if(debugit)
	      {
		cout << "considering cond-effect ";
		w->dump(cout);
	      }
	    FOR( set<int>::iterator, z, w->eff )
	      {
		// See it has an static cause
		size_t n_static_cause = 0;
		FOR( set<int>::iterator, c, w->prec )
		  n_static_cause += static_oneof_atoms.count(*c);
		if( n_static_cause > 1 )
		  cout << "Warning: hitting set not properly implemented" << endl
		       << "but needed to compile action under uncertainty." << endl;
		if( n_static_cause == 0 )
		  {
		    doesnt_has_static_hitting_cause.insert( *z );	      
		    if(debugit)
		      cout << "atom " << *z << " doesnt has an static cause" << endl;
		  }
		else
		  if(debugit)
		    cout << "atom " << *z << " has an static cause" << endl;

		// Update cases, even when in some causes they wont be used
		FOR( set<int>::iterator, p, w->prec )
		  if( *p != -*z && 
		      static_oneof_atoms.count(*p) > 0 )
		    {
		      static_cause_z[*z].insert(*p);
		      if(debugit)
			cout << "atom " << *z << " has " << *p << " as static cause" << endl;
		    }
	      }
	  }
	// For each candidate...
	for( map<int, set<int> >::iterator it = static_cause_z.begin();
	     it != static_cause_z.end(); ++it )
	  // All it causes contain an static hitting literal
	  if( doesnt_has_static_hitting_cause.count( it->first ) == 0 )
	    {
	      int z = it->first;
	      set<int>& cause = it->second;
	      assert( !cause.empty () );
	      if(debugit)
		{
		  cout << "Considering atom " << z << " that this causes: ";
		  t0c::pset( cause );
		}
	      
	      // check that all the causes belong to one "oneof"
	      // By now, assuming no overlap between "oneof"s at Init
	      // It is sound anyway...
	      set<int> myoneof;
	      int xi = *(cause.begin());
	      // Ojo: deberian ser oneof relevantes... pero por ahora son todos
	      // Aun no se ha calculado los oneof relevantes...
	      t0c::oneof_with( xi, myoneof );
	      if(debugit)
		{
		  cout << "result of oneof_with: ";
		  t0c::pset( myoneof );
		}
	      if( myoneof.empty() )
		continue;
	      myoneof.insert(xi);
	      if( !includes( myoneof.begin(), myoneof.end(),
			     cause.begin(), cause.end() ) )
		continue;
	      set<int> not_used;
	      set_difference( myoneof.begin(), myoneof.end(),
			      cause.begin(), cause.end(),
			      inserter(not_used, not_used.begin()));
	      if(debugit)
		{
		  cout << "For atom " << z << " has rules for this cases: ";
		  t0c::pset( not_used );
		}
	      FOR( set<int>::iterator, x, not_used )
		{
		  When w;
		  w.father = a.index;
		  w.comment = "Action-comp-mutex";
		  w.prec.insert(-z);
		  w.prec.insert(*x);
		  w.eff.insert(-z);

		  add_them.push_back(make_pair(w.father, w ) );

		  if(debugit)
		    {
		      cout << "Action-comp-mutex for action " << a.name
			   << ": adding cond-effect " << endl;
		      w.dump(cout);
		    }			
		}
	    }
      }

    if(true)
    for( vector<pair<size_t, When> >::iterator it = add_them.begin();
	 it != add_them.end(); ++it )
      {
	Act& a = actions[it->first];
	it->second.index = whens.size();
	a.addWhen( it->second );
	whens.push_back( &(a.conds.back()) );
	++nwhens;
	compiled_acts.insert(a.name);
      }

    registerExit();
    return nwhens;
  }

  bool
  clean_act( When& w, string name )
  {
    const bool debugit = false;
    //return true;
    if( !use_contingent )
      return true;
    if( compiled_acts.count(name) == 0 )
      return true;

    // If used without action compilation
    // need to deal with inv_tags_t0c and copied SL/tag atoms
    assert(0!=0);

    set<int> atoms_to_del;
    FOR( set<int>::iterator, e, w.eff )
      {
	t0c::tag_t &l_tag = t0c::inv_tags_t0c[abs(*e)];
	set<int>& tag = l_tag.second;
	if( tag.size() != 1 )
	  continue;
	int xi = *(tag.begin());
	if( xi < 0 ) continue;
	set<int> myoneof;
	t0c::oneof_with( xi, myoneof );
	myoneof.erase(xi);
	FOR( set<int>::iterator, xj, myoneof )
	  {
// 	    if( w.prec.count(k(*xj)) > 0 ||
// 		w.prec.count(-k(-*xj)) > 0 )
	    if( w.prec.count(k(*xj)) > 0 )
	      {
		if( debugit )
		  {
		    cout << "HLP: deleting 1 when : ";
		    w.dumpPDDL( cout, name_l, "", false );
		  }
		return false;
	      }
	    if( w.prec.count(-k(-*xj)) > 0 )
	      {
		if( debugit )
		  {
		    cout << "HLP: deleting 2 when : ";
		    w.dumpPDDL( cout, name_l, "", false );
		  }
		return false;
	      }
	    if( w.prec.count(k(-*xj)) > 0 )
	      {
		if( debugit )
		  {
		    cout << "HLP: deleting 3 atom " 
			 << name_l(k(-*xj)) << " from when : ";
		    w.dumpPDDL( cout, name_l, "", false );
		  }
		atoms_to_del.insert(k(-*xj));
	      }
	    if( w.prec.count(-k(*xj)) > 0 )
	      {
		if( debugit )
		  {
		    cout << "HLP: deleting 4 atom " 
			 << name_l(-k(*xj)) << " from when : ";
		    w.dumpPDDL( cout, name_l, "", false );
		  }
		atoms_to_del.insert(-k(*xj));
	      }
	  }
      }
    
    FOR( set<int>::iterator, a, atoms_to_del )
      w.prec.erase(*a);
    return true;
  }

}
