/* 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/.
 *
 * File:   InstructionsAdvanced.cpp
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created on June 26, 2016, 6:00 PM
 */

/**
 * \file    advanced.h
 * \brief   Compilation of statements that require more than one LLVM instruction
 */

#include "compilation/advancedinstructions.h"
#include "compilation/containers.h"
#include "compilation/transformersaturation.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::ICodeScopeUnit* scope = context.scope; \
    compilation::IFunctionUnit* function = context.function;

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

llvm::Value*
AdvancedInstructions::compileMapSolidOutput(const Expression &expr, const std::string hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(scope);

    //initialization
    Symbol symbolIn = Attachments::get<IdentifierSymbol>(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::ICodeScopeUnit* 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, builder.GetInsertBlock());

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

    //finalization:
    builder.SetInsertPoint(blockAfterLoop);

    return dataOut;
}

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

    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*
AdvancedInstructions::compileStructIndex(llvm::Value* aggregate, const ExpandedType& t, const std::string& idx) {
    EXPAND_CONTEXT
    UNUSED(scope);
    UNUSED(function);
    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) {
            //dereference pointer
            if (types.isPointer(t)) {
                llvm::Value* addr = llvm->builder.CreateConstGEP2_32(nullptr, aggregate, 0, i);
                return llvm->builder.CreateLoad(addr);
            }

            return llvm->builder.CreateExtractValue(aggregate, llvm::ArrayRef<unsigned>{i});
        }
    }

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

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

    //initialization:
    Symbol varInSymbol = Attachments::get<IdentifierSymbol>(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];

    llvm::BasicBlock *blockBeforeLoop = llvm->builder.GetInsertBlock();
    std::unique_ptr<TransformerSaturation> transformerSaturation(new TransformerSaturation(blockBeforeLoop, context.pass->managerTransformations));


    llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold", function->raw);
    llvm::BasicBlock *blockLoopBody = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold_body", function->raw);
    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold_after", function->raw);
    llvm::BasicBlock *blockNext = llvm::BasicBlock::Create(llvm::getGlobalContext(), "fold_next", function->raw);

    llvm->builder.CreateBr(blockLoop);

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

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

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

    // * Loop saturation checks
    bool flagSaturationTriggered = transformerSaturation->insertSaturationChecks(blockNext, blockAfterLoop, context);
    llvm::BasicBlock* blockSaturation = llvm->builder.GetInsertBlock();
    if (!flagSaturationTriggered){
        llvm->builder.CreateBr(blockNext);
    }

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

    // * finalization:
    llvm->builder.SetInsertPoint(blockAfterLoop);
    if (!flagSaturationTriggered){
        return accum;
    }

    llvm::PHINode* result = llvm->builder.CreatePHI(accumInit->getType(), 2);
    result->addIncoming(accum, blockLoop);
    result->addIncoming(accumNext, blockSaturation);
    return result;
}

llvm::Value*
AdvancedInstructions::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(), "foldinf", function->raw);
    llvm::BasicBlock *blockNext = llvm::BasicBlock::Create(llvm::getGlobalContext(), "foldinf_next", function->raw);
    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "foldinf_post", function->raw);
    std::unique_ptr<TransformerSaturation> transformerSaturation(new TransformerSaturation(blockBeforeLoop, context.pass->managerTransformations));

    llvm->builder.CreateBr(blockLoop);

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

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

    // * Loop saturation checks
    bool flagSaturationTriggered = transformerSaturation->insertSaturationChecks(blockNext, blockAfterLoop, context);
    assert(flagSaturationTriggered);

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

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

llvm::Value*
AdvancedInstructions::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 *blockEpilog = 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::BasicBlock *blockProlog = builder.GetInsertBlock();

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

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

    builder.SetInsertPoint(blockProlog);
    llvm->builder.CreateCondBr(cond, blockTrue, blockFalse);
    
    builder.SetInsertPoint(blockEpilog);
    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*
AdvancedInstructions::compileSwitch(const Expression& exprSwitch, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(function);
    AST* root = context.pass->man->root;
    llvm::IRBuilder<>& builder = llvm->builder;
    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::BasicBlock* blockProlog = builder.GetInsertBlock();
    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(llvm::getGlobalContext(), "switchAfter", function->raw);
    builder.SetInsertPoint(blockEpilog);
    llvm::Type* exprSwitchType = llvm->toLLVMType(root->getType(exprSwitch));
    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;
}

llvm::Value*
AdvancedInstructions::compileSwitchVariant(const Expression& exprSwitch, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(function);
    AST* root = context.pass->man->root;
    llvm::IRBuilder<>& builder = llvm->builder;
    llvm::Type* typI8= llvm::Type::getInt8Ty(llvm::getGlobalContext());
    const ExpandedType& typVariant = root->getType(exprSwitch.operands.at(0));
    llvm::Type* typVariantRaw = llvm->toLLVMType(typVariant);

    assert(typVariant->__operands.size() == exprSwitch.operands.size() - 1 && "Ill-formed Switch Variant");

    int casesCount = exprSwitch.operands.size();
    llvm::BasicBlock* blockProlog = builder.GetInsertBlock();
    llvm::BasicBlock *blockEpilog = llvm::BasicBlock::Create(llvm::getGlobalContext(), "switchAfter", function->raw);
    builder.SetInsertPoint(blockEpilog);
    llvm::Type* resultType = llvm->toLLVMType(root->getType(exprSwitch));
    llvm::PHINode *ret = builder.CreatePHI(resultType, casesCount, NAME("switch"));

    builder.SetInsertPoint(blockProlog);
    llvm::Value * conditionSwitchRaw = scope->process(exprSwitch.operands.at(0));
    llvm::Value* idRaw = builder.CreateExtractValue(conditionSwitchRaw, llvm::ArrayRef<unsigned>({0}));

    //Dereference preparation
    const bool flagDoDerefence = llvm::cast<llvm::StructType>(typVariantRaw)->getStructNumElements() > 1;
    llvm::Value* addrAsStorage = nullptr;
    if (flagDoDerefence){
        llvm::Type* typStorageRaw = llvm::cast<llvm::StructType>(typVariantRaw)->getElementType(1);
        llvm::Value* storageRaw = builder.CreateExtractValue(conditionSwitchRaw, llvm::ArrayRef<unsigned>({1}));
        addrAsStorage = llvm->builder.CreateAlloca(typStorageRaw);
        llvm->builder.CreateStore(storageRaw, addrAsStorage);
    }

    llvm::SwitchInst * instructionSwitch = builder.CreateSwitch(idRaw, nullptr, casesCount);
    llvm::BasicBlock* blockDefaultUndefined;
    std::list<CodeScope*>::const_iterator scopeCaseIt = exprSwitch.blocks.begin();
    for (int instancesSize = exprSwitch.operands.size()-1, instId = 0; instId < instancesSize; ++instId) {
        llvm::BasicBlock *blockCase = llvm::BasicBlock::Create(llvm::getGlobalContext(), "case" + std::to_string(instId), function->raw);
        builder.SetInsertPoint(blockCase);
        ICodeScopeUnit* unitCase = function->getScopeUnit(*scopeCaseIt);

        //Actual variant Derefence
        if (flagDoDerefence) {
            assert(exprSwitch.bindings.size() && "Switch condition alias not found");
            string identCondition = exprSwitch.bindings.front();
            const ExpandedType& instType = ExpandedType(typVariant->__operands.at(instId));
            llvm::Type* instTypeRaw = llvm->toLLVMType(instType);
            llvm::Value* addrAsInst =  llvm->builder.CreateBitOrPointerCast(addrAsStorage, instTypeRaw->getPointerTo());
            llvm::Value* instRaw = llvm->builder.CreateLoad(instTypeRaw, addrAsInst);
            const Symbol& identSymb =  unitCase->bindArg(instRaw, move(identCondition));
            Attachments::put<TypeInferred>(identSymb, instType);
        }

        llvm::Value* resultCase = function->getScopeUnit(*scopeCaseIt)->compile();
        builder.CreateBr(blockEpilog);

        ret->addIncoming(resultCase, blockDefaultUndefined = builder.GetInsertBlock());
        builder.SetInsertPoint(blockProlog);
        instructionSwitch->addCase(dyn_cast<llvm::ConstantInt>(llvm::ConstantInt::get(typI8, exprSwitch.operands.at(instId+1).getValueDouble())), blockCase);
        ++scopeCaseIt;
    }

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

//TODO recognize cases to make const arrays/stored in global mem/stack alloced.
llvm::Value*
AdvancedInstructions::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->getType(expression);
    assert(typAggrExpanded->__operator == TypeOperator::LIST);
    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*
        AdvancedInstructions::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);
}
