/*
 * LateContextCompiler2.cpp
 *
 *  Created on: 10 февр. 2016
 *      Author: pgess
 */

//TOTEST default variants - do not enable specialization context in order to check default variant invocation

#include "latecontextcompiler2.h"
#include "llvmlayer.h"
#include "pass/compilepass.h"
#include "query/context.h"
#include <iostream>

using namespace std;

namespace xreate {

const string topicSpecializationAtom = "specialization";
const string topicDependencyAtom = "dependency";

typedef ExpressionSerialization<RequirementIntegralCode>::Code DomainId;
typedef ExpressionSerialization<RequirementIntegralCode>::Serializer Domain;

//TODO implement variantDefault;
llvm::Value* compileDecisionSelector(llvm::IRBuilder<>& builder, llvm::Value* selector, std::vector<llvm::Value*> vectorVariants, llvm::Value* variantDefault=nullptr){
    assert(vectorVariants.size()>0);
    llvm::IntegerType* ty32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());

    llvm::Type* tyElement = vectorVariants[0]->getType();
    llvm::Type* tyVariants = llvm::VectorType::get(tyElement, vectorVariants.size());
    llvm::Value* vectorRaw = llvm::ConstantVector::getNullValue(tyVariants);


    for(DomainId i=0; i<vectorVariants.size(); ++i){
        vectorRaw = builder.CreateInsertElement(vectorRaw, vectorVariants[i], llvm::ConstantInt::get(ty32, i));
    }

    return builder.CreateExtractElement(vectorRaw, selector);
}

LateContextCompiler2::LateContextCompiler2(compilation::FunctionUnit* f, CompilePass* p)
	: function(f), pass(p){

    ContextQuery* context = pass->queryContext;
    __sizeOfDemand = context->getFunctionDemand(function->function->getName()).size();
}

llvm::Value*
LateContextCompiler2::findFunction(const std::string& calleeName, llvm::Function* specializationDefault, ScopePacked scopeCaller){
        const string& functionName = function->function->getName();
        ContextQuery* context = pass->queryContext;
        llvm::IRBuilder<>& builder = pass->man->llvm->builder;

    const FunctionDemand& demand = context->getFunctionDemand(functionName);
    const std::list<ManagedFnPtr>& specializations = pass->man->root->getFunctionVariants(calleeName);

    //independent decision:
    Expression topic(Operator::CALL, {(Atom<Identifier_t>(string(topicSpecializationAtom))), (Atom<Identifier_t>(string(calleeName))), (Atom<Number_t>(scopeCaller))});
    assert(demand.right.count(topic) && "Can't determine specialization for the function");
    size_t topicId = demand.right.at(topic);
    llvm::Value* topicDecisionRaw = builder.CreateExtractValue(this->rawContextArgument, llvm::ArrayRef<unsigned>{(unsigned) topicId});

    const Domain& specializationsDomain= context->getTopicDomain(topic);

    std::vector<llvm::Function*> vectorVariants;
    vectorVariants.reserve(specializationsDomain.size());
    for (const ManagedFnPtr& f: specializations){
        if (!f->guardContext.isValid()) continue;
        const auto& variantId =  specializationsDomain.getIdOptional(f->guardContext);
        if (variantId){
            if (vectorVariants.size() < *variantId + 1) {
                vectorVariants.resize(*variantId + 1);
            }

            vectorVariants[*variantId] = pass->getFunctionUnit(f)->compile();
        }
    }

    return compileDecisionSelectorAsSwitch(topicDecisionRaw, vectorVariants, specializationDefault);
}

llvm::Value*
LateContextCompiler2::compileContextArgument(const std::string& callee, ScopePacked scopeCaller){
        const std::string& atomDependentDecision = Config::get("clasp.context.decisions.dependent");
        llvm::IRBuilder<>& builder = pass->man->llvm->builder;
        ContextQuery* context = pass->queryContext;

    const string& functionName = function->function->getName();
    const Decisions& dictStaticDecisions = context->getFinalDecisions(scopeCaller);

    const FunctionDemand& demandCallee = context->getFunctionDemand(callee);
    const FunctionDemand& demandSelf = context->getFunctionDemand(functionName);

    llvm::IntegerType* ty32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());
    llvm::Type* tyDemand = llvm::ArrayType::get(ty32, demandCallee.size());

    //builder.CreateAlloca(tyDemand, llvm::ConstantInt::get(ty32, 1));
    llvm::Value* res = llvm::ConstantArray::getNullValue(tyDemand);

    for (size_t i=0, size = demandCallee.size(); i<size; ++i){
        const Expression& topic = demandCallee.left.at(i);
        llvm::Value* decisionRaw;

        if          (demandSelf.right.count(topic)){
            //TOTEST decision propagation
            //propagate decision
            const size_t& topicId = demandSelf.right.at(topic);
            decisionRaw = builder.CreateExtractValue(this->rawContextArgument, llvm::ArrayRef<unsigned>{(unsigned) topicId});

        } else if   (dictStaticDecisions.count(topic)){
            //static final decision:
            const Expression& decision = dictStaticDecisions.at(topic);
            const Domain& domainOfTopic = context->getTopicDomain(topic);
            const DomainId& decisionCode = domainOfTopic.getId(decision);
            decisionRaw = llvm::ConstantInt::get(ty32, decisionCode);

        } else {
            //dependent decision
            decisionRaw = compileDependentDecision(topic, scopeCaller);
        }

        res = builder.CreateInsertValue(res, decisionRaw, llvm::ArrayRef<unsigned>{(unsigned) i});
    }

    return res;
}

llvm::Value*
LateContextCompiler2::compileDependentDecision(const Expression& topic, ScopePacked scopeCaller){
        const string& functionName = function->function->getName();
        ContextQuery* context = pass->queryContext;
        llvm::IRBuilder<>& builder = pass->man->llvm->builder;
        llvm::IntegerType* ty32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());

    const FunctionDemand& demandSelf = context->getFunctionDemand(functionName);
    const Expression topicDependency = Expression(Operator::CALL, {Atom<Identifier_t>(string(topicDependencyAtom)), topic, Atom<Number_t>(scopeCaller)});

    const Domain& demandOfTopic = context->getTopicDomain(topic);
    const Domain& domainOfTopicDependency = context->getTopicDomain(topicDependency);

    //dependent decision
    vector<llvm::Value*> vectorDecisions(domainOfTopicDependency.size(), llvm::UndefValue::get(ty32));
    for (const std::pair<Expression, Expression>& entry: context->getDependentDecision(scopeCaller,topic)){
        vectorDecisions[domainOfTopicDependency.getId(entry.first)]= llvm::ConstantInt::get(ty32, demandOfTopic.getId(entry.second));
    }

    size_t topicDependencyId = demandSelf.right.at(topicDependency);
    llvm::Value* decisionRaw = builder.CreateExtractValue(this->rawContextArgument, llvm::ArrayRef<unsigned>{(unsigned) topicDependencyId});

    auto result = compileDecisionSelector(pass->man->llvm->builder, decisionRaw, vectorDecisions);

    return result;
}



llvm::Value*
LateContextCompiler2::compileDecisionSelectorAsSwitch(llvm::Value* selector, std::vector<llvm::Function*> vectorVariants, llvm::Function* variantDefault){
        llvm::IRBuilder<>& builder = pass->man->llvm->builder;
        llvm::IntegerType* ty32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());

    llvm::BasicBlock* blockDefault = llvm::BasicBlock::Create(llvm::getGlobalContext(), "caseDefault", this->function->raw);
    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(llvm::getGlobalContext(), "VariantDeterminationEnd", this->function->raw);

    llvm::SwitchInst* instrSwitch = builder.CreateSwitch(selector, blockDefault, vectorVariants.size());

    builder.SetInsertPoint(blockEpilog);
    llvm::PHINode *result =  builder.CreatePHI(variantDefault->getType(), vectorVariants.size(), "callee");

    for (size_t i=0; i<vectorVariants.size(); ++i){
        llvm::BasicBlock* blockCase = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", this->function->raw);
        builder.SetInsertPoint(blockCase);
        builder.CreateBr(blockEpilog);
        result->addIncoming(vectorVariants[i], blockCase);
        instrSwitch->addCase(llvm::ConstantInt::get(ty32, i), blockCase);
    }

    builder.SetInsertPoint(blockDefault);
    builder.CreateBr(blockEpilog);
    result->addIncoming(variantDefault, blockDefault);

    builder.SetInsertPoint(blockEpilog);
    return result;
}

size_t
LateContextCompiler2::getFunctionDemandSize() const {
    return __sizeOfDemand;
}

} /* namespace xreate */
