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

LateContextCompiler2::LateContextCompiler2(compilation::FunctionUnit* f, CompilePass* p)
	: function(f), pass(p){
    
    ContextQuery* context = pass->queryContext;
    __sizeOfDemand = context->getFunctionDemand(function->function->getName()).size();
}

llvm::Function* 
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 
        llvm::dyn_cast<llvm::Function>(
            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(decisionRaw, vectorDecisions);
    
    return result;
}

llvm::Value* 
LateContextCompiler2::compileDecisionSelector(llvm::Value* selector, std::vector<llvm::Value*> vectorVariants, llvm::Value* variantDefault){
    //TODO implement variantDefault;
        assert(vectorVariants.size()>0);
        llvm::IRBuilder<>& builder = pass->man->llvm->builder;
        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);
}

llvm::Value*
LateContextCompiler2::compileDecisionSelectorAsSwitch(llvm::Value* selector, std::vector<llvm::Function*> vectorVariants, llvm::Value* 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 */
