/*
 * File:   interpretationpass.cpp
 * Author: pgess
 *
 * Created on July 5, 2016, 5:21 PM
 */

#include "pass/interpretationpass.h"
#include "compilation/transformations.h"
#include <compilation/targetinterpretation.h>
#include "ast.h"
//DEBT implement InterpretationPass purely in clasp
//DEBT represent InterpretationPass as general type inference

using namespace std;

namespace xreate{

enum InterpretationQuery{QUERY_INTR_ONLY, QUERY_CMPL_ONLY};

template<>
InterpretationResolution
defaultValue<InterpretationResolution>(){
    return CMPL_ONLY;
}

InterpretationResolution
unify(InterpretationResolution flag) {
    return flag;
}

template<typename FLAG_A, typename FLAG_B, typename... FLAGS>
InterpretationResolution
unify(FLAG_A flagA, FLAG_B flagB, FLAGS... flags) {

    if (flagA== BOTH){
        return unify(flagB, flags...);
    }

    if (flagB == BOTH) {
        return unify(flagA, flags...);
    }

    assert(flagA == flagB);
    return flagA;
}

namespace detail {
    template<InterpretationQuery FLAG_REQUIRED>
    bool checkConstraints(InterpretationResolution flag) {
        return (   (flag==INTR_ONLY && FLAG_REQUIRED == QUERY_INTR_ONLY)
                || (flag==CMPL_ONLY && FLAG_REQUIRED == QUERY_CMPL_ONLY));
    }
}

template<InterpretationQuery FLAG_REQUIRED>
bool checkConstraints(std::vector<InterpretationResolution>&& flags) {
    assert(flags.size());

    InterpretationResolution flag = flags.front();
    return detail::checkConstraints<FLAG_REQUIRED>(flag);
}

template<InterpretationQuery FLAG_REQUIRED_A, InterpretationQuery FLAG_REQUIRED_B, InterpretationQuery... FLAGS>
bool checkConstraints(std::vector<InterpretationResolution>&& flags) {
    assert(flags.size());

    InterpretationResolution flag = flags.front();
    flags.pop_back();

    if (detail::checkConstraints<FLAG_REQUIRED_A>(flag)){
        return checkConstraints<FLAG_REQUIRED_B, FLAGS...>(move(flags));
    }

    return false;
}

void
setSpecializedOperator(const Expression& expression, const InterpretationOperator& op){
    if (Attachments::exists<Expression, InterpretationData>(expression)){
        InterpretationData& data = Attachments::get<Expression, InterpretationData>(expression);
        data.op = op;

    }   else {
        Attachments::put<Expression, InterpretationData>(expression, {BOTH, op});
    }

    compilation::Transformations::subscribe<Expression, compilation::TargetInterpretation>(expression);
}

InterpretationResolution
recognizeTags(const map<std::string, Expression>& tags){
    auto i = tags.find("interpretation");
    if (i== tags.end()){
        return BOTH;
    }

    assert(i->second.op == Operator::CALL);
    const string& cmd = i->second.operands.at(0).getValueString();

    //TODO make consistent names  of annotation and resolution
    if        (cmd == "force"){
        return INTR_ONLY;

    } else if (cmd == "suppress"){
        return CMPL_ONLY;
    }

    return BOTH;
}

void
recognizeTags(const Expression& e){
    InterpretationData tag{recognizeTags(e.tags), NONE};
    Attachments::put<Expression, InterpretationData>(e, tag);
}

InterpretationResolution
recognizeTags(const ManagedFnPtr& f){
    return recognizeTags(f->getTags());
}

InterpretationPass::InterpretationPass(PassManager* manager)
        : AbstractPass(manager) {}

InterpretationResolution
InterpretationPass::process(const Expression& expression, PassContext context, const std::string& decl){
    recognizeTags(expression);

    InterpretationResolution resolution = BOTH;

    switch (expression.__state){

        case Expression::NUMBER:
        case Expression::STRING: {
            break;
        }

        case Expression::IDENT: {
            resolution = Parent::processSymbol(expression.getValueString(), context);
            break;
        }

        case Expression::COMPOUND:
            break;

        default: { resolution = INTR_ONLY; break;}
    }

    if (expression.__state == Expression::COMPOUND)
    switch(expression.op){
        case Operator::EQU:
        case Operator::NE: {
            InterpretationResolution left =  process(expression.operands[0], context);
            InterpretationResolution right = process(expression.operands[1], context);

            resolution = unify(left, right);
            break;
        }

        case Operator::LOGIC_AND: {
            assert(expression.operands.size() == 1);
            resolution = process (expression.operands[0], context);
            break;
        }

        case Operator::CALL: {
              //TODO cope with static/dynamic context

            for (const Expression &op: expression.getOperands()) {
                resolution = unify(resolution, process(op, context));
            }

            list<ManagedFnPtr> callees = man->root->getFunctionVariants(expression.getValueString());
            if (callees.size()!=1){
                resolution = CMPL_ONLY;
                break;
            }

            ManagedFnPtr callee = callees.front();
            const Symbol& symbCalleeFunc{0, callee->getEntryScope()};

            //recursion-aware processing:
            //  - skip self recursion
            const Symbol& symbSelfFunc{0, context.function->getEntryScope()};
            if (symbSelfFunc == symbCalleeFunc){
                break;
            }

            //  - in order to recognize indirect recursion mark this function resolution as POSTPONED
            auto& cache = getSymbolCache();
            if (!cache.isCached(symbSelfFunc)){
                cache.setCachedValue(symbSelfFunc, POSTPONED);
            }

            InterpretationResolution resCallee = process(callee);
            if (resCallee == POSTPONED){
                assert(false && "Indirect recursion detected: can't decide on interpretation resolution");
            }

            resolution = unify(resolution, resCallee);
            break;
        }

        case Operator::IF:{
            InterpretationResolution flagCondition = process(expression.getOperands()[0], context);
            InterpretationResolution flagScope1 = Parent::process(expression.blocks.front(), context);
            InterpretationResolution flagScope2 = Parent::process(expression.blocks.back(), context);

            //special case: IF_INTERPRET_CONDITION
            if (checkConstraints<QUERY_INTR_ONLY>({flagCondition})){
                setSpecializedOperator(expression, IF_INTERPRET_CONDITION);
                flagCondition = BOTH;
            }

            resolution = unify(flagCondition, flagScope1, flagScope2);
            break;
        }

        case Operator::FOLD: {
            InterpretationResolution flagInput = process(expression.getOperands()[0], context);
            InterpretationResolution flagAccumInit = process(expression.getOperands()[1], context);

            CodeScope* scopeBody = expression.blocks.front();
            const std::string& nameEl = expression.bindings[0];
            getSymbolCache().setCachedValue(scopeBody->findSymbol(nameEl), InterpretationResolution(flagInput));

            const std::string& nameAccum = expression.bindings[1];
            getSymbolCache().setCachedValue(scopeBody->findSymbol(nameAccum), InterpretationResolution(flagAccumInit));

            InterpretationResolution flagBody = Parent::process(expression.blocks.front(), context);

            //special case: FOLD_INTERPRET_INPUT
            if (checkConstraints<QUERY_INTR_ONLY>({flagInput})){
                setSpecializedOperator(expression, FOLD_INTERPRET_INPUT);
                flagInput = BOTH;
            }

            resolution = unify(flagInput, flagAccumInit, flagBody);
            break;
        }

        case Operator::INDEX: {
            resolution = unify(
                process(expression.operands[0], context),
                Parent::processSymbol(expression.getValueString(), context)
            );

            break;
        }

        case Operator::SWITCH: {
            InterpretationResolution flagCondition = process(expression.operands[0], context);
            bool hasDefaultCase = expression.operands[1].op == Operator::CASE_DEFAULT;


            //determine conditions resolution
            InterpretationResolution flagHeaders = flagCondition;
            for (size_t size = expression.operands.size(), i= hasDefaultCase? 2: 1; i<size; ++i){
                const Expression& exprCase = expression.operands[i];

                flagHeaders = unify(flagHeaders, Parent::process(exprCase.blocks.front(), context));
            }

            if (checkConstraints<QUERY_INTR_ONLY>({flagHeaders})){
                setSpecializedOperator(expression, SWITCH_INTERPRET_CONDITION);
                flagHeaders = BOTH;
            }

            //determine body resolutions
            resolution = flagHeaders;
            for (size_t size = expression.operands.size(), i= 1; i<size; ++i){
                const Expression& exprCase = expression.operands[i];

                resolution = unify(resolution, Parent::process(exprCase.blocks.back(), context));
            }

            break;
        }

        case Operator::LIST:
        case Operator::LIST_NAMED: {
            for (const Expression &op: expression.getOperands()) {
                resolution = unify(resolution, process(op, context));
            }

            break;
        }

        default: {
            resolution = CMPL_ONLY;
            Parent::process(expression, context, decl);
            break;
        }
    }

    InterpretationResolution resolutionExpected = Attachments::get<Expression, InterpretationData>(expression, {BOTH, NONE})
            .resolution;

    resolution = unify(resolution, resolutionExpected);
    if (resolution == INTR_ONLY){
        Attachments::put<Expression, InterpretationData>(expression, {INTR_ONLY, NONE});
        compilation::Transformations::subscribe<Expression, compilation::TargetInterpretation>(expression);
    }


    return resolution;
}

    InterpretationResolution
    InterpretationPass::process(ManagedFnPtr function){
        InterpretationResolution resExpected = recognizeTags(function);

        //mark preliminary function resolution same as expected
        if (resExpected != BOTH){
            const Symbol& symbSelfFunc{0, function->getEntryScope()};
            getSymbolCache().setCachedValue(symbSelfFunc, move(resExpected));
        }

        CodeScope* entry = function->getEntryScope();
        auto& cache = getSymbolCache();
        std::vector<std::string> args = entry->__bindings;

        for (int argNo = 0, size = args.size(); argNo< size; ++argNo){
            Symbol symbArg = entry->findSymbol(args[argNo]);
            InterpretationResolution resArg = recognizeTags(entry->findDeclaration(symbArg).tags);
            cache.setCachedValue(symbArg, move(resArg));
        }

        InterpretationResolution resActual  = Parent::process(function);
        return unify(resActual, resExpected);
    }
}
-