/*
 * 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 October 7, 2017
 */

/**
 * \file  src/compilation/polymorph.h
 * \brief Polymorphism-aware compilation routines 
 */

#ifndef POLYMORPHCOMPILER_H
#define POLYMORPHCOMPILER_H

#include "pass/compilepass.h"
#include "query/polymorph.h"
#include "compilation/latetranscend.h"
#include "compilation/targetinterpretation.h"
#include "analysis/utils.h"
#include "aux/expressions.h"

namespace xreate{
namespace polymorph{

//typedef Expression Selector;

/** \brief An instance of \ref compilation::IFnInvocation  to manage polymorphic functions invocation*/
//class PolymorphFnInvocation: public compilation::IFnInvocation{
//public:
//  PolymorphFnInvocation(const latereasoning::LateAnnotation& selector,
//                        std::list<ManagedFnPtr> calleeSpecializations,
//                        CompilePass* pass,
//                        PolymorphQuery* query,
//                        LLVMLayer* llvm,
//                        latereasoning::LateReasoningCompiler* compiler);
//
//  llvm::Value* operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl = "");
//
//private:
//  latereasoning::LateAnnotation __selector;
//  std::list<ManagedFnPtr> __calleeSpecializations;
//
//  CompilePass* __pass;
//  PolymorphQuery* __query;
//  LLVMLayer* __llvm;
//  latereasoning::LateReasoningCompiler* __compiler;
//} ;

/** 
 * \brief Polymorphism aware \ref xreate::compilation::IBruteScope decorator
 * \implements xreate::compilation::IBruteScope
 */

bool isFnGuarded(const std::string& fn, const AST& root);

template <class Parent>
class PolymorphBruteScopeDecorator: public Parent{
public:
  PolymorphBruteScopeDecorator(const CodeScope * const codeScope,
                               compilation::IBruteFunction* f,
                               CompilePass* compilePass)
  : Parent(codeScope, f, compilePass){ }

protected:
  compilation::IFnInvocation*
  findFunction(const Expression& opCall) override{
    LLVMLayer* llvm = Parent::pass->man->llvm;
    AST* ast = Parent::pass->man->root;
    TranscendLayer* transcend = Parent::pass->man->transcend;

    //Check does invocation require guards satisfaction
    const std::string& calleeName = opCall.getValueString();
    if(!isFnGuarded(calleeName, *ast)){
      return Parent::findFunction(opCall);
    }

    //Several specializations
    PolymorphQuery* query = dynamic_cast<PolymorphQuery*> (
        Parent::pass->man->transcend->getQuery(QueryId::PolymorphQuery));

    //Fill up a dict of all possible specializations
    const std::list<ManagedFnPtr>& specs = ast->getFnSpecializations(calleeName);
    std::map<std::string, ManagedFnPtr> dictSpecializations;
    for(ManagedFnPtr specFn : specs) {
      const TypeAnnotation& specT = specFn->guard.type;
      assert(specFn->guard.isValid());
      assert(specT.__operator == TypeOperator::VARIANT);

      unsigned int variantId = specFn->guard.getValueDouble();
      std::string specStr = specT.fields.at(variantId);
      dictSpecializations.emplace(specStr, specFn);
    }

    auto supply = query->getFnSupply(ASTSite{opCall.id});
    Gringo::Symbol supplyRaw = std::get<0>(supply);
    ExpandedType supplyT = std::get<1>(supply);
    Expression supplyE = analysis::representTransExpression(supplyRaw, supplyT, transcend);

    const std::string& specStr = supplyT->fields.at(supplyE.getValueDouble());
    ManagedFnPtr specFn = dictSpecializations.at(specStr);
    const ExpandedType supplyDataT = ast->expandType(supplyT->__operands.at(supplyE.getValueDouble()));

    compilation::BruteFnInvocation* fnInvocDefault = new compilation::BruteFnInvocation(
      Parent::pass->getBruteFn(specFn)->compile(), llvm
    );
    if (!supplyDataT->isValid()){
      return fnInvocDefault;
    }

    const Expression& supplyDataE = getVariantData(supplyE, supplyT);
    llvm::Value* supplyDataRaw = Parent::process(supplyDataE);
    return new compilation::HiddenArgsFnInvocation({supplyDataRaw}, fnInvocDefault);
  }
} ;

template <class Parent>
class PolymorphBruteFnDecorator: public Parent{
public:
  PolymorphBruteFnDecorator(ManagedFnPtr f, CompilePass *p): Parent(f, p){}

protected:
  std::vector<llvm::Type *>
  prepareSignature() override {
    std::vector<llvm::Type *> &&signature = Parent::prepareSignature();
    if (!Parent::function->guard.isValid()) return signature;

    AST* ast = Parent::pass->man->root;
    unsigned int guardDataId = signature.size() + 1;
    const Expression& guardE = Parent::function->guard;
    assert(guardE.op == Operator::VARIANT);

    const ExpandedType guardT = ast->expandType(guardE.type.__operands.at(guardE.getValueDouble()));
    if (!guardT->isValid()) return signature;
    if (!guardE.bindings.size()) return signature;
    assert(guardE.bindings.size() == 1);

    std::string guardArgName = guardE.bindings.at(0);
    llvm::Type *guardTRaw = Parent::pass->man->llvm->toLLVMType(guardT);

    Expression argBindE;
    argBindE.type = guardT;

    Parent::function->addBinding(
      Atom<Identifier_t>(std::move(guardArgName)),
      std::move(argBindE),
      guardDataId
    );
    signature.push_back(guardTRaw);
    return signature;
  }
};

}
} //end of xreate::polymorph

#endif /* POLYMORPHCOMPILER_H */

