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

#include "assignment.h"
#include "component.h"
#include "constraint.h"
#include "constraint_container.h"
#include "effect.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 "target.h"
#include "variable.h"

#include "common/message.h"

#include <cassert>
#include <fstream>
#include <iostream>
#include <map>
#include <utap/utap.h>

using namespace std;
using namespace UTAP::Constants;

Variable* ExpressionBuilder::resolve(const UTAP::expression_t& expr, const Component* comp) const {
    Variable* var = resolver->find(comp, expr.getSymbol().getName());
    // Variable* var = comp->find(expr.getSymbol().getName());
    if (var) {
        return var;
    }
    if (comp->parent) {
        return resolve(expr, comp->parent);
    }
    error() << "could not resolve " << expr.toString() << endl;
    return NULL;
}

/// @todo rename to buildConstant, should then return a Constant
int32_t ExpressionBuilder::evaluate(const UTAP::expression_t& exp, const Component* proc) const {
    switch (exp.getKind()) {
    case PLUS:
        assert(exp.getSize() == 2);
        return evaluate(exp[0], proc) + evaluate(exp[1], proc);
    case MINUS:
        assert(exp.getSize() == 2);
        return evaluate(exp[0], proc) - evaluate(exp[1], proc);
    case MULT:
        assert(exp.getSize() == 2);
        return evaluate(exp[0], proc) * evaluate(exp[1], proc);
    case CONSTANT:
        return exp.getValue();
    case IDENTIFIER:
        if (exp.getType().isConstant()) {
            //return proc->findConst(exp)->value;
            return resolver->findConst(proc, exp)->value;
        }
        error() << "identifier " << exp.toString() << " is NOT const" << endl;
        return 0;
    case UNARY_MINUS:
        assert(exp.getSize() == 1);
        return -evaluate(exp[0], proc);
    default:
        error() << "kind " << exp.getKind() << " is not supported" << endl;
        return 0;
    }
}

Effect* ExpressionBuilder::buildEffect(const UTAP::expression_t& exp, const Component* comp) const {
    Effect* effect = new Effect();
    build(exp, comp, effect);
    return effect;
}

Guard* ExpressionBuilder::buildGuard(const UTAP::expression_t& exp, const Component* comp) const {
    Guard* guard = new Guard();
    build(exp, comp, guard);
    return guard;
}

Invariant* ExpressionBuilder::buildInvariant(const UTAP::expression_t& exp, const Component* comp) const {
    Invariant* inv = new Invariant();
    build(exp, comp, inv);
    return inv;
}

Action* ExpressionBuilder::buildAction(const UTAP::expression_t& exp, const Component* comp) const {
    Channel* chan = dynamic_cast<Channel*>(resolve(exp, comp));
    assert(chan);
    switch (exp.getSync()) {
    case UTAP::Constants::SYNC_QUE:
        return new Action(chan, false);
    case UTAP::Constants::SYNC_BANG:
        return new Action(chan, true);
    default:
        assert(false);
        return NULL;
    }
}

Target* ExpressionBuilder::buildTarget(
    const char* filename, UTAP::TimedAutomataSystem* tasystem, const System* system) const {

    ifstream ifs(filename);
    if (ifs.fail()) {
        error() << "failed to open file " << filename << endl;
        return NULL;
    }
    string query = "";
    while (ifs.good()) {
        query += (char) ifs.get();
    }
    ifs.close();

    UTAP::expression_t exp = parseExpression(query.c_str(), tasystem, true);
    Target* target = new Target();
    build(exp, system, target);
    return target;
}

static Constraint::comp_t adapt(UTAP::Constants::kind_t kind) {
    switch (kind) {
    case LT: return Constraint::LT;
    case LE: return Constraint::LE;
    case EQ: return Constraint::EQ;
    case GE: return Constraint::GE;
    case GT: return Constraint::GT;
    case NEQ: return Constraint::NEQ;
    default:
        assert(false);
        return Constraint::NEQ;
    }
}

Constraint* ExpressionBuilder::buildConstraint(const UTAP::expression_t& exp, const Component* comp, Expression* result) const {
    Expression* lhs = build(exp[0], comp, result);
    Expression* rhs = build(exp[1], comp, result);
    assert(lhs && rhs);

    ConstraintContainer* container = dynamic_cast<ConstraintContainer*>(result);
    assert(container);

    if (rhs->type == Expression::CONSTANT) {
        Constraint::comp_t comparator = adapt(exp.getKind());
        Constant* constant = dynamic_cast<Constant*>(rhs);
        assert(constant);
        ConstraintFactory& factory = ConstraintFactory::getFactory();

        if (lhs->type == Expression::CLOCK) {
            Clock* clock = dynamic_cast<Clock*>(lhs);
            ClockConstraint* cc = factory.create(clock, comparator, constant);
            container->addClockConstraint(cc);
            return cc;
        }
        if (lhs->type == Expression::INTEGER) {
            Integer* integer = dynamic_cast<Integer*>(lhs);
            IntConstraint* ic = factory.create(integer, comparator, constant);
            container->addIntConstraint(ic);
            return ic;
        }
    }

    error() << exp.toString() << " not supported" << endl;
    return NULL;
}

Assignment* ExpressionBuilder::buildAssignment(const UTAP::expression_t& exp, const Component* comp, Expression* result) const {
    Expression* lhs = build(exp[0], comp, result);
    Expression* rhs = build(exp[1], comp, result);
    Effect* effect = dynamic_cast<Effect*>(result);
    assert(lhs && rhs && effect);

    AssignmentFactory& factory = AssignmentFactory::getFactory();

    if (lhs->type == Expression::CLOCK && rhs->type == Expression::CONSTANT) {
        ClockReset* reset = factory.create(dynamic_cast<Clock*>(lhs), dynamic_cast<Constant*>(rhs));
        effect->resets.push_back(reset);
        return reset;
    }
    if (lhs->type == Expression::INTEGER && rhs->type == Expression::CONSTANT) {
        IntAssignment* assign = factory.create(dynamic_cast<Integer*>(lhs), dynamic_cast<Constant*>(rhs));
        effect->intassigns.push_back(assign);
        return assign;
    }
    if (lhs->type == Expression::INTEGER && rhs->type == Expression::INTEGER) {
        IntAssignment* assign = factory.create(dynamic_cast<Integer*>(lhs), dynamic_cast<Integer*>(rhs));
        effect->intassigns.push_back(assign);
        return assign;
    }
    error() << exp.toString() << " not supported" << endl;
    return NULL;
}

Variable* ExpressionBuilder::buildDotExpression(const UTAP::expression_t& exp, const Component* comp, Expression* result) const {
    assert(exp.getSize() == 1);
    if (exp[0].getType().isProcess()) {
        assert(dynamic_cast<const System*>(comp));
        const Component* proc = comp->getComponent(exp[0].toString());
        Variable* var = resolver->getChild(proc, exp.getIndex());
        if (var->type == Expression::INTEGER) {
            return var;
        }
        if (var->type == Expression::LOCATION) {
            Target* target = dynamic_cast<Target*>(result);
            Location* loc = dynamic_cast<Location*>(var);
            assert(target && loc);
            LocationConstraint* lc = ConstraintFactory::getFactory().create(loc);
            target->addLocationConstraint(lc);
            return loc;
        }
    }
    error() << exp.toString() << " cannot be handled" << endl;
    return NULL;
}

Expression* ExpressionBuilder::build(const UTAP::expression_t& exp, const Component* comp, Expression* result) const {
    switch (exp.getKind()) {
    case LT:
    case LE:
    case EQ:
    case GE:
    case GT:
    case NEQ:
        return buildConstraint(exp, comp, result);
    case EF:
        return build(exp[0], comp, result);
    case DOT:
        return buildDotExpression(exp, comp, result);
    case AND:
    case COMMA:
        assert(exp.getSize() == 2);
        build(exp[0], comp, result);
        build(exp[1], comp, result);
        return NULL;
    case ASSIGN:
        return buildAssignment(exp, comp, result);
    case CONSTANT:
        return ConstantFactory::newConstant(evaluate(exp, comp));
    case IDENTIFIER:
        if (exp.getType().isConstant()) {
            //return comp->findConst(exp);
            return resolver->findConst(comp, exp);
        } else {
            return resolve(exp, comp);
        }
    case PLUS:
    case MINUS:
    case MULT:
        cout <<  exp[0] << " " << exp[1] << endl;
    default:
        error() << "kind " << exp.getKind() << " of "
                << exp.toString() << " is not supported" << endl;
        return NULL;
    }
}
