/*
 * LateContext.cpp
 *
 *  Created on: Jan 8, 2016
 *      Author: pgess
 */

#include "pass/compilepass.h"
#include "compilation/latecontext.h"
#include "query/context.h"
#include "llvmlayer.h"

namespace xreate {

LateContext::LateContext(const std::string& function, CompilePass* compilePass)
	: pass(compilePass), domain(&compilePass->queryContext->getFunctionDomain(function))
{}

LateContext::~LateContext()
{}

llvm::Value*
LateContext::compileCheckContext(const Expression& e){
//	llvm::Value* environment;
//	LLVMLayer* llvm;
//
//	PackedExpression rep = __context.pack(e);
//	assert(storage.count(rep));
//
//	size_t id = storage.at(rep);
//	//TODO determine short name of expression
//	return llvm->builder.CreateExtractElement(environment, id, NAME("checkContext"+e.getValueString()));

	return nullptr;
}

llvm::Value*
LateContext::findFunction(const std::string name, ScopePacked scopeOuter, llvm::Function* functionOuter){
	LLVMLayer* llvm = pass->man->llvm;
	ContextQuery* queryContext = pass->queryContext;
	const FunctionSpecializations& specializations = queryContext->getFunctionSpecializations(name);

	//check external function
	if (!specializations.size()){
		return nullptr;
	}

	const std::list<Expression>& context = queryContext->getContext(scopeOuter);

	boost::optional<ManagedFnPtr> variant;
	for(const Expression& c: context){
		if (specializations.count(c)){
			assert(!variant);
			variant = specializations.at(c);
		}
	}

	if (variant) {
		return pass->getFunctionUnit(*variant)->compile();// RETURN compiled function unit
	}

	//require default variant if no appropriate static context exists
	assert(specializations.count(Expression()));
	llvm::Value* funcDefault = pass->getFunctionUnit(specializations.at(Expression()))->compile();

	//choose default variant if no late context exists
	if (!domain->size()) {
		return funcDefault;
	}

	//RUNTIME PART
	llvm::BasicBlock *blockCurrent = llvm->builder.GetInsertBlock();
	llvm::BasicBlock *blockEnd = llvm::BasicBlock::Create(llvm::getGlobalContext(), "ContextDeterminationEnd", functionOuter);

	llvm->builder.SetInsertPoint(blockEnd);
	llvm::Type* tyFuncCallee = funcDefault->getType();
	llvm::PHINode *ret =  llvm->builder.CreatePHI(tyFuncCallee, domain->size(), "callee");


	//check runtime context
	for (const Expression& c: *domain){
		if (specializations.count(c)){
			///if (domain[c]) return specialization.at(c) else
			llvm->builder.SetInsertPoint(blockCurrent);
			llvm::Value* cond;
			llvm::BasicBlock *blockNext = llvm::BasicBlock::Create(llvm::getGlobalContext(), "ContextDeterminationNext", functionOuter);
			llvm->builder.CreateCondBr(cond, blockEnd, blockNext);
			ret->addIncoming(pass->getFunctionUnit(specializations.at(c))->compile(), blockCurrent);
			blockCurrent = blockNext;
		}
	}

	//check default variant in runtime
	llvm->builder.SetInsertPoint(blockCurrent);
	llvm->builder.CreateBr(blockEnd);
	ret->addIncoming(funcDefault, blockCurrent);

	llvm->builder.SetInsertPoint(blockEnd);
	return ret;
}

llvm::Value*
LateContext::compileArgument(const std::string& callee, ScopePacked scopeOuter){
	LLVMLayer* llvm = pass->man->llvm;

	std::unordered_set<size_t> indexContextStatic;
	list<Expression> listContextStatic = pass->queryContext->getContext(scopeOuter);
	const ContextDomain* calleeDomain =  &pass->queryContext->getFunctionDomain(callee);

	llvm::Type* tyBool = llvm::Type::getInt1Ty(llvm::getGlobalContext());
	llvm::Type* tyInt32 = llvm::Type::getInt32Ty(llvm::getGlobalContext());

	llvm::Value* result = llvm->builder.CreateAlloca(tyBool, llvm::ConstantInt::get(tyInt32, calleeDomain->size()));
	llvm::Value* valueTrue = llvm::ConstantInt::get(tyBool, 1);
	llvm::Value* valueFalse = llvm::ConstantInt::get(tyBool, 0);

	for (const Expression& e: listContextStatic){
		auto i = calleeDomain->getExpressionId(e);
		if (i){
			result = llvm->builder.CreateInsertElement(result, llvm::ConstantInt::get(tyInt32, *i), valueTrue);
			indexContextStatic.insert(*i);
		}
	}

	for (size_t i=0, size=calleeDomain->size(); i<size; ++i){
		const Expression& e = calleeDomain->get(i);
		if (indexContextStatic.count(i))
			continue;

		auto posPrev = domain->getExpressionId(e);
		if (posPrev){
			llvm::Value* element = llvm->builder.CreateExtractElement(raw, llvm::ConstantInt::get(tyInt32, *posPrev));
			result = llvm->builder.CreateInsertElement(result, llvm::ConstantInt::get(tyInt32, i), element);
			continue;
		}

		result = llvm->builder.CreateInsertElement(result, llvm::ConstantInt::get(tyInt32, i), valueFalse);
	}

	return result;
}

//void
//LateContext::set(const Expression& e){
//	PackedExpression representation = 	__context.pack(e);
//
//	assert(__context.count(representation) == 0);
//	__context.emplace(move(representation), __context.size());
//}

} /* namespace xreate */
