// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: successor_generator.cpp 815 2011-09-10 12:57:27Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "interference.h"
#include "successor_generator.h"
#include "transition.h"

#include "common/option.h"

#include "system/effect.h"
#include "system/guard.h"
#include "system/invariant.h"
#include "system/location.h"
#include "system/process.h"
#include "system/state.h"
#include "system/system.h"
#include "system/task.h"

#include <cassert>
#include <ext/slist>

using namespace std;
using __gnu_cxx::slist;

SuccessorGenerator::SuccessorGenerator(const Task* task, const Options* opts, int32_t indep_size) : 
    system(task->system), 
    opts(opts),
    indep_size(indep_size),
    depth(task->system->builder->getField("depth")),
    interference_level(task->system->builder->getField("interference level"))
{
    if (opts->ce) {
	filter = new InterferenceFilter(task, opts);
    }
}

static inline void applyEdge(const Edge* edge, State* state) {
    state->proc(edge->getProcess()->id) = edge->dst->idInProcess;
    edge->effect->apply(state);
}

static inline bool applyDiscreteTransition(const Transition* trans, State* next) {
    // cut zone with guard
    
    if (!trans->edge1->guard->constrain(next)) {
	return false;
    }
    if (trans->edge2 && !trans->edge2->guard->constrain(next)) {
	return false;	    
    }
    
    // apply resets and assignments
    applyEdge(trans->edge1, next);
    if (trans->edge2) {
	applyEdge(trans->edge2, next);
    }
    return true;
}

void SuccessorGenerator::generateSuccessors(const State* state, vector<State*>& succs) {
    static vector<const Transition*> trans;
    trans.clear();    
    generateApplicableTransitions(state, trans);    
    for (uint32_t i = 0; i < trans.size(); i++) {

	State* next = state->clone();
	if (applyDiscreteTransition(trans[i], next) && delay(next)) {
	    next->reachedby = trans[i];
	    // TODO: remove the ifs and execute them after the call to
	    // generateSuccessors
	    if (depth != -1) {
		next->extra(depth)++;
		assert(next->extra(depth) == state->extra(depth) +1);
	    }
	    if (interference_level != -1) {
		next->extra(interference_level) = -1;
	    }
	    succs.push_back(next);
	} else {
	    State::destroy(next);
	}
    }

    // TODO: also ugly
    if (opts->ce) {
	filter->label(succs);
    }
}

bool SuccessorGenerator::delay(State* state) const {
    if (!system->isCommitted(state)) {
	state->zone().up();
    }    

    for (uint32_t i = 0; i < system->procs.size(); i++) {
	const Process* proc = system->procs[i];
	if (!proc->locs[state->proc(i)]->inv->constrain(state)) {
	    return false;
	}
    }
    return true;
}

void SuccessorGenerator::generateApplicableTransitions(const State* state, vector<const Transition*>& succs) const {
    typedef vector<const Edge*> list_t;
    static const uint32_t nrChans = system->getNrChannels();
    static vector<list_t> bang(nrChans, list_t());
    static vector<list_t> que(nrChans, list_t());
    const TransitionBuilder& tb = TransitionBuilder::builder(system);
    
    for (uint32_t i = 0; i < nrChans; i++) {
	bang[i].clear();
	que[i].clear();
    } 

    bool isCommitted = system->isCommitted(state);
    
    for (uint32_t i = 0; i < system->procs.size(); i++) {
	const Process* proc = system->procs[i];
	const Location* loc = proc->locs[state->proc(i)];
	for (uint32_t e = 0; e < loc->outgoing.size(); e++) {
	    const Edge* edge = loc->outgoing[e];
	    vector<Edge*> edges;
	    if (edge->guard->isSatBy(state)) { 
		switch (edge->getType()) 
		{
		case Edge::TAU:
		    if (isCommitted == loc->commit) {
			assert(loc == edge->src);
			succs.push_back(tb.newTransition(edge));
		    }
		    break;
		case Edge::BANG:
		    bang[edge->getAction()->id].push_back(edge);
		    break;
		case Edge::QUE:
		    que[edge->getAction()->id].push_back(edge);
		    break;
		default:
		    assert(false);
		}
 	    }
	}
    }
    // sync transitions 
    assert(bang.size() == nrChans && que.size() == nrChans); 
    for (uint32_t i = 0; i < nrChans; i++) {
	if (!bang[i].empty() && !que[i].empty()) {
	    for (list_t::iterator b = bang[i].begin(); b != bang[i].end(); ++b) {
		for (list_t::iterator q = que[i].begin(); q != que[i].end(); ++q) {
		    assert(*b && *q);
		    // no self sync
		    if (( (*b)->getProcess()->id != (*q)->getProcess()->id) &&
			(isCommitted == (*b)->src->commit || isCommitted ==(*q)->src->commit)) {
			succs.push_back(tb.newTransition(*b, *q));
		    }
		}
	    } 
	}
    }
} 
