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

#include "edge.h"
#include "expression.h"
#include "expression_builder.h"
#include "guard.h"
#include "invariant.h"
#include "location.h"
#include "process.h"
#include "resolver.h"
#include "system.h"
#include "system_builder.h"

#include "common/message.h"

#include <cassert>
#include <set>
#include <utap/utap.h>

using namespace std;
using namespace UTAP::Constants;

using UTAP::TimedAutomataSystem;
using UTAP::instance_t;
using UTAP::template_t;
using UTAP::state_t;
using UTAP::edge_t;
using UTAP::symbol_t;
using UTAP::expression_t;
using UTAP::variable_t;
using UTAP::declarations_t;

SystemBuilder::SystemBuilder() : 
    nrEdges(0),
    nrProcs(0),
    nrLocs(0),
    nrClocks(0),
    nrInts(0),
    nrChans(0),
    resolver(NULL)
{}

System* SystemBuilder::buildSystem(UTAP::TimedAutomataSystem* tasystem) const {
    System* system = new System;
    resolver = system->resolver;

    declarations_t& decs = tasystem->getGlobals();
    for (list<variable_t>::const_iterator it = decs.variables.begin(); it != decs.variables.end(); ++it) {
 	addVariable(*it, system);
    }
    
    list<instance_t>& procs = tasystem->getProcesses();
    for (list<instance_t>::const_iterator it = procs.begin(); it != procs.end(); ++it) {
	addProcess(*it, system);
    }
    system->prepare();
    return system;
}

void SystemBuilder::addProcess(const UTAP::instance_t& instance, System* system) const {
    Process* proc = new Process(instance.uid.getName(), system, nrProcs++);
    system->procs.push_back(proc);
    
    /// @todo perhaps this should be capsulated in addProcessArguments
    typedef std::map< symbol_t, expression_t > map_t; 
    for (map_t::const_iterator it = instance.mapping.begin(); it != instance.mapping.end(); ++it) {
	variable_t var = {it->first, it->second};
	addVariable(var, proc);
    }
    
    for (list<variable_t>::const_iterator it = instance.templ->variables.begin(); it != instance.templ->variables.end(); ++it) {
 	addVariable(*it, proc);
    }
    
    for (list<state_t>::const_iterator it = instance.templ->states.begin(); it != instance.templ->states.end(); ++it) {
	addLocation(*it, proc);
    }
    
    for (list<edge_t>::const_iterator it = instance.templ->edges.begin(); it != instance.templ->edges.end(); ++it) {
 	addEdge(*it, proc);
    }    
    
    proc->init = proc->getLocationByName(instance.templ->init.getName());
}

void SystemBuilder::addEdge(const UTAP::edge_t& e, Process* proc) const {
    ExpressionBuilder ebuilder(resolver);
    Edge* edge = new Edge(e.nr, nrEdges++);    

    edge->src = proc->getLocationByName(e.src->uid.getName());
    edge->dst = proc->getLocationByName(e.dst->uid.getName());    
    edge->guard = ebuilder.buildGuard(e.guard, proc);
    edge->effect = ebuilder.buildEffect(e.assign, proc);    
    edge->src->outgoing.push_back(edge);
    edge->dst->incoming.push_back(edge);

    if (!e.sync.empty()) {
	edge->setAction(ebuilder.buildAction(e.sync, proc));
    }
    proc->edges.push_back(edge);
}

void SystemBuilder::setLocationType(const UTAP::type_t& type, Location* location) const {
    switch (type.getKind()) {
    case LOCATION:
	break;
    case URGENT:
	warning() << "urgent locations are not supported yet" << endl;
	location->urgent = true;
	for (uint32_t i = 0; i < type.size(); i++) {
 	    setLocationType(type[i], location);
 	}
	break;
    case COMMITTED:
	location->commit = true;
	for (uint32_t i = 0; i < type.size(); i++) {
 	    setLocationType(type[i], location);
 	}
	break;
    default:
	error() << type << "' is not a valid location type." << endl;
    }
} 

void SystemBuilder::addLocation(const UTAP::state_t& state, Process* proc) const {
    assert(proc);
    
    if (!state.costrate.empty()) {
	error() << state.costrate.toString() << "'. Cost rates are NOT supported." << endl; 
    }

    Location* loc = new Location(proc, state.uid.getName(), state.locNr, nrLocs++);
    if (state.invariant.empty()) {
	loc->inv = new Invariant;
    } else {
	ExpressionBuilder ebuilder(resolver);
	loc-> inv = ebuilder.buildInvariant(state.invariant, proc);
    }
    
    setLocationType(state.uid.getType(), loc);
    loc->proc = proc;
    proc->locs.push_back(loc);
    resolver->addLocation(proc, loc);
}

void SystemBuilder::addInteger(const UTAP::variable_t& var, Component* comp) const {
    const UTAP::type_t type = var.uid.getType();

    assert(type.isInteger() && type.is(RANGE));
    
    ExpressionBuilder ebuilder(resolver);
    Integer* integer = new Integer(var.uid.getName(), nrInts++);
    integer->lower = ebuilder.evaluate(type.getRange().first, comp);
    integer->upper = ebuilder.evaluate(type.getRange().second, comp);
    if (var.expr.empty()) {
	integer->initial = std::max(0, integer->lower);
    } else {
	integer->initial = ebuilder.evaluate(var.expr, comp);
    }
    
    comp->ints.push_back(integer);
    resolver->addInteger(comp, integer);
}

void SystemBuilder::addChannel(const UTAP::variable_t& var, Component* comp) const {
    assert(var.uid.getType().isChannel());

    Channel* channel = new Channel(var.uid.getName(), nrChans++);
    setChannelType(var.uid.getType(), channel);    
    comp->channels.push_back(channel);
    resolver->addChannel(comp, channel);
}

void SystemBuilder::setChannelType(const UTAP::type_t& type, Channel* channel) const {
    assert(type.isChannel());
    
    switch (type.getKind()) {
    case CHANNEL:
	break;
    case BROADCAST:
	channel->broadcast = true;
  	for (uint32_t i = 0; i < type.size(); i++) {
  	    setChannelType(type[i], channel);
  	}
  	break;
    case URGENT:
	channel->urgent = true;
 	for (uint32_t i = 0; i < type.size(); i++) {
 	    setChannelType(type[i], channel);
 	}
 	break;	
    default:
	error() << "invalid channel type '" << type << "'" << endl;
    }
} 

void SystemBuilder::addConstant(const UTAP::variable_t& var, Component* comp) const {
    ExpressionBuilder ebuilder(resolver);
    resolver->addConstant(comp, new Constant(var.uid.getName(), ebuilder.evaluate(var.expr, comp)));
}

void SystemBuilder::addClock(const UTAP::variable_t& var, Component* comp) const {
    Clock* clock = new Clock(var.uid.getName(), nrClocks++);
    comp->clocks.push_back(clock);
    resolver->addClock(comp, clock);
}

void SystemBuilder::addVariable(const UTAP::variable_t& var, Component* comp) const {
    UTAP::type_t type = var.uid.getType();

    switch (type.getKind()) {
    case CLOCK:
	addClock(var, comp);
	break;
    case RANGE:
	assert(type[0].isInteger());
	addInteger(var, comp);
	break;
    case CHANNEL:
    case URGENT:
    case COMMITTED:
    case BROADCAST:
	assert(var.expr.empty());
	assert(type.isChannel());
	addChannel(var, comp);
	break;	
    case CONSTANT:
	addConstant(var, comp);
	break;
    case REF:
	error() << "references aren't handled" << endl;
	break;
    default:
	error() << var.uid.getName() << " of type " << var.uid.getType().getKind() << " not handled " << endl;
    }
}


