// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: abstractor.cpp 831 2011-10-09 21:39:48Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "system/system.h"
#include "system/target.h"
#include "system/task.h"
#include "system/assignment.h"
#include "system/parser.h"
#include "system/variable.h"
#include "system/process.h"
#include "system/edge.h"
#include "system/location.h"
#include "system/effect.h"
#include "system/guard.h"
#include "system/invariant.h"
#include "common/message.h"
#include "abstractor.h"

#include <fstream>
#include <cassert>
#include <iostream>
#include <string>
#include <unistd.h>
#include <set>
#include <boost/foreach.hpp>

#define foreach BOOST_FOREACH

using namespace std;

// abstract the symbol <name> from the system. The name refers either
// to a integer variable, clock, channel or process. Local variables
// of must be prefixed with "P.", where P is the process' name
void Abstractor::abstractSymbol(const string& name) {
    if (find(system, name)) {
	symbols.insert(name);
    } else {
	debug() << "no such variable " << name << endl;
    }
}

void Abstractor::abstract_declarations(const Component* comp, Component* acomp) const {
    string prefix = comp->name;
    if (comp->name != "") {
	prefix += ".";
    }
    foreach (Integer* i, comp->ints) {
	if (keepSymbol(prefix + i->name)) {
	    acomp->ints.push_back(i);
	}
    }
	
    foreach (Clock* i, comp->clocks) {
	if (keepSymbol(prefix + i->name)) {
	    acomp->clocks.push_back(i);
	}
    }
	
    foreach (Channel* i, comp->channels) {
	if (keepSymbol(prefix + i->name)) {
	    acomp->channels.push_back(i);
	}
    } 	
}

Location* Abstractor::abstract_location(const Location* l, Process* aproc) const {
    Location* loc = new Location(aproc, l->name, aproc->locs.size(), nrLocs++);
    loc->urgent = l->urgent;
    loc->commit = l->commit;
    if (!weak_abstraction) {
	loc->inv = abstract_invariant(l->inv);
    }
    else {
	loc->inv = l->inv;
    }
	
    return loc;
}

bool Abstractor::keepSymbol(const std::string& name) const {
    return symbols.find(name) == symbols.end();
}

bool Abstractor::keepSymbol(const Variable* v) const {
    return !abstract_vars.contains(v);
}

bool Abstractor::keepAllSymbols(const varset_t& vars) const {
    foreach (const Variable* v, vars) {
	if (!keepSymbol(v)) {
	    return false;
	}
    }
    return true;
}

Invariant* Abstractor::abstract_invariant(Invariant* inv) const {
    Invariant* res = new Invariant;
    foreach (ClockConstraint* cc, inv->clockconstraints) {
	if (keep_constraint(cc)) {
	    res->addClockConstraint(cc);
	}
    }
    return res;
}

bool Abstractor::keep_constraint(Constraint* c) const {
    return keepAllSymbols(c->readVars());
}

ClockReset* Abstractor::abstract_clockreset(const ClockReset* r) const {
    if (!keepAllSymbols(r->writeVars()) || !keepAllSymbols(r->readVars())) { 
	return NULL;
    } else {
	AssignmentFactory& factory = AssignmentFactory::getFactory();
	return factory.create(r->lhs, r->rhs);
    }
}

Process* Abstractor::abstract_process(const Process* p) {
    Process* aproc = new Process(p->name, asys, nrProcs++);
    if (keepSymbol(p->name)) {	
	abstract_declarations(p, aproc);	 
	foreach (const Location* l, p->locs) {
	    aproc->locs.push_back(abstract_location(l, aproc));
	}
	aproc->init = aproc->getLocationByName(p->init->name);
	foreach (Edge* e, p->edges) {
	    abstract_edge(e, aproc);
	}
    } else {       
	const string prefix = p->name + ".";
	foreach (const Integer* i, p->ints) {
	    abstractSymbol(prefix + i->name);
	}       
	foreach (const Clock* i, p->clocks) {
	    abstractSymbol(prefix + i->name);
	}	
	foreach (const Channel* i, p->channels) {
	    abstractSymbol(prefix + i->name);
	}
	Location* loop = new Location(aproc, "loop", aproc->locs.size(), nrLocs++);
	loop->inv = new Invariant;
	aproc->locs.push_back(loop);
	aproc->init = aproc->getLocationByName("loop");
	foreach (Edge* edge, p->edges) {
	    Edge* aedge = new Edge(aproc->edges.size(), nrEdges++);
	    
	    if (edge->getAction() && keepSymbol(edge->getAction()->chan)) {
		aedge->setAction(edge->getAction());
	    }
	    abstract_effect(edge, aproc, aedge);
	    aedge->guard = new Guard;
	}
	foreach (Edge* aedge, aproc->edges) {
	    aedge->src = aproc->getLocationByName("loop");
	    aedge->dst = aproc->getLocationByName("loop");
	    aedge->src->outgoing.push_back(aedge);
	    aedge->dst->incoming.push_back(aedge);
	}
    }
    return aproc;
}

Guard* Abstractor::abstract_guard(Guard* g) const {
    if (!keepAllSymbols(g->readVars())) {
	Guard* res = new Guard;
	foreach (IntConstraint* ic, g->intconstraints) {
	    if (keep_constraint(ic)) {
		res->addIntConstraint(ic);
	    }
	}
	foreach (ClockConstraint* cc, g->clockconstraints) {
	    if (keep_constraint(cc)) {
		res->addClockConstraint(cc);
	    }
	}
	return res;
    } else {
	return g;
    }
}

void OldAbstractor::abstract_effect(const Edge* edge, Process* aproc, Edge* aedge) const {
    // assert: if the rhs of an effect is abstraced then so is the lhs
    // if the lhs is absteacted -> remove the entire assignment

    Effect *eff = new Effect;
    foreach (IntAssignment* i, edge->effect->intassigns) {
	// FIXME: after testing, this can be simplified
	if (!keepAllSymbols(i->writeVars())) {
	    // remove entire assignment
	} else if (!keepAllSymbols(i->readVars())) {
	    assert(!keepAllSymbols(i->writeVars()));
	    // remove entire assignment
	} else {
	    assert(keepAllSymbols(i->writeVars()) && keepAllSymbols(i->readVars())); 
	    eff->intassigns.push_back(i);
	} 
    }
    foreach (ClockReset* r, edge->effect->resets) {	
	if (keepAllSymbols(r->writeVars()) || weak_abstraction) {
	    eff->resets.push_back(r);
	}
    }
    
    aedge->effect = eff;
    aproc->edges.push_back(aedge);
    // TODO: what if the edge becomes empty??? Maybe a topic for the
    // RedundancyCleaner
}

void OldAbstractor::compute_var_deps() {
    // initialize and reserve memory
    const uint32_t nrInts = system->getTotalNrIntegers();
    var_deps.assign(nrInts, vector<int32_t>(nrInts, INT_MAX));
    for (uint32_t i = 0; i < nrInts; i++) {
	var_deps[i][i] = 0;
    }
    
    foreach (const Process* proc, system->procs) {
	foreach (const Edge* edge, proc->edges) {
	    foreach (const IntAssignment* ia, edge->effect->intassigns) {
		assert(ia->writeVars().size() == 1);
		foreach (const Variable* v, ia->readVars()) {
		    const Integer* rhs = dynamic_cast<const Integer*>(v);
		    assert(v);
		    var_deps[rhs->id][ia->lhs->id] = 1;//[rhs->id] = 1;
		}
	    } 
	}	
    }
    
    // floyd warshall 
    for (uint32_t k = 0; k < nrInts; k++) {
	for (uint32_t i = 0; i < nrInts; i++) {
	    for (uint32_t j = 0; j < nrInts; j++) {
		if (var_deps[i][k] != INT_MAX && var_deps[k][j] != INT_MAX) {
		    var_deps[i][j] = std::min(var_deps[i][j], var_deps[i][k] + var_deps[k][j]);
		}
	    }
	}
    }
}

System* OldAbstractor::abstract() {
    compute_var_deps();
    for (uint32_t i = 0; i < var_deps.size(); i++) {
	if (!keepSymbol(system->getInteger(i))) {
	    for (int32_t j = 0; j < var_deps[i].size(); j++) { 
		if (var_deps[i][j] < INT_MAX) {
		    abstractSymbol(system->getInteger(j)->name);
		}
	    }
	}
    }
    return Abstractor::abstract();
}

void Abstractor::abstract_effect(const Edge* edge, Process* aproc, Edge* aedge) const {
    Effect* eff = new Effect;
    vector<const VarAssignment*> varassigns;
    
    foreach (IntAssignment* i, edge->effect->intassigns) {
	if (!keepAllSymbols(i->writeVars())) { // lhs in abstract_vars
	    // done, nothing to do here
	} else if (keepAllSymbols(i->readVars())) { 
	    assert(keepAllSymbols(i->writeVars()) && keepAllSymbols(i->readVars()));
	    eff->intassigns.push_back(i);	    
	} else {
	    assert(keepAllSymbols(i->writeVars()) && !keepAllSymbols(i->readVars()));
	    const VarAssignment* va = dynamic_cast<const VarAssignment*>(i);
	    assert(va);
	    varassigns.push_back(va);
	}
    }

    foreach (ClockReset* r, edge->effect->resets) {	
	if (keepAllSymbols(r->writeVars()) || weak_abstraction) {
	    eff->resets.push_back(r);
	}
    }
    
    aedge->effect = eff;
    if (varassigns.empty()) {
	aproc->edges.push_back(aedge);
    } else {
	AssignmentFactory& factory = AssignmentFactory::getFactory();
	vector<Effect*> effects;
	vector<Effect*> new_effects;
	effects.push_back(eff);

	foreach (const VarAssignment* va, varassigns) {
	    foreach (Effect* e, effects) {
		for (int32_t value = va->rhs->lower; value <= va->rhs->upper; value++) {		
		    new_effects.push_back(new Effect(*e));		    
		    new_effects.back()->intassigns.push_back(factory.create(va->lhs, value));
		} 
	    }
	    effects = new_effects;
	    new_effects.clear();
	}
	foreach (Effect* p, effects) {
	    Edge* add_edge = createEdge(edge, aproc);
	    add_edge->effect = p;
	    aproc->edges.push_back(add_edge);
	}	
    }
}

Edge* Abstractor::createEdge(const Edge* e, const Process* aproc) const {
    Edge* edge = new Edge(aproc->edges.size(), nrEdges++);
    edge->src = aproc->getLocationByName(e->src->name);
    edge->dst = aproc->getLocationByName(e->dst->name);    
    edge->guard = abstract_guard(e->guard);
    if (edge->src) {
	edge->src->outgoing.push_back(edge);
    }
    if (edge->dst) {
	edge->dst->incoming.push_back(edge);
    }
    
    if (e->getAction() && keepSymbol(e->getAction()->chan)) {
	edge->setAction(e->getAction());
    }
    return edge;
}

void Abstractor::abstract_edge(const Edge* e, Process* aproc) const {
    Edge* edge = createEdge(e, aproc);
    abstract_effect(e, aproc, edge);
}

System* Abstractor::abstract() {
    asys = new System;
    abstract_declarations(system, asys);
    foreach (const Process* p, system->procs) {
	Process* aproc = abstract_process(p);
	if (aproc) {
	    asys->procs.push_back(aproc);
	}
    }
    return asys;
}

void Abstractor::genPDBInfo(const string& filename) const {
    ofstream out(filename.c_str());
    foreach (const Component* c, abstract_procs) {
	const Process* p = dynamic_cast<const Process*>(c);
	assert(p);
	out << p->id << " ";
    }
    out << ". ";
    foreach (const Variable* v, abstract_vars) {
	const Integer* i = dynamic_cast<const Integer*>(v);
	if (i) {
	    out << i->id << " ";
	}
    }
    out << ". ";
    foreach (const Variable* v, abstract_vars) {
	const Clock* c = dynamic_cast<const Clock*>(v);
	if (c) {
	    out << c->id << " ";
	}
    }
    out << "." << endl;
    out.close();
}

bool Abstractor::find(const System* system, const string& name) {
    debug() << "searching " << name << endl;
    int pos = name.find('.');
    string proc = "";
    string var = name;
	    
    if (pos != -1) {
	proc = name.substr(0, pos);
	var = name.substr(pos+1, name.size());
    }
	
    if (proc != "") {
	const Component* p = system->getComponent(proc);
	assert(p);
	Variable* v = system->find(p, var);
	if (v) {
	    debug() << "  found " << *v << endl;
	    abstract_vars += v;
	    return true;
	} else {
	    debug() << "  no variable with name " << name << endl;
	}	    
    } else {
	const Component* p = system->getComponent(var);
	if (p) {
	    debug() << "  found process " << var << endl;
	    abstract_procs.insert(p);
	    return true;
	} else {
	    Variable* v = system->find(system, var);
	    if (v) {
		debug() << "  found global " << *v << endl;
		abstract_vars += v;
		return true;
	    } else {
		debug() << "  no variable with name " << name << endl;
	    }	   
	}
    }
    return false;
}

bool RedundancyCleaner::relevant(const Edge* edge) const {
    if (edge->src != edge->dst) {
	return true;
    }
    if (!edge->src->inv->clockconstraints.empty()) {
	return true;
    }
    if (!edge->effect->empty()) {
	return true;
    }
    if (edge->getType() != Edge::TAU) {
	return true;
    }
    return false;
}

void RedundancyCleaner::clean(System* system) const {
    vector<Process*> new_procs;
    foreach (Process* proc, system->procs) {
	vector<Edge*> new_edges;
	foreach (Edge* pe, proc->edges) {
	    bool found = false;
	    foreach (Edge* ne, new_edges) {
		if (*pe == *ne) {
		    found = true;
		    break;
		}
	    }
	    if (!found && relevant(pe)) {
		new_edges.push_back(pe);
	    }	    
	}
	proc->edges = new_edges;
    }
    
    

    // TODO: update locations. ie. incomming outgoing edges
    delOnlyReadVars(system);
}

void RedundancyCleaner::delOnlyReadVars(System* system) const {
    varset_t all_vars;
    foreach (const Variable* var, system->ints) {
	all_vars += var;
    }
    foreach (const Variable* var, system->clocks) {
	if (var->name != "t(0)") {
	    all_vars += var;
	}
    }
    foreach (const Process* proc, system->procs) {
	foreach (const Variable* var, proc->ints) {
	    all_vars += var;
	}
	foreach (const Variable* var, proc->clocks) {
	    all_vars += var;
	}
    }  
    
    varset_t read_vars;
    foreach (const Process* proc, system->procs) {
	foreach (const Location* loc, proc->locs) {
	    read_vars += loc->inv->readVars();
	}
	foreach (const Edge* edge, proc->edges) {	    
	    read_vars += edge->guard->readVars();
	}
    }

    // foreach (const Variable* v, all_vars) {
    // 	if (!read_vars.contains(v)) {
    // 	    cout << "// DELETE " << v->name << endl;
    // 	}
    // }
}

set<string> collect(const System* system) {
    set<string> names;
    foreach (const Clock* c, system->clocks) {
	names.insert(c->name);
    }
    foreach (const Integer* i, system->ints) {
	names.insert(i->name);
    }

    foreach (const Process* p, system->procs) {
	names.insert(p->name);
	foreach (const Clock* c, p->clocks) {
	    names.insert(p->name + "." + c->name);
	}
	foreach (const Integer* i, p->ints) {
	    names.insert(p->name + "." + i->name);
	}	
    }
    names.erase(names.find("t(0)"));
    return names;
}

int main(int argc, char *argv[]) {
    bool oldSyntax = true;
    bool oldAbstractor = false;
    bool complement = false;
    bool weak_abstraction = false;
    string filename;
    debug().setSilent(true);
    int c;
    while ((c = getopt(argc, argv, "wokcndf:")) != -1) {
	switch (c) {
	case 'k':
	case 'c':
	    complement = true;
	    break;
	case 'n':
	    oldSyntax = false;
	    break;
	case 'd':
	    debug().setSilent(false);
	    break;
	case 'f':
	    filename = optarg;
	    break;
	case 'o':
	    oldAbstractor = true;
	    break;
	case 'w':
	    // weak abstraction = only abstract guards that contain
	    // the variables; this is interesting for clocks:
	    // invariants and resets are *not* abstracted;
	    weak_abstraction = true;
	    break;
	default:
	    cout << "error: unknown option" << endl;
	    break;
	}
    }
    if (argc - optind < 1) {
	error() << "not enough arguments" << endl;
    }    
    
    Task* task = SystemParser::parser()(argv[optind], oldSyntax);
    System* system = task->system;

    Abstractor* abs = NULL;
    if (oldAbstractor) {
	abs = new OldAbstractor(system, weak_abstraction);
    } else {
	abs = new Abstractor(system, weak_abstraction);
    } 

    set<string> names;
    if (complement) {
	names = collect(system);
	for (int i = optind+1; i < argc; i++) {
	    set<string>::iterator it = names.find(argv[i]);
	    if (it != names.end()) {
		names.erase(it);
	    }
	}
    } else {
	for (int i = optind+1; i < argc; i++) {
	    names.insert(argv[i]);
	}
    }
    foreach (const string& name, names) {
	abs->abstractSymbol(name.c_str());
    }
    
    System* asys = abs->abstract();
    RedundancyCleaner cleaner;
    cleaner.clean(asys);
    cout << *asys << endl;
    if (filename != "") {
	abs->genPDBInfo(filename);
    }
}
