/*
 * 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/.
 *
 * latereasoning.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on May 26, 2018, 3:54 PM
 */

#include "compilation/latereasoning.h"
#include "aux/latereasoning.h"
#include "compilation/scopedecorators.h"
#include "analysis/typeinference.h"
#include <vector>

using namespace xreate::compilation;
using namespace std;

namespace xreate{ namespace latereasoning {

llvm::Value*
LateReasoningCompiler::compile(const Expression& expr, const std::string& identHint){
    #define HINT(x) (identHint.empty()? x : identHint)

    LLVMLayer* llvm = context.pass->man->llvm;
    compilation::ICodeScopeUnit* scope = context.scope;
    AST* root = context.pass->man->root;
    llvm::IRBuilder<>& builder = llvm->builder;
    compilation::IFunctionUnit* function = context.function;
    llvm::Type* typI8= llvm::Type::getInt8Ty(llvm->llvmContext);
    CodeScope* scopeBody = expr.blocks.front();
    Symbol guardS = Symbol{scopeBody->getSymbol(expr.bindings.front()), scopeBody};

    const ExpandedType& typCondition = root->getType(expr.operands.at(0));
    vector<Expression> guardVariants = typeinference::getSlaveVariants(typCondition, context.pass->man->transcend);
    const int countVariants = guardVariants.size();
    assert(countVariants);

    llvm::Value * conditionRaw = scope->process(expr.operands.at(0));
    llvm::Value* variantRaw = builder.CreateExtractValue(conditionRaw, llvm::ArrayRef<unsigned>({0}));
    llvm::SwitchInst * instructionSwitch = builder.CreateSwitch(variantRaw, nullptr, countVariants);

    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(llvm->llvmContext, "switchLateAfter", function->raw);
    builder.SetInsertPoint(blockEpilog);
    llvm::Type* exprSwitchType = llvm->toLLVMType(root->getType(expr));
    llvm::PHINode *ret = builder.CreatePHI(exprSwitchType, countVariants, HINT("switchLate"));
    llvm::BasicBlock* blockDefault = nullptr;

    bool flagFirstPass = true;
    for (int variantId = 0; variantId<countVariants; ++variantId){
        //Pass information to the late model
        flagFirstPass?
            Attachments::put<LateBindingT>(guardS, guardVariants.at(variantId))
            : Attachments::update<LateBindingT>(guardS, guardVariants.at(variantId));
        flagFirstPass = false;

        llvm::BasicBlock *blockCase = llvm::BasicBlock::Create(
            llvm->llvmContext,
            "case" + std::to_string(variantId),
            function->raw);
        if(variantId == 0) blockDefault = blockCase;
        builder.SetInsertPoint(blockCase);
        auto scopeBody = Decorators<CachedScopeDecoratorTag>::getInterface<>(
            function->getScopeUnit(expr.blocks.back()));
        scopeBody->reset();

        llvm::Value* resultCase = scopeBody->compile();
        ret->addIncoming(resultCase, builder.GetInsertBlock());
        instructionSwitch->addCase(llvm::dyn_cast<llvm::ConstantInt>(llvm::ConstantInt::get(typI8, variantId)), blockCase);
        builder.CreateBr(blockEpilog);
    }

    instructionSwitch->setDefaultDest(blockDefault);
    builder.SetInsertPoint(blockEpilog);
    return ret;
}
}}