/* 
 * File:   Transformations.cpp
 * Author: pgess
 * 
 * Created on June 18, 2016, 6:23 PM
 */

#include "compilation/transformations.h"
#include "pass/compilepass.h"
#include "llvmlayer.h"
#include <llvm/IR/Value.h>

using namespace llvm;

namespace xreate { namespace compilation {

Transformations::Transformations(CompilePass* passCompilation): pass(passCompilation) {
}

void
Transformations::subscribe(const std::string& annotation, int handler){
    __subscriptions.emplace(annotation, handler);
}

bool 
Transformations::isAcceptable(const Expression& expression){
    //check subscription based on expression attachments
    if (Attachments::get<Expression, Transformations>(expression, {false, 0}).flagSubscribed){
        return true;
    }
    
    //subscription based on expression annotations;
    if (expression.tags.size() == 0) return false;
    
    for (auto tag: expression.tags) {
        if (__subscriptions.count(tag.first)){
            return true;
        }
    }
    
    return false;
}

Expression
Transformations::transform(const Expression& expression, const Context& ctx){
    Expression result = expression;
    
    for (auto handler: getAppropriateTransformers(expression)){
        result = handler->transform(result, ctx);
    }
    
    return result;
}

llvm::Value* 
Transformations::transform(const Expression& expression, llvm::Value* raw, const Context& ctx){
    for (auto handler: getAppropriateTransformers(expression)){
        raw = handler->transform(expression, raw, ctx);
    }
    
    return raw;
}

std::list<Transformer*>
Transformations::getAppropriateTransformers(const Expression& expression){

    std::list<Transformer*> result;
    
    for (auto tag: expression.tags) {
        if (__subscriptions.count(tag.first)){
            auto handlers = __subscriptions.equal_range(tag.first);
            
            for (auto handlerIt=handlers.first; handlerIt!= handlers.second; ++handlerIt){
                Transformer* handler  = __transformers[handlerIt->second];
                
                if (handler->isAcceptable(expression)){
                    result.push_back(handler);
                }
            }
        }
    }
    
    auto subscriberInternal = Attachments::get<Expression, Transformations>(expression, {false, 0});
    if (subscriberInternal.flagSubscribed){
        result.push_back(__transformers[subscriberInternal.subscriberId]);
    }
    
    return result;
}

TransformerSaturation::TransformerSaturation(Transformations* man)
    : __man(man) {
    
    man->subscribe("break", TransformerInfo<TransformerSaturation>::id);
}

bool 
TransformerSaturation::isAcceptable(const Expression& expression){
    return true;
}

bool
TransformerSaturation::exists() const{
    return __block != nullptr;
}

llvm::Value*
TransformerSaturation::transform(const Expression& expression, llvm::Value* raw, const Context& ctx){
    __block = __man->pass->man->llvm->builder.GetInsertBlock();
    
    return raw;
}

llvm::BasicBlock* 
TransformerSaturation::getBlock() const{
    return __block;
}

void
TransformerSaturation::inject(llvm::BasicBlock* blockAllocation, llvm::BasicBlock* blockExit, compilation::Context context){
    llvm::Type* tyInt1 = llvm::Type::getInt1Ty(llvm::getGlobalContext());
    llvm::Value* valueConstOne = llvm::ConstantInt::get(tyInt1, 1);
    llvm::Value* valueConstFalse = llvm::ConstantInt::get(tyInt1, 0);

        //allocation of saturation flag
    IRBuilder<> builderAlloca(blockAllocation, blockAllocation->getFirstInsertionPt());
    llvm::Value* flagSaturation =  builderAlloca.CreateAlloca(tyInt1, valueConstOne, "flagSaturation");
    builderAlloca.CreateStore(valueConstFalse, flagSaturation, true);

    //set up saturation flag
    llvm::BasicBlock* blockSaturation = __block;
    IRBuilder<> builderSaturation(blockSaturation, blockSaturation->getFirstInsertionPt());
    builderSaturation.CreateStore(valueConstOne, flagSaturation, true);

    //check saturation flag:
    //TODO remove blockContinue, receive from caller block to continue.
    llvm::BasicBlock *blockContinue = llvm::BasicBlock::Create(llvm::getGlobalContext(), "continue", context.function->raw);
    context.pass->man->llvm->builder.CreateCondBr(context.pass->man->llvm->builder.CreateLoad(flagSaturation), blockExit, blockContinue);
    
    context.pass->man->llvm->builder.SetInsertPoint(blockContinue);
}

}}