// -*- mode: C++; c-file-style: "stroustrup"; c-basic-offset: 4; -*-
////////////////////////////////////////////////////////////////////
//
// $Id: ffsystem.cpp 816 2011-09-10 13:19:07Z Sebastian Kupferschmid $
//
////////////////////////////////////////////////////////////////////

#include "ffedge.h"
#include "ffhelper.h"
#include "ffiguard.h"
#include "ffloc.h"
#include "ffsystem.h"
#include "ffvar.h"

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

#include <cassert>
#include <iostream>

namespace ff {

    using namespace std;
    
    System::System(const Task* task) : 
	assignments(task->system->getTotalNrIntAssignments(), NULL),
	constraints(task->system->getTotalNrIntConstraints(), NULL),
	reachedLocations(task->system->getTotalNrLocations()),
	goal(reachedLocations),	
	level(0) 
    {	
	buildSystem(task);
    }

    System::~System() {
	destroyVector(locations);
	destroyVector(edges);
	destroyVector(variables);
    }
	
    void System::incLevel() {
	level++;
	reachedLocations.incLevel();
	for (uint32_t i = 0; i < variables.size(); i++) {
	    variables[i]->incLevel();
	}
    }

    void System::init(const State* state) {	
	// init Vars
	for (uint32_t i = 0; i < variables.size(); ++i) {
	    variables[i]->init(state->var(i));
	}

	// init Locs
	reachedLocations.clear();
	for (uint32_t i = 0; i < procOffsets.size(); i++) {
	    Location* loc = locations[procOffsets[i] + state->proc(i)];
	    reachedLocations.add(loc);
	}
	reachedLocations.incLevel();

	// init edges: this is called to reset the guards
	for (uint32_t i = 0; i < edges.size(); ++i) {
	    edges[i]->init();
	}

	goal.init();
	level = 0;
    }

    void System::buildSystem(const Task* task) {	
	nrChannels = task->system->getTotalNrChannels();
	
	// calculate proc offsets
	uint32_t offset = 0;
	procOffsets.assign(task->system->procs.size(), 0);	
	for (uint32_t i = 0; i < task->system->procs.size(); ++i) {
	    procOffsets[i] = offset;
	    offset += task->system->procs[i]->locs.size();
	}
	
	// allocate and initialise memory 
	initVector(locations, task->system->getTotalNrLocations());
	initVector(edges, task->system->getTotalNrEdges());
	initVector(variables, task->system->getTotalNrIntegers());
		
	// build locations, edges, variables 	
	for (uint32_t i = 0; i < task->system->ints.size(); i++) {
	    buildVariable(task->system->ints[i]);
	}
	for (uint32_t j = 0; j < task->system->procs.size(); j++) {
	    const ::Process* proc = task->system->procs[j];
	    
	    for (uint32_t i = 0; i < proc->locs.size(); i++) {
		buildLocation(proc->locs[i]);
	    }
	    for (uint32_t i = 0; i < proc->ints.size(); i++) {
		buildVariable(proc->ints[i]);
	    } 
	    for (uint32_t i = 0; i < proc->edges.size(); i++) {
		buildEdge(proc->edges[i]);
	    } 	    
	}

	// add sync edges
	for (uint32_t i = 0; i < nrChannels; i++) {
	    vector<Edge*> bang;
	    vector<Edge*> que;
	    for (uint32_t j = 0; j < edges.size(); j++) {
		if (edges[j]->getChannelNr() == int32_t(i)) {
		    switch(edges[j]->getType()) {
		    case Edge::BANG:
			bang.push_back(edges[j]);
			break;
		    case Edge::QUE:
			que.push_back(edges[j]);
			break;
		    default:
			assert(false);
			break;
		    }
		}
	    }
	    for (uint32_t b = 0; b < bang.size(); b++) {
		for (uint32_t q = 0; q < que.size(); q++) {
		    if (bang[b]->getProcID() != que[q]->getProcID()) {
			bang[b]->addSyncEdge(que[q]);
			que[q]->addSyncEdge(bang[b]);
		    }
		}
	    }
	}

	// add write edges to variables
	for (uint32_t i = 0; i < edges.size(); i++) {
	    const vector<IntegerAssignment*>& assigns = edges[i]->getAssignments();
	    for (uint32_t j = 0; j < assigns.size(); j++) {
		assigns[j]->getLhs()->writeEdges.push_back(edges[i]);
	    }
	}
	    
	// build goal
	const Conjunction<LocationConstraint>& lcs = task->target->getLocationConstraints();
	for (uint32_t i = 0; i < lcs.size(); i++) {
	    goal.addLocation(convert(lcs[i]->loc));
	}
	
	const Conjunction<IntConstraint>& ics = task->target->getIntConstraints();
	for (uint32_t i = 0; i < ics.size(); i++) {
	    goal.addConstraint(newIntegerConstraint(ics[i]));
	}
    }

    ostream& System::display(ostream& o) const {
	for (uint32_t i = 0; i < variables.size(); i++) {
	    o << *variables[i] << endl;
	}
	for (uint32_t i = 0; i < edges.size(); i++) {
	    o << *edges[i] << endl;
	}
	return o;
    }
    
    void System::buildVariable(const ::Integer* iv) {
	Variable* build = convert(iv);
	build->setID(iv->id);
	build->setName(iv->name);
	build->setRange(Range(iv->lower, iv->upper));
    }
    
    void System::buildEdge(const ::Edge* edge) {
	Edge* build = convert(edge);
	// locations
	build->from = convert(edge->src);
	build->to = convert(edge->dst);

	// sync stuff
	if (edge->getType() == ::Edge::TAU) {
	    build->setType(Edge::TAU);
	    build->setChannelNr(-1);
	} else {
	    build->setChannelNr(edge->getAction()->id);
	    build->setType(edge->getAction()->isBang ? Edge::BANG : Edge::QUE);
	}
	
	// set process id
	build->setProcID(edge->getProcess()->id);

	// assignments
	const vector<IntAssignment*>& intassigns = edge->effect->intassigns;
	for (uint32_t i = 0; i < intassigns.size(); i++) {
	    build->addAssignment(newAssignment(intassigns[i]));
	}
	
	// guards
	const Conjunction<IntConstraint>& ics = edge->guard->intconstraints;
	for (uint32_t i = 0; i < ics.size(); i++) {
	    build->addConstraint(newIntegerConstraint(ics[i]));
	} 
    }
    
    void System::buildLocation(const ::Location* loc) {
	Location* build = convert(loc);
	build->setName(loc->proc->name + "." +loc->name);
	build->setID(loc->idInSystem);
	
	for (uint32_t i = 0; i < loc->outgoing.size(); i++) {
	    build->addOutgoingEdge(convert(loc->outgoing[i]));
	}			
	for (uint32_t i = 0; i < loc->incoming.size(); i++) {
	    build->addIncomingEdge(convert(loc->incoming[i]));
	}			
    }

    ////////////////////////////////////////////////////////////////////
    //
    // Some factory methods
    //
    ////////////////////////////////////////////////////////////////////

    IntegerAssignment* System::newAssignment(const ::IntAssignment* assign) {
	Variable* lhs = convert(assign->lhs);
	if (isConstAssignment(assign)) {
	    const ::ValueAssignment* a = dynamic_cast<const ::ValueAssignment*>(assign);
	    if (assignments[a->id] == NULL) {
		assert(a->id < assignments.size());
		assignments[a->id] = new ConstAssignment(a->id, lhs, a->rhs);
	    } 
	    return assignments[a->id];
	}
	if (isVarAssignment(assign)) {
	    const ::VarAssignment* a = dynamic_cast<const ::VarAssignment*>(assign);
	    if (assignments[a->id] == NULL) {
		assert(a->id < assignments.size());
		assignments[a->id] = new VarAssignment(a->id, lhs, convert(a->rhs));
	    } 
	    return assignments[a->id];
	}	
	cout << "Do not support " << *assign << " ignoring" << endl;
	return new NullAssignment();
    }

    IntegerConstraint* System::newIntegerConstraint(const ::IntConstraint* ic) {
	static IntegerConstraint* nc = new NullConstraint(-1);
	if (isConstConstraint(ic)) {
	    assert(constraints.size() > ic->id);
	    if (constraints[ic->id] == NULL) {
		switch (ic->comp) {
		case Constraint::EQ:
		    constraints[ic->id] = new ConstConstraint(convert(ic->lhs), ic->rhs, IntegerConstraint::EQ, ic->id);
		    return constraints[ic->id];
		case Constraint::NEQ:
		    constraints[ic->id] = new ConstConstraint(convert(ic->lhs), ic->rhs, IntegerConstraint::NEQ, ic->id);
		    return constraints[ic->id];
		default:
		    cout << "Do not support " << *ic << " ignoring" << endl;
		    return nc;
		}
	    } 
	    return constraints[ic->id];
	}	
	cout << "Do not support " << *ic << " ignoring" << endl;
	return nc;
    }

    ////////////////////////////////////////////////////////////////////
    //
    // classifiers for constraints and assignments
    //
    ////////////////////////////////////////////////////////////////////   

    bool System::isConstAssignment(const ::IntAssignment* assign) const {
	return dynamic_cast<const ::ValueAssignment*>(assign) != NULL;
    }
    
    bool System::isVarAssignment(const ::IntAssignment* assign) const {
	return dynamic_cast<const ::VarAssignment*>(assign) != NULL;
    }

    bool System::isConstConstraint(const ::IntConstraint* constraint) const {
	return dynamic_cast<const ::IntConstraint*>(constraint) != NULL;	
    }

    ////////////////////////////////////////////////////////////////////
    //
    // Converter functions from avacs_parser to avacs_nlff
    //
    ////////////////////////////////////////////////////////////////////
	
    Edge* System::convert(const ::Edge* edge) const {
	assert(edges[edge->idInSystem] != NULL);
	return edges[edge->idInSystem];
    }
    
    Location* System::convert(const ::Location* loc) const {
	assert(locations[loc->idInSystem] != NULL);
	return locations[loc->idInSystem];
    }

    Variable* System::convert(const ::Integer* var) const {
	assert(variables[var->id] != NULL);
	return variables[var->id];
    }
}
