/*
 * File:   InstructionsAdvanced.cpp
 * Author: pgess
 *
 * Created on June 26, 2016, 6:00 PM
 */

//#include <compilation/transformations.h>
#include "compilation/advanced.h"
#include "compilation/containers.h"

#include "query/context.h"
#include "query/containers.h"
#include "llvmlayer.h"
#include "ast.h"

using namespace std;
using namespace llvm;
using namespace xreate;
using namespace xreate::containers;
using namespace xreate::compilation;

#define NAME(x) (hintRetVar.empty()? x : hintRetVar)
#define UNUSED(x) (void)(x)
#define EXPAND_CONTEXT         \
    LLVMLayer* llvm = context.pass->man->llvm; \
    compilation::AbstractCodeScopeUnit* scope = context.scope; \
    compilation::FunctionUnit* function = context.function;

Advanced::Advanced(compilation::Context ctx)
: context(ctx), tyNum(static_cast<llvm::IntegerType*> (ctx.pass->man->llvm->toLLVMType(ExpandedType(TypeAnnotation(TypePrimitive::Num))))) {
}

llvm::Value*
Advanced::compileMapSolidOutput(const Expression &expr, const std::string hintRetVar) {
    EXPAND_CONTEXT

    //initialization
    Symbol symbolIn = Attachments::get<Symbol>(expr.getOperands()[0]);

    ImplementationRec<SOLID> implIn = containers::Query::queryImplementation(symbolIn).extract<SOLID>(); // impl of input list
    size_t size = implIn.size;
    CodeScope* scopeLoop = expr.blocks.front();
    std::string varEl = scopeLoop->__bindings[0];

    Iterator* it = Iterator::create(context, symbolIn);
    llvm::Value *rangeFrom = it->begin();
    llvm::Value *rangeTo = it->end();

    //definitions
    ArrayType* tyNumArray = (ArrayType*) (llvm->toLLVMType(ExpandedType(TypeAnnotation(tag_array, TypePrimitive::Num, size))));
    llvm::IRBuilder<> &builder = llvm->builder;

    llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "loop", function->raw);
    llvm::BasicBlock *blockBeforeLoop = builder.GetInsertBlock();
    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "postloop", function->raw);
    Value* dataOut = llvm->builder.CreateAlloca(tyNumArray, ConstantInt::get(tyNum, size), NAME("map"));

    // * initial check
    Value* condBefore = builder.CreateICmpSLE(rangeFrom, rangeTo);
    builder.CreateCondBr(condBefore, blockLoop, blockAfterLoop);

    // create PHI:
    builder.SetInsertPoint(blockLoop);
    llvm::PHINode *stateLoop = builder.CreatePHI(tyNum, 2, "mapIt");
    stateLoop->addIncoming(rangeFrom, blockBeforeLoop);

    // loop body:
    Value* elIn = it->get(stateLoop, varEl);
    compilation::AbstractCodeScopeUnit* scopeLoopUnit = function->getScopeUnit(scopeLoop);
    scopeLoopUnit->bindArg(elIn, move(varEl));
    Value* elOut = scopeLoopUnit->compile();
    Value *pElOut = builder.CreateGEP(dataOut, ArrayRef<Value *>(std::vector<Value*>{ConstantInt::get(tyNum, 0), stateLoop}));
    builder.CreateStore(elOut, pElOut);

    //next iteration preparing
    Value *stateLoopNext = builder.CreateAdd(stateLoop, llvm::ConstantInt::get(tyNum, 1));
    stateLoop->addIncoming(stateLoopNext, blockLoop);

    //next iteration checks:
    Value* condAfter = builder.CreateICmpSLE(stateLoopNext, rangeTo);
    builder.CreateCondBr(condAfter, blockLoop, blockAfterLoop);

    //finalization:
    builder.SetInsertPoint(blockAfterLoop);

    return dataOut;
}

Value*
Advanced::compileArrayIndex(llvm::Value* aggregate, std::vector<llvm::Value *> indexes, std::string hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(function);

    indexes.insert(indexes.begin(), llvm::ConstantInt::get(tyNum, 0));
    llvm::Value *pEl = llvm->builder.CreateGEP(aggregate, llvm::ArrayRef<llvm::Value *>(indexes));
    return llvm->builder.CreateLoad(pEl, NAME("el"));
}

Value*
Advanced::compileStructIndex(llvm::Value* aggregate, const ExpandedType& t, const std::string& idx) {
    EXPAND_CONTEXT
    UNUSED(scope);


    TypeUtils types(llvm);

    std::vector<std::string>&& fields = types.getStructFields(t);
    for (unsigned i = 0, size = fields.size(); i < size; ++i) {
        if (fields.at(i) == idx) {

            std::vector<llvm::Value*> refs;

            llvm::IntegerType* tyInt = llvm::Type::getInt32Ty(llvm::getGlobalContext());
            llvm::ConstantInt* zero = llvm::ConstantInt::get(tyInt, 0, false);

            llvm::BasicBlock *blockSafe = llvm::BasicBlock::Create(llvm::getGlobalContext(), "safe", function->raw);


            // TODO review safety check: validPtr for `aggregate`
            // SECTIONTAG validptr exception
            PointerType* tyAggr = dyn_cast<PointerType>(aggregate->getType());
            llvm::Value* null = llvm::ConstantPointerNull::get(tyAggr);
            Value* condNull = llvm->builder.CreateICmpNE(aggregate, null);

            llvm::BasicBlock *blockException = llvm::BasicBlock::Create(llvm::getGlobalContext(), "exception", function->raw);
            llvm->builder.CreateCondBr(condNull, blockSafe, blockException);
            llvm->initExceptionBlock(blockException);

            llvm->builder.SetInsertPoint(blockSafe);
            std::vector<Value*> indexes;

            //dereference pointer
            if (types.isPointer(t)) {
                indexes.push_back(zero);
            }

            indexes.push_back(ConstantInt::get(tyInt, i));

            Value* addr = llvm->builder.CreateGEP(aggregate, indexes);
            return llvm->builder.CreateLoad(addr);
        }
    }

    assert(false && "not found required struct field");
    return nullptr;
}

llvm::Value*
Advanced::compileFold(const Expression& fold, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    assert(fold.op == Operator::FOLD);

    //initialization:
    Symbol varInSymbol = Attachments::get<Symbol>(fold.getOperands()[0]);
    Implementation info = Query::queryImplementation(varInSymbol);

    Iterator* it = Iterator::create(context, varInSymbol);
    llvm::Value* rangeBegin = it->begin();
    llvm::Value* rangeEnd = it->end();
    llvm::Value* accumInit = scope->process(fold.getOperands()[1]);
    std::string varIn = fold.getOperands()[0].getValueString();
    std::string varAccum = fold.bindings[1];
    std::string varEl = fold.bindings[0];

    //require transformers feature
    //TransformerSaturation* transformerSaturation = context.pass->transformations->get<TransformerSaturation>();

    llvm::BasicBlock *blockBeforeLoop = llvm->builder.GetInsertBlock();
    llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold", function->raw);
    llvm::BasicBlock *blockBody = llvm::BasicBlock::Create(llvm::getGlobalContext(), "body", function->raw);
    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "postfold", function->raw);

    llvm->builder.CreateBr(blockLoop);

    // * create phi
    llvm->builder.SetInsertPoint(blockLoop);
    llvm::PHINode *accum = llvm->builder.CreatePHI(accumInit->getType(), 2, NAME("accum"));
    accum->addIncoming(accumInit, blockBeforeLoop);
    llvm::PHINode *itLoop = llvm->builder.CreatePHI(rangeBegin->getType(), 2, "foldIt");
    itLoop->addIncoming(rangeBegin, blockBeforeLoop);

    // * loop body
    llvm->builder.SetInsertPoint(blockBody);
    CodeScope* scopeLoop = fold.blocks.front();
    compilation::AbstractCodeScopeUnit* loopUnit = function->getScopeUnit(scopeLoop);
    Value* elIn = it->get(itLoop);
    loopUnit->bindArg(accum, move(varAccum));
    loopUnit->bindArg(elIn, move(varEl));
    Value* accumNext = loopUnit->compile();

    // * computing next iteration state
    Value *itLoopNext = it->advance(itLoop);
    accum->addIncoming(accumNext, llvm->builder.GetInsertBlock());
    itLoop->addIncoming(itLoopNext, llvm->builder.GetInsertBlock());
    llvm->builder.CreateBr(blockLoop);

    // * break checks, continue checks
    //!! only after compiled Loop Body in order to fetch saturation expression
    llvm->builder.SetInsertPoint(blockLoop);

    //reuiqre transformers feature
//    if (transformerSaturation->exists()) {
//        transformerSaturation->inject(blockBeforeLoop, blockAfterLoop, context);
//    }

    // * next iteration checks
    Value* condRange = llvm->builder.CreateICmpNE(itLoop, rangeEnd);
    llvm->builder.CreateCondBr(condRange, blockBody, blockAfterLoop);

    // finalization:
    llvm->builder.SetInsertPoint(blockAfterLoop);

    return accum;
}

llvm::Value*
Advanced::compileFoldInf(const Expression& fold, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    assert(fold.op == Operator::FOLD_INF);

    std::string accumName = fold.bindings[0];
    llvm::Value* accumInit = scope->process(fold.getOperands()[0]);

    llvm::BasicBlock *blockBeforeLoop = llvm->builder.GetInsertBlock();
    llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold", function->raw);
    llvm::BasicBlock *blockBody = llvm::BasicBlock::Create(llvm::getGlobalContext(), "body", function->raw);
    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "postfold", function->raw);

    //require transformers feature
    //TransformerSaturation* transformerSaturation = context.pass->transformations->get<TransformerSaturation>();

    llvm->builder.CreateBr(blockLoop);

    // * create phi
    llvm->builder.SetInsertPoint(blockLoop);
    llvm::PHINode *accum = llvm->builder.CreatePHI(accumInit->getType(), 2, NAME("accum"));
    accum->addIncoming(accumInit, blockBeforeLoop);

    // * loop body
    llvm->builder.SetInsertPoint(blockBody);
    CodeScope* scopeLoop = fold.blocks.front();
    compilation::AbstractCodeScopeUnit* unitLoop = function->getScopeUnit(scopeLoop);
    unitLoop->bindArg(accum, move(accumName));
    Value* accumNext = unitLoop->compile();

    // * computing next iteration state
    accum->addIncoming(accumNext, llvm->builder.GetInsertBlock());
    llvm->builder.CreateBr(blockLoop);

    // * break checks, continue checks
    llvm->builder.SetInsertPoint(blockLoop);

    //require transformers feature
//    assert(transformerSaturation->exists());
//    transformerSaturation->inject(blockBeforeLoop, blockAfterLoop, context);

    llvm->builder.CreateBr(blockBody);

    // finalization:
    llvm->builder.SetInsertPoint(blockAfterLoop);
    return accum;
}




llvm::Value*
Advanced::compileIf(const Expression& exprIf, const std::string& hintRetVar) {
    EXPAND_CONTEXT

            //initialization:
            const Expression& condExpr = exprIf.getOperands()[0];
    llvm::IRBuilder<>& builder = llvm->builder;
    //llvm::Type* tyResultType = llvm->toLLVMType(llvm->ast->expandType(exprIf.type));

    llvm::BasicBlock *blockAfter = llvm::BasicBlock::Create(llvm::getGlobalContext(), "ifAfter", function->raw);
    llvm::BasicBlock *blockTrue = llvm::BasicBlock::Create(llvm::getGlobalContext(), "ifTrue", function->raw);
    llvm::BasicBlock *blockFalse = llvm::BasicBlock::Create(llvm::getGlobalContext(), "ifFalse", function->raw);

    llvm::Value* cond = scope->process(condExpr);
    llvm->builder.CreateCondBr(cond, blockTrue, blockFalse);

    builder.SetInsertPoint(blockTrue);
    CodeScope* scopeTrue = exprIf.blocks.front();
    llvm::Value* resultTrue = function->getScopeUnit(scopeTrue)->compile();
    builder.CreateBr(blockAfter);

    builder.SetInsertPoint(blockFalse);
    CodeScope* scopeFalse = exprIf.blocks.back();
    llvm::Value* resultFalse = function->getScopeUnit(scopeFalse)->compile();
    builder.CreateBr(blockAfter);

    builder.SetInsertPoint(blockAfter);
    llvm::PHINode *ret = builder.CreatePHI(resultTrue->getType(), 2, NAME("if"));
    ret->addIncoming(resultTrue, blockTrue);
    ret->addIncoming(resultFalse, blockFalse);

    return ret;
}

//TODO Switch: default variant no needed when all possible conditions are considered

llvm::Value*
Advanced::compileSwitch(const Expression& exprSwitch, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(function);

    assert(exprSwitch.operands.size() >= 2);
    assert(exprSwitch.operands[1].op == Operator::CASE_DEFAULT && "No default case in Switch Statement");
    int countCases = exprSwitch.operands.size() - 1;
    llvm::IRBuilder<>& builder = llvm->builder;

    llvm::BasicBlock* blockProlog = builder.GetInsertBlock();
    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(llvm::getGlobalContext(), "switchAfter", function->raw);
    builder.SetInsertPoint(blockEpilog);
    llvm::Type* exprSwitchType = llvm->toLLVMType(ExpandedType(exprSwitch.type));
    llvm::PHINode *ret = builder.CreatePHI(exprSwitchType, countCases, NAME("switch"));

    builder.SetInsertPoint(blockProlog);
    llvm::Value * conditionSwitch = scope->process(exprSwitch.operands[0]);
    llvm::BasicBlock *blockDefault = llvm::BasicBlock::Create(llvm::getGlobalContext(), "caseDefault", function->raw);
    llvm::SwitchInst * instructionSwitch = builder.CreateSwitch(conditionSwitch, blockDefault, countCases);

    for (int size = exprSwitch.operands.size(), i = 2; i < size; ++i) {
        llvm::BasicBlock *blockCase = llvm::BasicBlock::Create(llvm::getGlobalContext(), "case" + std::to_string(i), function->raw);

        llvm::Value* condCase = function->getScopeUnit(exprSwitch.operands[i].blocks.front())->compile();
        builder.SetInsertPoint(blockCase);
        llvm::Value* resultCase = function->getScopeUnit(exprSwitch.operands[i].blocks.back())->compile();
        builder.CreateBr(blockEpilog);

        ret->addIncoming(resultCase, builder.GetInsertBlock());
        builder.SetInsertPoint(blockProlog);
        instructionSwitch->addCase(dyn_cast<llvm::ConstantInt>(condCase), blockCase);
    }

    //compile default block:
    builder.SetInsertPoint(blockDefault);
    CodeScope* scopeDefault = exprSwitch.operands[1].blocks.front();
    llvm::Value* resultDefault = function->getScopeUnit(scopeDefault)->compile();
    builder.CreateBr(blockEpilog);
    ret->addIncoming(resultDefault, builder.GetInsertBlock());
    builder.SetInsertPoint(blockEpilog);

    return ret;
}

//TODO recognize cases to make const arrays/stored in global mem/stack alloced.
llvm::Value*
Advanced::compileListAsSolidArray(const Expression &expr, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(scope);
    UNUSED(function);

    AST* root = context.pass->man->root;
    const size_t& length = expr.getOperands().size();
    const Expression& expression = expr;
    llvm::Value* zero = ConstantInt::get(tyNum, 0);
    llvm::Value* one = ConstantInt::get(tyNum, 1);

    ExpandedType typAggrExpanded = root->expandType(expression.type);
    assert(typAggrExpanded->__operator == TypeOperator::ARRAY);
    llvm::Type* typEl = llvm->toLLVMType(ExpandedType(typAggrExpanded->__operands[0]));
    ArrayType* typAggr = (ArrayType*) llvm::ArrayType::get(typEl, length);
    llvm::Value* list = llvm->builder.CreateAlloca(typAggr, ConstantInt::get(Type::getInt32Ty(llvm::getGlobalContext()), length, false), hintRetVar);

    const std::vector<Expression>& operands = expression.getOperands();
    llvm::Value* addrOperand = llvm->builder.CreateGEP(typAggr, list, ArrayRef<Value *>(std::vector<Value*>{zero, zero}));
    llvm->builder.CreateStore(scope->process(operands.front()), addrOperand) ;
    for (auto i=++operands.begin(); i!=operands.end(); ++i){
        addrOperand = llvm->builder.CreateGEP(typEl, addrOperand, ArrayRef<Value *>(std::vector<Value*>{one}));
        llvm->builder.CreateStore(scope->process(*i), addrOperand) ;
    }

    return list;
//        Value* listDest = l.builder.CreateAlloca(typList, ConstantInt::get(typI32, __size), *hintRetVar);
//        l.buil1der.CreateMemCpy(listDest, listSource, __size, 16);
}

llvm::Value*
        Advanced::compileConstantStringAsPChar(const string& data, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(function);
            UNUSED(scope);

            Type* typPchar = PointerType::getUnqual(Type::getInt8Ty(llvm::getGlobalContext()));
            //ArrayType* typStr = (ArrayType*) (llvm->toLLVMType(ExpandedType(TypeAnnotation(tag_array, TypePrimitive::I8, size+1))));

            /*
            std::vector<Constant *> chars;
            chars.reserve(size+1);

            for (size_t i=0; i< size; ++i){
                chars[i] = ConstantInt::get(typI8, (unsigned char) data[i]);
            }
            chars[size] = ConstantInt::get(typI8, 0);
             */

            Value* rawData = ConstantDataArray::getString(llvm::getGlobalContext(), data);
            Value* rawPtrData = llvm->builder.CreateAlloca(rawData->getType(), ConstantInt::get(Type::getInt32Ty(llvm::getGlobalContext()), 1, false));
            llvm->builder.CreateStore(rawData, rawPtrData);
    return llvm->builder.CreateCast(llvm::Instruction::BitCast, rawPtrData, typPchar, hintRetVar);
}
