/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * File:   interpretationpass.cpp
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created on July 5, 2016, 5:21 PM
 */

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

using namespace std;

namespace xreate{

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

namespace interpretation{
enum InterpretationQuery{QUERY_INTR_ONLY, QUERY_CMPL_ONLY};

namespace details {
    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));
    }

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

        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 ANY;
    }
}

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== ANY){
        return unify(flagB, flags...);
    }

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

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

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

    InterpretationResolution flag = flags.front();
    return details::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 (details::checkConstraints<FLAG_REQUIRED_A>(flag)){
        return checkConstraints<FLAG_REQUIRED_B, FLAGS...>(move(flags));
    }

    return false;
}

bool
InterpretationData::isDefault() const{
    return (resolution == ANY && op == NONE);
}

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

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

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

    Attachments::init<FunctionInterpretationData>();
    Attachments::init<InterpretationData>();
}

void InterpretationPass::run(){
    ManagedFnPtr f = man->root->begin<Function>();
    auto& visitedSymbols = getSymbolCache();

    while (f.isValid()) {
        const Symbol& symbolFunction{ScopedSymbol::RetSymbol, f->getEntryScope()};

        if (!visitedSymbols.isCached(symbolFunction)){
            visitedSymbols.setCachedValue(symbolFunction, process(f));
        }

        ++f;
    }
}

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

    InterpretationResolution resolution = ANY;
    InterpretationOperator op = NONE;

    switch (expression.__state){

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

        case Expression::IDENT: {
            resolution = Parent::processSymbol(Attachments::get<Symbol>(expression), context);
            break;
        }

        case Expression::COMPOUND:
            break;

        default: { resolution = CMPL_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
            //TODO BUG here: if several variants they all are processed as CMPL careless of signature
            list<ManagedFnPtr> callees = man->root->getFunctionVariants(expression.getValueString());
            if (callees.size()!=1){
                resolution = CMPL_ONLY;
                break;
            }

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

            //recursion-aware processing:
            //  - skip self recursion
            const Symbol& symbSelfFunc{ScopedSymbol::RetSymbol, context.function->getEntryScope()};
            if (!(symbSelfFunc == symbCalleeFunc)){
                InterpretationResolution resCallee = processFnCall(callee, context);
                assert(resCallee != FUNC_POSTPONED && "Indirect recursion detected: can't decide on interpretation resolution");

                resolution = unify(resolution, resCallee);
            }

            //check arguments compatibility
            const FunctionInterpretationData& calleeSignature = FunctionInterpretationHelper::getSignature(callee);
            for (size_t op=0, size = expression.operands.size(); op < size; ++op){
                const Expression &operand = expression.operands[op];
                InterpretationResolution argActual =  process(operand, context);
                InterpretationResolution argExpected =  calleeSignature.signature[op];

                //TODO use args unification result to properly process function call
                unify(argActual, argExpected);
            }

            if (FunctionInterpretationHelper::needPartialInterpretation(callee)){
                op= CALL_INTERPRET_PARTIAL;
            }

            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})){
                op= IF_INTERPRET_CONDITION;
                flagCondition = ANY;
            }

            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];
            Symbol symbEl{ScopedSymbol{scopeBody->__identifiers.at(nameEl), versions::VERSION_NONE}, scopeBody};
            getSymbolCache().setCachedValue(symbEl, InterpretationResolution(flagInput));

            const std::string& nameAccum = expression.bindings[1];
            Symbol symbAccum{ScopedSymbol{scopeBody->__identifiers.at(nameAccum), versions::VERSION_NONE}, scopeBody};
            getSymbolCache().setCachedValue(symbAccum, InterpretationResolution(flagAccumInit));

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

            //special case: FOLD_INTERPRET_INPUT
            if (checkConstraints<QUERY_INTR_ONLY>({flagInput})){
                op= FOLD_INTERPRET_INPUT;
                flagInput = ANY;
            }

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

        case Operator::INDEX: {
            resolution = unify(
                process(expression.operands[0], context),
                process(expression.operands[1], 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})){
                op= SWITCH_INTERPRET_CONDITION;
                flagHeaders = ANY;
            }

            //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::SWITCH_VARIANT: {
            InterpretationResolution resolutionCondition = process(expression.operands.at(0), context);
            resolution= resolutionCondition;

            if (checkConstraints<QUERY_INTR_ONLY>({resolution})){
                op= SWITCH_VARIANT;
                resolution = ANY;
            }

            const string identCondition = expression.bindings.front();
            for(auto scope: expression.blocks){
                //set binding resolution
                ScopedSymbol symbolInternal = scope->getSymbol(identCondition);
                getSymbolCache().setCachedValue(Symbol{symbolInternal, scope}, InterpretationResolution(resolutionCondition));

                resolution = unify(resolution, Parent::process(scope, context));
            }

            for(auto scope: expression.blocks){
                resolution = unify(resolution, Parent::process(scope, context));
            }
            break;
        }

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

            break;
        }

        case Operator::VARIANT: {
            if(expression.getOperands().size()){
                resolution = process(expression.getOperands().front(), context);
            } else {
                resolution = ANY;
            }

            break;
        }

        default: {
            resolution = CMPL_ONLY;

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

            for (CodeScope* scope: expression.blocks) {
                Parent::process(scope, context);
            }

            break;
        }
    }

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

    resolution = unify(resolution, resolutionExpected);
    if (resolution != resolutionExpected && (op!=NONE || resolution == INTR_ONLY)){
        Attachments::put<InterpretationData>(expression, {resolution, op});
    }

    return resolution;
}

    InterpretationResolution
    InterpretationPass::processFnCall(ManagedFnPtr function, PassContext context){
        return process(function);
    }

    InterpretationResolution
    InterpretationPass::process(ManagedFnPtr function){
        CodeScope* entry = function->getEntryScope();
        std::vector<std::string> arguments = entry->__bindings;
        const Symbol& symbSelfFunc{ScopedSymbol::RetSymbol, function->getEntryScope()};
        auto& cache = getSymbolCache();

        if (cache.isCached(symbSelfFunc))
            return cache.getCachedValue(symbSelfFunc);

        const FunctionInterpretationData& fnSignature = FunctionInterpretationHelper::getSignature(function);
        InterpretationResolution fnResolutionExpected = details::recognizeTags(function->getTags());

        //mark preliminary function resolution as expected
        if (fnResolutionExpected != ANY){
            cache.setCachedValue(symbSelfFunc, move(fnResolutionExpected));

        } else {
            //  - in order to recognize indirect recursion mark this function resolution as POSTPONED
            cache.setCachedValue(symbSelfFunc, FUNC_POSTPONED);
        }

        //set resolution for function arguments as expected
        for (int argNo = 0, size = arguments.size(); argNo< size; ++argNo){
            Symbol symbArg{ScopedSymbol{entry->__identifiers.at(arguments[argNo]), versions::VERSION_NONE}, entry};
            cache.setCachedValue(symbArg, InterpretationResolution(fnSignature.signature[argNo]));
        }

        PassContext context;
        context.function = function;
        context.scope = entry;
        InterpretationResolution resActual  = process(CodeScope::getDeclaration(symbSelfFunc), context);
        resActual =  unify(resActual, fnResolutionExpected);
        return cache.setCachedValue(symbSelfFunc, move(resActual));
    }

    const FunctionInterpretationData
    FunctionInterpretationHelper::getSignature(ManagedFnPtr function){
        if (Attachments::exists<FunctionInterpretationData>(function)){
            return Attachments::get<FunctionInterpretationData>(function);
        }

        FunctionInterpretationData&& data = recognizeSignature(function);
        Attachments::put<FunctionInterpretationData>(function, data);
        return data;
    }

    FunctionInterpretationData
    FunctionInterpretationHelper::recognizeSignature(ManagedFnPtr function){
        CodeScope* entry = function->__entry;
        FunctionInterpretationData result;
        result.signature.reserve(entry->__bindings.size());

        bool flagPartialInterpretation = false;
        for(size_t no=0, size=entry->__bindings.size(); no < size; ++no){
            const std::string& argName = entry->__bindings[no];
            Symbol symbArg{ScopedSymbol{entry->__identifiers.at(argName), versions::VERSION_NONE}, entry};

            const Expression& arg = CodeScope::getDeclaration(symbArg);

            InterpretationResolution argResolution = details::recognizeTags(arg.tags);
            flagPartialInterpretation |= (argResolution == INTR_ONLY);

            result.signature.push_back(argResolution);
        }
        result.flagPartialInterpretation = flagPartialInterpretation;
        return result;
    }

    bool FunctionInterpretationHelper::needPartialInterpretation(ManagedFnPtr function){
        const FunctionInterpretationData& data = getSignature(function);
        return data.flagPartialInterpretation;
    }
}} //end of namespace xreate::interpretation

