#include "codeinstructions.h"
#include "llvmlayer.h"
#include "ast.h"

using namespace llvm;
namespace xreate {
    llvm::Value*
    InstructionMap::compileDefault(const std::string * const hintRetVar) {

        CodeScope body;
        std::string varIn;
        std::string varOut;
        std::string varEl;
        int size;
        CodeScope *scopeOuter;

        llvm::IntegerType *i32Tag = (llvm::IntegerType*) TypeAnnotation(TypePrimitive::i32).toLLVMType();
        const std::string itLoopName = "it";

        Value *vecIn = scopeOuter->findSymbol(varIn, llvm);
        Value *vecOut = scopeOuter->findSymbol(varOut, llvm);

        llvm::IRBuilder<> &builder = llvm.builder;

        llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm::getGlobalContext(), "loop", llvm.function);
        llvm::BasicBlock *blockBeforeLoop = builder.GetInsertBlock();
        llvm::BasicBlock *blockAfterLoop = builder.GetInsertBlock();
        Value *cond1 = llvm.builder.CreateICmpEQ(0, ConstantInt::get((i32Tag), size));
        builder.CreateCondBr(cond1, blockAfterLoop, blockLoop);

        builder.SetInsertPoint(blockLoop);
        llvm::PHINode *itLoop = builder.CreatePHI(i32Tag, 2, itLoopName);
        itLoop->addIncoming(llvm::ConstantInt::get(i32Tag, 0), blockBeforeLoop);

        Value *pElIn = builder.CreateGEP(vecIn, ArrayRef<Value *>(std::vector<Value*>{ConstantInt::get(i32Tag, 0), itLoop}));
        Value *pElOut = builder.CreateGEP(vecOut, ArrayRef<Value *>(std::vector<Value*>{ConstantInt::get(i32Tag, 0), itLoop}));

        Value *elIn = builder.CreateLoad(pElIn, varEl);

        body.bindArg(elIn, std::string(varEl));
        Value *elOut = body.compile(llvm);

        builder.CreateStore(elOut, pElOut);
        Value *itNextLoop = builder.CreateAdd(itLoop, ConstantInt::get(i32Tag, 1));
        itLoop->addIncoming(itNextLoop, builder.GetInsertBlock());

        Value *cond2 = llvm.builder.CreateICmpSLT(itNextLoop, ConstantInt::get(i32Tag, size));
        builder.CreateCondBr(cond2, blockLoop, blockAfterLoop);

        builder.SetInsertPoint(blockAfterLoop);

        return elOut;
    }

    /*
    void InstructionMap::compileComputation() {

    }
    */


    InstructionList::InstructionList(const Expression& e, LLVMLayer& l)
        : llvm(l), __data(e), __size(e.getOperands().size())
    {
    }

    llvm::Value*
    InstructionList::compileDefault(const std::string * const hintRetVar) {
        ArrayType* typList = (ArrayType*) (TypeAnnotation(tag_array, TypePrimitive::i32, __size).toLLVMType());
        Type*typI32 = TypeAnnotation(TypePrimitive::i32).toLLVMType();

        std::vector<Constant *> list;
        list.reserve(__size);

        const std::vector<Expression> operands = __data.getOperands();
        std::transform(operands.begin(), operands.end(), std::inserter(list, list.begin()),
            [typI32](const Expression& e){return ConstantFP::get(typI32, e.getValueDouble());});

        Value* listSource = ConstantArray::get(typList, ArrayRef<Constant*>(list));
        Value* listDest = llvm.builder.CreateAlloca(typList, ConstantFP::get(typI32, __size), *hintRetVar);

        llvm.builder.CreateMemCpy(listDest, listSource, __size, 16);

        return listDest;
    }
}