/*
 * 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/interpretation.h"
#include "compilation/targetinterpretation.h"

#include <vector>

using namespace xreate::interpretation;
using namespace xreate::compilation;
using namespace std;

namespace xreate{
namespace latereasoning{

#define HINT(x) (hint.empty()? x : hint)

std::map<SymbolPacked, Expression> LateReasoningCompiler:: __dictGuardDefinitions = std::map<SymbolPacked, Expression>();

llvm::Value*
LateReasoningCompiler::processSwitchLateStatement(const Expression& expr, const std::string& hint) {
    AST* root = __context.pass->man->root;
    LLVMLayer* llvm = __context.pass->man->llvm;

    CodeScope* scopeBody = expr.blocks.front();
    Symbol guardS = Symbol{scopeBody->getSymbol(expr.bindings.front()), scopeBody};
    const ExpandedType& guardT = root->getType(expr.operands.at(0));
    const ExpandedType& guardTPlain = guardT->__operator == TypeOperator::SLAVE?
        interpretation::dereferenceSlaveType(guardT, __context.pass->man->transcend)
        : guardT;

    llvm::Value * guardRaw = __context.scope->process(expr.operands.at(0));
    llvm::Type* instructionT = llvm->toLLVMType(root->getType(expr));

    return compileExpand(guardS, guardRaw, guardTPlain, instructionT, hint, [this, scopeBody]() {
        ICodeScopeUnit* bodyUnit = this->__context.function->getScopeUnit(scopeBody);
        bodyUnit->reset();
        return this->__context.function->getScopeUnit(scopeBody)->compile();
    });
}

llvm::Value* 
LateReasoningCompiler::compileAutoExpand(const LateAnnotation& annotation,
                                         llvm::Type* resultT,
                                         const std::string& hint,
                                         Handler handler) {
    TranscendLayer* transcend = __context.pass->man->transcend;
    AST* root = __context.pass->man->root;

    const std::list<SymbolPacked>& guardKeys =  annotation.guardKeys;
    std::list<Symbol> guardsToExpand;
    for(const SymbolPacked key : guardKeys) {
        if(!__dictGuardDefinitions.count(key)) {
            const Symbol& keyS = transcend->unpack(key);
            InterpretationScope* keyScope = __fnI12n->getScope(keyS.scope);
            if (!keyScope->isBindingDefined(keyS.identifier)) {
                guardsToExpand.push_back(keyS);
            }
        }
    }

    typedef std::function < llvm::Value * () > Compiler;
    Compiler programInit([handler, annotation, this]() {
        std::list<Expression>&& values = findKeys(annotation.guardKeys);
        auto answer = annotation.select(values, __context.pass->man->root, __context.pass->man->transcend);
        assert(answer);
        return handler(*answer);
    });

    Compiler aggregate = std::accumulate(guardsToExpand.begin(), guardsToExpand.end(),
        programInit,
        [this, root, transcend, &resultT, hint](Compiler program, const Symbol & key) {
            const ExpandedType& keyT = root->getType(CodeScope::getDefinition(key));
            const ExpandedType& keyTPlain = keyT->__operator == TypeOperator::SLAVE?
                interpretation::dereferenceSlaveType(keyT, transcend)
                : keyT;
            
            return Compiler([this, key, keyTPlain, resultT, hint, program](){
                llvm::Value * keyRaw = __context.scope->processSymbol(key);
                return compileExpand(key, keyRaw, keyTPlain, resultT, hint, program);
            });
        }
    );

    return aggregate();
}

llvm::Value*
LateReasoningCompiler::compileExpand(const Symbol& keyS,
                                     llvm::Value* keyRaw,
                                     const ExpandedType& domainT,
                                     llvm::Type* resultT,
                                     const std::string& hint,
                                     CompilerHandler compilerBody) {

    assert(domainT->__operator == TypeOperator::VARIANT);
    std::list<Expression> domInstancesList = generateAllInstancesInDomain2(domainT);
    std::vector<Expression> domInstances(domInstancesList.begin(), domInstancesList.end());
    const int countInstances = domInstances.size();
    assert(countInstances);
    TranscendLayer* transcend = __context.pass->man->transcend;
    SymbolPacked keyP = transcend->pack(keyS);

    LLVMLayer* llvm = __context.pass->man->llvm;
    llvm::IRBuilder<>& builder = llvm->builder;
    compilation::IFunctionUnit* function = __context.function;
    llvm::Type* typI8 = llvm::Type::getInt8Ty(llvm->llvmContext);

    llvm::Value* keyVariantRaw = builder.CreateExtractValue(keyRaw, llvm::ArrayRef<unsigned>({0}));
    llvm::SwitchInst* instructionSwitch = builder.CreateSwitch(keyVariantRaw, nullptr, countInstances);

    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(
        llvm->llvmContext, "epilog", function->raw);

    builder.SetInsertPoint(blockEpilog);
    llvm::PHINode *ret = builder.CreatePHI(resultT, countInstances, HINT("reverse"));
    llvm::BasicBlock* blockDefault = nullptr;
    
    for (int instanceId = 0; instanceId < countInstances; ++instanceId) {
        llvm::BasicBlock *blockCase = llvm::BasicBlock::Create(
            llvm->llvmContext,
            "case" + std::to_string(instanceId),
            function->raw);
        if(instanceId == 0) blockDefault = blockCase;
        builder.SetInsertPoint(blockCase);

        //assign guard values
        const Expression& instanceE = domInstances.at(instanceId);
        __dictGuardDefinitions[keyP] = instanceE;

//        __fnI12n->getScope(keyS.scope)->overrideBindings({
//            {instanceE, keyS.identifier}
//        });

        //invoke further compilation handler
        llvm::Value* resultCase = compilerBody();

        ret->addIncoming(resultCase, builder.GetInsertBlock());
        instructionSwitch->addCase(llvm::dyn_cast<llvm::ConstantInt>(llvm::ConstantInt::get(typI8, instanceId)), blockCase);
        builder.CreateBr(blockEpilog);
    }

    //erase guard assignment
    __dictGuardDefinitions.erase(keyP);

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

std::list<Expression>
LateReasoningCompiler::findKeys(const std::list<SymbolPacked>& keys) {
    TranscendLayer* transcend = __context.pass->man->transcend;
    std::list<Expression> result;
    InterpretationScope* scopeI12n = __fnI12n->getScope(__context.scope->scope);
    
    std::transform(keys.begin(), keys.end(), std::inserter(result, result.end()),
        [this, scopeI12n, transcend](const SymbolPacked & key) {
            if (__dictGuardDefinitions.count(key)){
                return __dictGuardDefinitions.at(key);
            }
            return scopeI12n->processSymbol(transcend->unpack(key));
        });

    return result;
}
}
}
