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

/*
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on June 23, 2018, 2:51 PM
 *
 * \file    latex.h
 * \brief   latex
 */

#ifndef LATEX_H
#define LATEX_H

#include "compilation/latereasoning.h"
#include "compilation/interpretation-instructions.h"
#include "query/latex.h"
#include "pass/compilepass.h"
#include "analysis/interpretation.h"

namespace xreate{
namespace latex{

ExpandedType
getSubjectDomain(const std::string& subject, LatexQuery* query);

/** \brief Latex(Late Context)-enabled decorator for IFunctionUnit
 *  \extends IFunctionUnit
 */
template<class Parent>
class LatexBruteFunctionDecorator: public Parent{
public:

    LatexBruteFunctionDecorator(ManagedFnPtr f, CompilePass* p)
    : Parent(f, p){
        __query = reinterpret_cast<LatexQuery*> (Parent::pass->man->transcend->getQuery(QueryId::LatexQuery));
    }

protected:

    std::vector<llvm::Type*>
    prepareSignature(){
        std::vector<llvm::Type*>&& signature = Parent::prepareSignature();

        const Demand& demand = __query->getFnDemand(Parent::function->getName());
        signature.reserve(signature.size() + demand.size());

        for(const std::string& subject: demand){
            const ExpandedType& subjectT = getSubjectDomain(subject, __query);
            Expression bindingE;
            bindingE.type = subjectT;
            std::string argCaption = std::string("latex_") + subject;
            Parent::function->addBinding(Atom<Identifier_t>(std::string(argCaption)), std::move(bindingE));

            llvm::Type* subjectTRaw = Parent::pass->man->llvm->toLLVMType(subjectT);
            signature.push_back(subjectTRaw);
        }

        return signature;
    }

public:
    LatexQuery* __query;
};

class ExtraArgsFnInvocation: public compilation::IFnInvocation{
public:

    ExtraArgsFnInvocation(std::vector<llvm::Value*> argsLatex, compilation::IFnInvocation* parent)
    : __argsLatex(argsLatex), __parent(parent){ }

    llvm::Value* operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl = "");

private:
    std::vector<llvm::Value*> __argsLatex;
    compilation::IFnInvocation* __parent;
};

template<class Parent>
class LatexBruteScopeDecorator: public Parent{
public:

    LatexBruteScopeDecorator(const CodeScope * const codeScope, compilation::IFunctionUnit* f, CompilePass* compilePass)
    : Parent(codeScope, f, compilePass){ }

    compilation::IFnInvocation*
    findFunction(const Expression& opCall){
        compilation::IFnInvocation* invocDefault = Parent::findFunction(opCall);
        const std::string& calleeName = opCall.getValueString();
        LatexQuery* query = reinterpret_cast<LatexQuery*> (Parent::pass->man->transcend->getQuery(QueryId::LatexQuery));

        const Demand& fnCalleeDemand = query->getFnDemand(calleeName);
        if(!fnCalleeDemand.size()) return invocDefault;

        //prepare latex arguments
        std::vector<llvm::Value*> argsLatex;
        argsLatex.reserve(fnCalleeDemand.size());

        for(const std::string& subject: fnCalleeDemand){
            ExpandedType subjectT = getSubjectDomain(subject, query);
            llvm::Type* subjectTRaw = Parent::pass->man->llvm->toLLVMType(subjectT);
            const latereasoning::LateAnnotation& decision = query->getDecision(subject, Parent::scope);

            compilation::Context ctx{this, Parent::function, Parent::pass};
            interpretation::InterpretationScope* scopeIntrpr =
                Parent::pass->targetInterpretation->transformContext(ctx);
            latereasoning::LateReasoningCompiler* compiler
                = new latereasoning::LateReasoningCompiler(scopeIntrpr, ctx);

            llvm::Value* subjectRaw = compiler->compileAutoExpand(
                decision,
                subjectTRaw,
                subject,
                [&](const Gringo::Symbol & decisionRaw){
                    const Expression& decisionE = interpretation::representTransExpression(
                        decisionRaw.args()[2], subjectT, Parent::pass->man->transcend);
                    Attachments::put<TypeInferred>(decisionE, subjectT);
                    return Parent::process(decisionE, subject);
                });

            argsLatex.push_back(subjectRaw);
        }

        return new ExtraArgsFnInvocation(std::move(argsLatex), invocDefault);
    }
};

}
} //end of namespace xreate::context

#endif /* LATEX_H */

