/* 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
 */

/**
 * \file    interpretationpass.h
 * \brief   Interpretation analysis: determines what parts of a code could be interpreted
 */

#include "ast.h"
#include "pass/interpretationpass.h"
#include "compilation/targetinterpretation.h"
#include "analysis/utils.h"
#include "analysis/predefinedanns.h"

#include <bits/stl_vector.h>

//DEBT implement InterpretationPass purely in transcend
//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) {
  std::list<Expression> tagsL;
  auto predefined = analysis::PredefinedAnns::instance();
  for(const auto& tag: tags){tagsL.push_back(tag.second);}
  const Expression& tagI12nE = analysis::findAnnById(
    (unsigned) analysis::PredefinedAnns::ExprAnnotations::I12N,
    ExpandedType(predefined.exprAnnsT),
    tagsL);

  if (!tagI12nE.isValid()) return ANY;
  analysis::PredefinedAnns::I12ModeTag modeI12n = (analysis::PredefinedAnns::I12ModeTag) tagI12nE.operands.at(0).getValueDouble();

  switch(modeI12n){
    case analysis::PredefinedAnns::I12ModeTag::ON:
      return INTR_ONLY;
    case analysis::PredefinedAnns::I12ModeTag::OFF:
      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<I12nFunctionSpec>();
    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 opNo=NONE;

    switch(expression.__state) {

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

    case Expression::IDENT:
    {
        resolution=Parent::processSymbol(Attachments::get<IdentifierSymbol>(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:
        {
            size_t sizeOperands = expression.operands.size();
            std::vector<InterpretationResolution> operands;
            operands.reserve(sizeOperands);
            
            for(size_t opNo=0; opNo<sizeOperands; ++opNo) {
                const Expression &operand=expression.operands[opNo];
                operands.push_back(process(operand, context));
            }
                        
            //TODO cope with static/dynamic context
            //TODO BUG here: if several variants they all are processed as CMPL regardless of signature
            list<ManagedFnPtr> callees=man->root->getFnSpecializations(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 I12nFunctionSpec& calleeSignature=FunctionInterpretationHelper::getSignature(callee);
            for(size_t opNo=0; opNo<sizeOperands; ++opNo){
                InterpretationResolution argActual=operands.at(opNo);
                InterpretationResolution argExpected=calleeSignature.signature[opNo];

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

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

            break;
        }

        case Operator::CALL_INTRINSIC:
        {
          switch((IntrinsicFn) expression.getValueDouble()){
            case IntrinsicFn::REC_FIELDS: resolution = INTR_ONLY; break;
            default: resolution=CMPL_ONLY; break;
          }
          break;
        }
        
        case Operator::QUERY:
        {
            resolution=INTR_ONLY;
            break;
        }
        
        case Operator::QUERY_LATE:
        {
            InterpretationResolution predicate=process(expression.operands[0], context);
            unify(predicate, INTR_ONLY);
            
            CodeScope* exprBody=expression.blocks.front();
            const std::string& argName=expression.bindings[0];
            Symbol argS = {
                ScopedSymbol{exprBody->__identifiers.at(argName), versions::VERSION_NONE}, 
                exprBody
            };
            getSymbolCache().setCachedValue(argS, INTR_ONLY);
            Parent::process(expression.blocks.front(), context);
            
            resolution = CMPL_ONLY;
            opNo=QUERY_LATE;
            break;
        }
        
        case Operator::SWITCH_LATE:
        {
            resolution = CMPL_ONLY;
            opNo = SWITCH_LATE;
            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})){
                opNo=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})){
                opNo=FOLD_INTERPRET_INPUT;
                flagInput=ANY;
            }

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

        case Operator::INDEX:
        {
            for(const Expression &opNo : expression.getOperands()) {
                resolution=unify(resolution, process(opNo, 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})){
                opNo=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})){
                opNo=SWITCH_VARIANT;
                resolution=ANY;
            }

            const string identCondition=expression.bindings.front();
            for(auto scope : expression.blocks) {
                //set binding resolution
                ScopedSymbol symbolInternal= scope->findSymbolByAlias(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:
        {
            for(const Expression &opNo : expression.getOperands()) {
                resolution=unify(resolution, process(opNo, 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 &opNo : expression.getOperands()) {
                process(opNo, context);
            }

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

            break;
        }
        }

    InterpretationData dataExpected=
            Attachments::get<InterpretationData>(expression,{ANY, NONE});

    resolution=unify(resolution, dataExpected.resolution);
    if(resolution!=dataExpected.resolution || opNo != dataExpected.op ){
        Attachments::put<InterpretationData>(expression,{resolution, opNo});
    }

    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 I12nFunctionSpec& 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::getDefinition(symbSelfFunc), context);
    resActual=unify(resActual, fnResolutionExpected);
    return cache.setCachedValue(symbSelfFunc, move(resActual));
}

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

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

I12nFunctionSpec
FunctionInterpretationHelper::recognizeSignature(ManagedFnPtr function) {
    CodeScope* entry=function->__entry;
    I12nFunctionSpec 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::getDefinition(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 I12nFunctionSpec& data=getSignature(function);
    return data.flagPartialInterpretation;
}
}
} //end of namespace xreate::interpretation


/** \class xreate::interpretation::InterpretationPass
 *
 * The class encapsulates *Interpretation Analysis* to support [Interpretation](/d/concepts/interpretation/).
 *
 * It recognizes program functions, expressions, instructions eligible for interpretation
 * and stores the output in \ref Attachments<I12nFunctionSpec> and \ref Attachments<InterpretationData>
 *
 * There are number of instructions currently eligible for interpretation:
 *  - Basic literals: numbers and strings
 *  - Compounds: lists, structs, variants
 *  - Non-versioned identifiers
 *  - Comparison and logic operators
 *  - %Function calls
 *  - `query` intrinsic function calls
 *  - Branching: `if`, `loop fold`, `switch`, `switch variant` statements
 *
 * Some of these instructions are eligible also for *late interpretation* to allow coupling
 * of compiled instructions with interpreted ones, those are:
 *  - Partial function calls
 *  - Branching: `if`, `loop fold`, `switch`, `switch variant` statements
 *
 * \sa xreate::interpretation::TargetInterpretation, [Interpretation Concept](/d/concepts/interpretation/)
 */
