/*
 * 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 "query/ptrvalid.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::CodeScopeUnit* 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::compileMapSolid(const Expression &expr, const std::string hintRetVar) {
        EXPAND_CONTEXT

                //initialization
        std::string varIn = expr.getOperands()[0].getValueString();
        Symbol symbolIn = scope->scope->findSymbol(varIn);

        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::CodeScopeUnit* 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(const Symbol &dataSymbol, std::vector<llvm::Value *> indexes, std::string hintRetVar)
{
    EXPAND_CONTEXT
    UNUSED(function);

    //TODO find out symbol identifier in order to name it in raw llvm;
    llvm::Value* data = scope->compileSymbol(dataSymbol);
    const Expression& decl = CodeScope::findDeclaration(dataSymbol);

    //TODO review array index compilation strategy
    if (decl.op == Operator::LIST) {
        assert(indexes.size() == 1);
        return llvm->builder.CreateExtractElement(data, indexes[0], NAME("el"));
    }


    indexes.insert(indexes.begin(), llvm::ConstantInt::get(tyNum, 0));
    Value *pEl = llvm->builder.CreateGEP(data, 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: not null ptr
            // SECTIONTAG validptr exception
            Symbol s;
            if (! QueryPtrValid::assertValidPtr(s)){
                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  = scope->scope->findSymbol(fold.getOperands()[0].getValueString());
    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];
    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::CodeScopeUnit* 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);
    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);
    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::CodeScopeUnit* 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
        assert(transformerSaturation->exists());
    llvm->builder.SetInsertPoint(blockLoop);
    transformerSaturation->inject(blockBeforeLoop, blockAfterLoop, context);
    llvm->builder.CreateBr(blockBody);

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

llvm::Value*
Advanced::compileLoopContext(const Expression& expression, const std::string& hintRetVar){
	EXPAND_CONTEXT
	llvm::IRBuilder<>& builder = llvm->builder;

	ContextQuery* queryContext = reinterpret_cast<ContextQuery*> (context.pass->man->clasp->getQuery(QueryId::ContextQuery));

	ScopePacked scopeOuterId = context.pass->man->clasp->pack(scope->scope);
	const Domain& contextScopeOuter =  queryContext->getContext(scopeOuterId);
	std::string classSelected = expression.operands[0].getValueString();

	std::list<Expression> elementsSelected;
	for (const Expression& c: contextScopeOuter){
		if (c.op == Operator::CALL && c.getValueString() == classSelected){
			assert(c.operands.size());
			elementsSelected.push_back(c.operands[0]);
		}
	}

	assert(expression.blocks.size());
	CodeScope* scopeInner = expression.blocks.front();
	compilation::CodeScopeUnit* scopeInnerUnit = function->getScopeUnit(scopeInner);
	ScopePacked scopeInnerId = context.pass->man->clasp->pack(scopeInner);

	llvm::Value* result = nullptr;
	for (const Expression& element: elementsSelected){
		std::string blockName = "context" + element.getValueString();
		llvm::BasicBlock *blockInner = llvm::BasicBlock::Create(llvm::getGlobalContext(), blockName, function->raw);
		builder.CreateBr(blockInner);
		builder.SetInsertPoint(blockInner);

		queryContext->forceContext(scopeInnerId, {element});
		scopeInnerUnit->reset();
		result = scopeInnerUnit->compile();
	}

	return result;
}

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(tyResultType, 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(scope);
    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;
}

llvm::Value*
Advanced::compileConstantArray(const Expression &expr, const std::string& hintRetVar) {
    EXPAND_CONTEXT
    UNUSED(scope);
    UNUSED(function);

    const size_t& __size = expr.getOperands().size();
    const Expression& __data = expr;

    ArrayType* typList = (ArrayType*) (llvm->toLLVMType(ExpandedType(TypeAnnotation(tag_array, TypePrimitive::I32, __size))));
    Type*typI32 = llvm->toLLVMType(ExpandedType(TypeAnnotation(TypePrimitive::I32)));

    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 ConstantInt::get(typI32, e.getValueDouble());});

    Value* listSource = ConstantArray::get(typList, ArrayRef<Constant*>(list));
        /*
    Value* listDest = l.builder.CreateAlloca(typList, ConstantInt::get(typI32, __size), *hintRetVar);
    l.buil1der.CreateMemCpy(listDest, listSource, __size, 16);
        */

    return listSource;
}

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);
}
