// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: causalGraph.cpp 823 2011-10-01 14:48:13Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "causalgraph/causalGraph.h"
#include "causalgraph/cg_variable.h"
#include "causalgraph/cg_operator.h"
#include "causalgraph/scc.h"
#include "causalgraph/max_dag.h"
#include "causalgraph/domainTransitionGraph.h"
#include "causalgraph/dtg.h"

#include "system/location.h"
#include "system/assignment.h"
#include "system/process.h"
#include "system/effect.h"
#include "system/edge.h"
#include "system/guard.h"
#include "system/constraint.h"
#include "system/task.h"
#include "system/system.h"
#include "system/target.h"

#include "common/message.h"

#include <iostream>
#include <cassert>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

using namespace std;

namespace cg {

    CausalGraph::CausalGraph(const Task* task) {
	initCGVariables(task);
	initCGOperators(task);
	if (task->target) {
	    initCGGoals(task);
	}
	
	constructGraph();
	compute_sccs();
	compute_topological_pseudo_sort();
	constructDTGs();
    }
    
    void CausalGraph::constructGraph() {
	debug() << "Computing causal graph... ";
	
	foreach (const CGOperator* op, operators) {
	    vector<CGVariable*> source_vars = op->getSourceVariables(variables);
	    vector<CGVariable*> dest_vars = op->getDestinationVariables(variables);
	    
	    // dest_vars are also source vars (if both v and w are
	    // written by an operator, then there is an edge (v,w) in
	    // the graph)
	    for (uint32_t j = 0; j < dest_vars.size(); j++) {
	    	source_vars.push_back(dest_vars[j]);
	    } 
	    
	    foreach (CGVariable* curr_target, dest_vars) { 
		foreach (CGVariable* curr_source, source_vars) {
		    WeightedSuccessors& weighted_succ = weighted_graph[curr_source];
		    
		    if (predecessor_graph.count(curr_target) == 0) {
			predecessor_graph[curr_target] = Predecessors();
		    }		    
		    if (curr_source != curr_target) {	
			// weighted_succ.insert(make_pair(curr_target, 0)).first->second++;
			// predecessor_graph
			
			if (weighted_succ.find(curr_target) != weighted_succ.end()) {
			    weighted_succ[curr_target]++;
			    predecessor_graph[curr_target][curr_source]++;			    
			} else {
			    weighted_succ[curr_target] = 1;
			    predecessor_graph[curr_target][curr_source] = 1;			    
			}
		    }
		}
	    }
	}

	debug() << *this << endl << "done" << endl;
    }

    const CausalGraph::Partition& CausalGraph::compute_sccs() {
	assert(sccs.empty());
	map<CGVariable *, int> variableToIndex;
	for (uint i = 0; i < variables.size(); i++) {
	    variableToIndex[variables[i]] = i;
	}
	
	vector<vector<int> > unweighted_graph;
	unweighted_graph.resize(variables.size());
	for (auto it = weighted_graph.begin(); it != weighted_graph.end(); ++it) {
	    int index = variableToIndex[it->first];
	    vector<int> &succ = unweighted_graph[index];
	    const WeightedSuccessors &weighted_succ = it->second;
	    for (auto it = weighted_succ.begin(); it != weighted_succ.end(); ++it) {
		succ.push_back(variableToIndex[it->first]);
	    }
	}
	
	vector<vector<int> > int_result = SCC(unweighted_graph).get_result();
	
	for (uint i = 0; i < int_result.size(); i++) {
	    vector<CGVariable *> component;
	    for (uint j = 0; j < int_result[i].size(); j++) {
		component.push_back(variables[int_result[i][j]]);
	    }
	    sccs.push_back(component);
	}
	
	debug() << "The causal graph is "
		<<  (sccs.size() == variables.size() ? "" : "not ")
		<< "acyclic." << endl;
	if (sccs.size() != variables.size()) {
	    debug() << "Components: " << endl;
	    for (uint i = 0; i < sccs.size(); i++) {
		for (uint j = 0; j < sccs[i].size(); j++) {
		    debug() << " " << sccs[i][j]->name;
		}
		debug() << endl;
	    }
	}
	return sccs;
    }
    
    // Fast Downwards ordering procedure 
    const CausalGraph::Ordering& CausalGraph::compute_topological_pseudo_sort() {
	map<CGVariable *, int> goal_map;
	for (uint32_t i = 0; i < goals.size(); i++) {
	    goal_map[goals[i].first] = goals[i].second;
	}
	for (uint32_t scc_no = 0; scc_no < sccs.size(); scc_no++) {
	    const vector<CGVariable *> &curr_scc = sccs[scc_no];
	    if (curr_scc.size() > 1) {
		// component needs to be turned into acyclic subgraph  
		
		// Map variables to indices in the strongly connected component.
		map<CGVariable *, int> variableToIndex;
		for (uint32_t i = 0; i < curr_scc.size(); i++) {
		    variableToIndex[curr_scc[i]] = i;
		}
		
		// Compute subgraph induced by curr_scc and convert the successor
		// representation from a map to a vector.
		vector<vector<pair<int, int> > > subgraph;
		for (uint32_t i = 0; i < curr_scc.size(); i++) {
		    // For each variable in component only list edges inside component.
		    const WeightedSuccessors &all_edges = weighted_graph.find(curr_scc[i])->second;
		    vector<pair<int, int> > subgraph_edges;
		    for (auto curr = all_edges.begin(); curr != all_edges.end(); ++curr) {
			CGVariable *target = curr->first;
			int cost = curr->second;
			auto index_it = variableToIndex.find(target);
			if (index_it != variableToIndex.end()) {
			    int new_index = index_it->second;
			    if (goal_map.find(target) != goal_map.end()) {
				subgraph_edges.push_back(make_pair(new_index, 100000 + cost));
			    }
			    subgraph_edges.push_back(make_pair(new_index, cost));
			}
		    }
		    subgraph.push_back(subgraph_edges);
		}
		
		static vector<int> order;
		order.clear();
		MaxDAG(subgraph).get_result(order);
		for (uint32_t i = 0; i < order.size(); i++) {
		    ordering.push_back(curr_scc[order[i]]);	
		}	
	    } else {
		ordering.push_back(curr_scc[0]);
	    }
	}
	 
	// start of test_ordering
	// push variables for automata at the end
	int index = -1;
	for (uint32_t i = 0; i < variables.size(); i++) {
	    if (!variables[i]->is_integer_var()) {
		index = i;
		break;
	    }
	}

	// if there is at least one automaton with more than one
	// location, push the corresponing variables at the end
	if (index != -1) {
	    vector<CGVariable*> test_ordering;
	    for (uint32_t i = 0; i < ordering.size(); i++) {
		for (int j = 0; j < (int)variables.size(); j++) {
		    if (ordering[i]->name == variables[j]->name && j < index)
			test_ordering.push_back(ordering[i]);
		}
	    }
	    for (uint32_t i = 0; i < ordering.size(); i++) {
		for (int j = 0; j < (int)variables.size(); j++) {
		    if (ordering[i]->name == variables[j]->name && j >= index)
			test_ordering.push_back(ordering[i]);
		}
	    }
	  
	    assert(test_ordering.size() == ordering.size());
	  
	    ordering = test_ordering;
	}
        
	for (uint32_t i = 0; i < ordering.size(); i++) {
	    ordering[i]->level = i;
	}	
	return ordering;
    }
    
    ostream& CausalGraph::display(ostream& o) const {
	for (auto source = weighted_graph.begin(); source != weighted_graph.end(); ++source) {
	    o << "dependent on var " << source->first->name << ": " << endl;
	    const WeightedSuccessors &curr = source->second;
	    for (WeightedSuccessors::const_iterator it = curr.begin(); it != curr.end(); ++it) {
		o << "  [" << it->first->name << ", " << it->second << "]" << endl;
	    }
	}
	
	for (auto source = predecessor_graph.begin(); source != predecessor_graph.end(); ++source) {
	    o << "var " << source->first->name << " is dependent of: " << endl;
	    const Predecessors &curr = source->second;
	    for (auto it = curr.begin(); it != curr.end(); ++it) {
		o << "  [" << it->first->name << ", " << it->second << "]" << endl; 
	    }
	}
	return o;
    }

    bool CausalGraph::leftOf(const CGVariable* v1, const CGVariable* v2) const {
	assert(*v1 != *v2);
	foreach (const CGVariable* v, ordering) {
	    if (*v == *v1) {
		return true;
	    } else if (*v == *v2) {
		return false;
	    }
	}
	assert(false);
	return true;
    }

    ostream& CausalGraph::graphviz(ostream& o) const {
	o << "digraph G {" << endl;
	
	for (auto source = weighted_graph.begin(); source != weighted_graph.end(); ++source) {
	    const WeightedSuccessors &curr = source->second;
	    for (auto it = curr.begin(); it != curr.end(); ++it) {		
		o << source->first->name << " -> " << it->first->name << ";" << endl;
	    }
	}
	return o << "}" << endl;
    }
        
    // returns true if variable with name varname is contained in scc
    bool CausalGraph::member(const vector<CGVariable*> scc, const string& varname) const {
	for (uint32_t i = 0; i < scc.size(); i++) {
	    if (scc[i]->name == varname) {
		return true;
	    }
	}
	return false;
    }
    
    void CausalGraph::get_root_variables(vector<CGVariable*>& root_variables) const { 
	for (auto source = predecessor_graph.begin(); source != predecessor_graph.end(); ++source) {
	    
	    CGVariable* var = source->first;
	    const Predecessors &curr = source->second;
	    
	    auto it = curr.begin();
	    if (it == curr.end()) {
		root_variables.push_back(var);
	    }
	}
    }

    // returns the direct successors of var
    void CausalGraph::get_successors(int var, vector<int>& successors) const {    
	for (auto source = weighted_graph.begin(); source != weighted_graph.end(); ++source) {	    
	    if (source->first->level == var) {	
		const WeightedSuccessors &curr = source->second;
		for (auto it = curr.begin(); it != curr.end(); ++it){		    
		    successors.push_back(it->first->level);	  
		}
	    }
	}
    }

    void CausalGraph::constructDTGs() {
	vector<DomainTransitionGraph*> transition_graphs;
	computeDomainTransitionGraphs(transition_graphs);
	
	g_transition_graphs.reserve(transition_graphs.size());
	assert(ordering.size() == transition_graphs.size());
	for (uint32_t i = 0; i < ordering.size(); i++) {
	    DTG* dtg = new DTG(ordering[i]);
	    dtg->construct_transitions(transition_graphs[i], g_transition_graphs, operators);	    
	    g_transition_graphs.push_back(dtg);
	    delete transition_graphs[i];	    
	}
    }
    
    // returns the (cascaded) safe variables
    void CausalGraph::compute_safe_variables(vector<CGVariable*>& safe_variables) {
		    	
	safe_variables.clear();
	vector<CGVariable*> root_variables;
	
	get_root_variables(root_variables);
	
	for (uint32_t i = 0; i < root_variables.size(); i++) {
	    int root_var = root_variables[i]->level;
	    
	    if (g_transition_graphs[root_var]->is_strongly_connected() && check_for_clocks(root_variables[i])) {
		safe_variables.push_back(root_variables[i]);
	    }
	}
    
	// for all successors of variables in safe_variables check 
	// if they build a singleton scc in the cg until fixpoint is reached
	bool changed = true;
	while (changed) {
	    changed = false;
	    vector<CGVariable*> additional_variables;
	    for (uint32_t i = 0; i < safe_variables.size(); i++) {
		vector<int> succs;
		get_successors(safe_variables[i]->level, succs);
	
		bool is_singleton_scc;
		for (uint32_t j = 0; j < succs.size(); j++) {
		    is_singleton_scc = false;
		    for (uint32_t k = 0; k < sccs.size(); k++) {
			if (sccs[k].size() == 1 && sccs[k][0]->level == succs[j]) {
			    is_singleton_scc = true;
			}
		    }
		    CGVariable* succ_var = ordering[succs[j]];
		    if (is_singleton_scc && g_transition_graphs[succs[j]]->is_strongly_connected()) {
			additional_variables.push_back(succ_var);
		    }
		}
	    }
	    for (uint32_t i = 0; i < additional_variables.size(); i++) {
		CGVariable* add_var = additional_variables[i];
		bool found = false;
		for (uint32_t j = 0; j < safe_variables.size(); j++) {
		    if (safe_variables[j]->name == add_var->name) {
			found = true;
		    }
		}
		if (!found && check_for_clocks(add_var)) {
		    safe_variables.push_back(add_var);
		    changed = true;
		}
	    }
	}
    }

    // input: a candidate for an independent variable var
    // output: returns true if there is no operator op such that op reads or writes a clock variable and also writes var
    bool CausalGraph::check_for_clocks(CGVariable* var) const {
	
	bool ok = true;
	
	for (uint32_t i = 0; i < operators.size(); i++) {
	    
	    const Effect* effect = operators[i]->edge1->effect;
	    bool found = false;

	    for (uint32_t j = 0; j < effect->intassigns.size(); j++) {
		if (effect->intassigns[j]->lhs->name == var->name) {
		    found = true;
		}
	    }
	    if (!found) {
		if (operators[i]->edge1->dst->name == var->name) {
		    found = true;
		}
	    }
	    if (found) {
		if (effect->resets.size() > 0)
		    ok = false;
		if (operators[i]->edge1->guard->clockconstraints.size() > 0)
		    ok = false;
		
	    }
	
	    if (!operators[i]->is_tau_operator()) {
		const Effect* effect = operators[i]->edge2->effect;
		bool found = false;
		for (uint32_t j = 0; j < effect->intassigns.size(); j++) {
		    if (effect->intassigns[j]->lhs->name == var->name) {
			found = true;
		    }
		}
		 
		if (!found) {
		    if (operators[i]->edge2->dst->name == var->name) {
			found = true;
		    }
		}
		 
		if (found) {
		    if (effect->resets.size() > 0)
			ok = false;
		    if (operators[i]->edge2->guard->clockconstraints.size() > 0)
			ok = false;
		     
		}
	    }	    
	}
	
	return ok;
		
    }

    void CausalGraph::computeDomainTransitionGraphs(vector<DomainTransitionGraph*>& transition_graphs) {
	assert(transition_graphs.empty());
	for (uint32_t i = 0; i < ordering.size(); i++) {
	    transition_graphs.push_back(new DomainTransitionGraph(ordering[i], variables, idmapper));
	}
	
	for (uint32_t i = 0; i < operators.size(); i++) {
	    CGOperator* op = operators[i];
	    
	    vector<const IntAssignment*> integerassignments;
	    vector<const IntConstraint*> integerconstraints;
	    op->getIntegerAssignments(integerassignments);
	    op->getIntegerConstraints(integerconstraints);
	
	    // process integer assignments
	    for (uint32_t j = 0; j < integerassignments.size(); j++) {
		const IntAssignment* as = integerassignments[j];
		CGVariable* asvar_lhs = variables[as->lhs->id];
		assert (as->lhs->name == asvar_lhs->name);

		vector<pair<CGVariable*, int> > additionalConstraints; 
      	
		// process ValueAssignment
		const ValueAssignment* valass = dynamic_cast<const ValueAssignment*>(as);
		if (valass) {
		    processValueAssignmentDTG(transition_graphs, valass->rhs, asvar_lhs, integerconstraints, op, i, additionalConstraints);
		}
		
		// process VarAssignment
		// For variable assignments v:=w build transitions in DTG(v) for all possible values of w (i.e., lower(w)...upper(w))
		const VarAssignment* varass = dynamic_cast<const VarAssignment*>(as);
		if (varass) {		    
		    CGVariable* asvar_rhs = variables[varass->rhs->id];
		    uint32_t range = asvar_rhs->upper;
		    		 
		    for (uint32_t k = asvar_rhs->lower; k <= range; k++) {
			// for assignment v:=k, we need the additional guard v==k
			additionalConstraints.clear(); 
			pair<CGVariable*, int> constr;
			constr.first = asvar_rhs;
			constr.second = k;
			additionalConstraints.push_back(constr);
			processValueAssignmentDTG(transition_graphs, k, asvar_lhs, integerconstraints, op, i, additionalConstraints);
		    }
		}
	    }
	    processLocationAssignmentDTG(transition_graphs, op, i);
	    
	}
	
	// Remove duplicates and dominated transitions as Fast Downward does
	for (uint32_t i = 0; i < transition_graphs.size(); i++) {
	    transition_graphs[i]->finalize();
	}	
    }

    void CausalGraph::initCGVariables(const Task* task) {
	// FIXME: check if variables are local or global
	// integers
	foreach (const Integer* ivar, task->system->ints) {
	    CGVariable* var = new CGVariable(ivar->name, ivar->lower, ivar->upper, GLOBAL_INTEGER);
	    var->id = variables.size();
	    var->system_id = var->id;	    
	    variables.push_back(var);
	}

	// processes
	int nr_procs = 0; // counter for the number of processes that have at least 2 locations
	const uint32_t nr_ints = task->system->ints.size();
	foreach (const Process* proc, task->system->procs) {
	    int upper = proc->locs.size() - 1;
	    
	    if (upper > 0) {
		CGVariable* var = new CGVariable(proc->name, 0, upper, PROCESS);
		variables.push_back(var);
		idmapper(PROCESS, proc->id) = nr_ints + nr_procs;
		var->id = nr_ints + nr_procs;
		var->system_id = proc->id;
		nr_procs++;
	    } else {
		idmapper(PROCESS, proc->id) = -1;
	    }
	}
    }
    
    void CausalGraph::initCGOperators(const Task* task) {
	// An operator is either an edge (interleaving) or a pair of edges (binary sync)
	vector<Edge*> bangEdges;
	vector<Edge*> queueEdges;
	foreach (Process* proc, task->system->procs) {
	    foreach (Edge* edge, proc->edges) {
		switch(edge->getType()) {
		case Edge::TAU:
		    operators.push_back(new CGOperator(edge, NULL, idmapper));
		    break;
		case Edge::BANG:
		    bangEdges.push_back(edge);
		    break;
		case Edge::QUE:
		    queueEdges.push_back(edge);
		    break;
		default:
		    assert(false);
		    break;
		}
	    }
	}
	
	// process bang and queue edges and construct corresponding sync operator
	for (uint32_t i = 0; i < bangEdges.size(); i++) {
	    Edge* bangEdge = bangEdges[i];
	    
	    for (uint32_t j = 0; j < queueEdges.size(); j++) {
		Edge* queueEdge = queueEdges[j];
		
		if (bangEdge->getAction()->id == queueEdge->getAction()->id && bangEdge->getProcess()->id != queueEdge->getProcess()->id) {
		    CGOperator* sync_op = new CGOperator(bangEdge, queueEdge, idmapper);
		    operators.push_back(sync_op);
		}
	    }
	}
    }

    void CausalGraph::initCGGoals(const Task* task) {

	// goal integer constraints
	for (uint32_t i = 0; i < task->target->getIntConstraints().size(); i++) {
	    const IntConstraint* goal_int = task->target->getIntConstraints()[i];
	    
	    CGVariable* goal_var = variables[goal_int->lhs->id];
	    
	    // HACK! The comparator must not be NEQ (!=)
	    assert(goal_int->comp != IntConstraint::NEQ);
	    uint32_t goal_value = goal_int->rhs;
	    goals.push_back(make_pair(goal_var, goal_value));
	}
	
	// goal locations
	for (uint32_t i = 0; i < task->target->getLocationConstraints().size(); i++) {
	    
	    const Location* goal_loc = task->target->getLocationConstraints()[i]->loc;
	    
	    int procID = goal_loc->proc->id;
	    CGVariable* goal_var;
	    int varid = idmapper(PROCESS, procID);
	    if (varid != -1) {
		goal_var = variables[varid];
		uint32_t goal_value = goal_loc->idInProcess;
		goals.push_back(make_pair(goal_var, goal_value));
	    }
	}
    }

    // Compute Domain Transition Graphs
    void CausalGraph::processValueAssignmentDTG(vector<DomainTransitionGraph*>& transition_graphs, 
						int va_rhs,
						const CGVariable* asvar_lhs,
						const vector<const IntConstraint*>& integerconstraints,
						const CGOperator* op, 
						int op_index, 
						const vector<pair<CGVariable*, int> >& additionalConstraints) {
	
	int post = va_rhs;
	int pre = -1;
	int var_level = asvar_lhs->level;

	// check if asvar_lhs is also contained in the guard
	bool found = false;
	for (uint32_t k = 0; k < integerconstraints.size() && !found; k++)
	    if (integerconstraints[k]->lhs->name == asvar_lhs->name) {
		// HACK! Assert that constraint comparator is not NEQ
		// TODO: think about how to resolve this
		assert (integerconstraints[k]->comp != IntConstraint::NEQ);
		found = true;
		pre = integerconstraints[k]->rhs; 
	    }

	if (found) {
	    transition_graphs[var_level]->addTransition(pre, post, op, op_index, additionalConstraints);
	} else {
	    assert (pre == -1);
	    
	    for (int pre = asvar_lhs->lower; pre <= asvar_lhs->upper; pre++) {
		if (pre != post) {
		    transition_graphs[var_level]->addTransition(pre, post, op, op_index, additionalConstraints);
		}
	    }
	}
    }
    
    void CausalGraph::processLocationAssignmentDTG(vector<DomainTransitionGraph*>& transition_graphs, const CGOperator* op, int op_index) {
	const Location* src_loc1 = op->edge1->src;
	const Location* dst_loc1 = op->edge1->dst;
	
	vector<pair<CGVariable*, int> > additionalConstraints; // dummy vector (only needed in processValueAssignmentDTG)
	
	int varid = idmapper(PROCESS, src_loc1->proc->id);
	
	if (varid != -1) {
	    CGVariable* var1 = variables[varid];

	    assert (var1->level != -1);
	    if (src_loc1->idInProcess != dst_loc1->idInProcess) {
		transition_graphs[var1->level]->addTransition(src_loc1->idInProcess, 
							      dst_loc1->idInProcess, 
							      op, op_index, 
							      additionalConstraints);
	    }
	}
	
	if (!op->is_tau_operator()) {
	    
	    Location* src_loc2 = op->edge2->src;
	    Location* dst_loc2 = op->edge2->dst;
	    int varid = idmapper(PROCESS, src_loc2->proc->id);
	    if (varid != -1) {
		CGVariable* var2 = variables[varid];
		
		assert (var2->level != -1);
		
		if (src_loc2->idInProcess != dst_loc2->idInProcess) {
		    transition_graphs[var2->level]->addTransition(src_loc2->idInProcess, dst_loc2->idInProcess, op, op_index, additionalConstraints);
		}
	    }
	}	
    }



}
