#include "instr-containers.h"
#include "llvmlayer.h"
#include "ast.h"
#include "query/containers.h"
#include "query/ptrvalid.h"

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

#define NAME(x) (hintRetVar.empty()? x : hintRetVar)

#define EXPAND_CONTEXT         \
    LLVMLayer* llvm = context.pass->man->llvm; \
    CompilePass::CodeScopeUnit* scope = context.scope; \
    CompilePass::FunctionUnit* function = context.function; \

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

llvm::Value*
Instructions::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->__args[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);
    CompilePass::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*
Instructions::compileArrayIndex(const Symbol &dataSymbol, std::vector<llvm::Value *> indexes, std::string hintRetVar)
{
    EXPAND_CONTEXT

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

    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*
Instructions::compileStructIndex(llvm::Value* aggregate, const ExpandedType& t, const std::string& idx){
    EXPAND_CONTEXT



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


					// safety check: not null ptr
			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");
}

llvm::Value*
Instructions::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* rangeFrom = it->begin();
    llvm::Value* rangeTo = 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::Value* valSat;
    bool flagHasSaturation = false; //false; // TODO add `saturation` ann.

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



            // * initial check
    Value* condBefore = llvm->builder.CreateICmpNE(rangeFrom, rangeTo);
    llvm->builder.CreateCondBr(condBefore, blockLoop, blockEarly);
    llvm->builder.SetInsertPoint(blockEarly);
    llvm->builder.CreateRet(accumInit);

    		// Saturation check
    if (flagHasSaturation)
    {
        Value* condSat = llvm->builder.CreateICmpNE(accumInit, valSat);
        llvm->builder.CreateCondBr(condSat, blockLoop, blockAfterLoop);
    }

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

            // * loop body
    CodeScope* scopeLoop = fold.blocks.front();
    CompilePass::CodeScopeUnit* loopUnit = function->getScopeUnit(scopeLoop);
    Value* elIn = it->get(stateLoop);
    loopUnit->bindArg(accum, move(varAccum));
    loopUnit->bindArg(elIn, move(varEl));
    Value* accumNext = loopUnit->compile();

            // * break checks, continue checks
    if (flagHasSaturation)
    {
        llvm::BasicBlock *blockChecks = llvm::BasicBlock::Create(llvm::getGlobalContext(), "checks", function->raw);
        Value* condSat = llvm->builder.CreateICmpNE(accumNext, valSat);
        llvm->builder.CreateCondBr(condSat, blockChecks, blockAfterLoop);
        llvm->builder.SetInsertPoint(blockChecks);
    }
            // * computing next iteration state
    Value *stateLoopNext = it->move(stateLoop);
    accum->addIncoming(accumNext,  llvm->builder.GetInsertBlock());
    stateLoop->addIncoming(stateLoopNext, llvm->builder.GetInsertBlock());

             // * next iteration checks
    Value* condAfter = llvm->builder.CreateICmpNE(stateLoopNext, rangeTo);
    llvm->builder.CreateCondBr(condAfter, blockLoop, blockAfterLoop);

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

    return accum;
}

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

           //initialization:
    const Expression& condExpr = exprIf.getOperands()[0];
    llvm::IRBuilder<>& builder = llvm->builder;

    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(tyNum, 2, NAME("if"));
    ret->addIncoming(resultTrue, blockTrue);
    ret->addIncoming(resultFalse, blockFalse);

    return ret;
}

llvm::Value*
Instructions::compileSwitch(const Expression& exprSwitch, const std::string& hintRetVar){
	EXPAND_CONTEXT
    llvm::IRBuilder<>& builder = llvm->builder;


	//builder.CreateSwitch()

}

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

    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*
Instructions::compileConstantStringAsPChar(const string& data, const std::string& hintRetVar)
{
    EXPAND_CONTEXT

    size_t size = data.size();
    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);
}



template<ImplementationType I>
class IteratorForward;

template<>
class IteratorForward<ON_THE_FLY> : public Iterator {
private:
    LLVMLayer* llvm;
    const xreate::Symbol current;
    const Symbol source;
    const ImplementationLinkedList linkedlist;
    CodeScope* const sourceScope;
            //TODO initialize ans mark as const (three fields)
    CompilePass::CodeScopeUnit* sourceUnit;
    CompilePass::FunctionUnit* function;
    const Expression& sourceDecl;
    CompilePass::Context context;
    llvm::Type* sourceRawType =nullptr;

public:
    IteratorForward(CompilePass::Context ctx, const xreate::Symbol& s, const ImplementationRec<ON_THE_FLY>& implementation)
        : context(ctx), source(implementation.source), current(s), sourceScope(source.scope), sourceDecl(CodeScope::findDeclaration(source)),
            linkedlist(source), sourceUnit(new CompilePass::CodeScopeUnit(source.scope, ctx.function, ctx.pass)), llvm(ctx.pass->man->llvm)
    {
    }

    llvm::Value* begin() {
        switch(sourceDecl.op) {
            case xreate::Operator::LIST:
            {
            	sourceRawType = Type::getInt32Ty(llvm::getGlobalContext());
                return ConstantInt::get(Type::getInt32Ty(llvm::getGlobalContext()), 0);
            };

            case xreate::Operator::LIST_RANGE:{
                assert(sourceDecl.operands.size()==2);

                llvm::Value* result = sourceUnit->process(sourceDecl.operands.at(0));
                sourceRawType = result->getType();

                return result;
            };
        }

        if (linkedlist){
            llvm::Value* result = sourceUnit->process(sourceDecl);
            sourceRawType = result->getType();
           return result;
        }

        assert(false);
    }

    llvm::Value* end(){
        switch(sourceDecl.op) {
            case xreate::Operator::LIST: {
                size_t idLast = sourceDecl.operands.size() - 1;
                return ConstantInt::get(sourceRawType, idLast);
            }

            case xreate::Operator::LIST_RANGE: {
                assert(sourceDecl.operands.size() == 2);
                return sourceUnit->process(sourceDecl.operands.at(1));
            };
        }

                    //return null pointer
        if (linkedlist){
        	return ConstantPointerNull::getNullValue(sourceRawType);
        }
    }

    llvm::Value* get(Value* index,const std::string& hintRetVar="") override{
        const Expression& currentDecl = CodeScope::findDeclaration(current);

        switch (currentDecl.op) {
            case xreate::Operator::LIST:             {
                llvm::Value* currentValue = sourceUnit->process(currentDecl);   //TODO re check is it right scope(source) to compilation currentDecl
                return Instructions(context).compileArrayIndex(current, vector<Value *>{index});
            };

            case xreate::Operator::LIST_RANGE: {
                return index;
            };

            case xreate::Operator::MAP:             {
                assert(currentDecl.getOperands().size()==1);
                assert(currentDecl.bindings.size());
                assert(currentDecl.blocks.size());

                CodeScope* scopeLoop = currentDecl.blocks.front();
                const std::string& varIn = currentDecl.getOperands()[0].getValueString();
                std::string varEl = currentDecl.bindings[0];

                const Symbol& symbIn =  current.scope->findSymbol(varIn);
                auto it = std::unique_ptr<Iterator>(Iterator::create(context, symbIn));

                Value* elIn = it->get(index, varEl);
                CompilePass::CodeScopeUnit* unitLoop = function->getScopeUnit(scopeLoop);
                unitLoop->bindArg(elIn, std::move(varEl));
                return unitLoop->compile();
            }

            case xreate::Operator::NONE: {
                assert(currentDecl.__state==Expression::IDENT);
                const Symbol& symbIn =  current.scope->findSymbol(currentDecl.getValueString());
                auto it = std::unique_ptr<Iterator>(Iterator::create(context, symbIn));
                return it->get(index);
            };
        }

        if (linkedlist){
            return index;
        }
    }

    llvm::Value* move(Value* index, const std::string& hintRetVar) override{
        switch(sourceDecl.op)
        {
            case xreate::Operator::LIST:
            case xreate::Operator::LIST_RANGE:
                return llvm->builder.CreateAdd(index, llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), 1), hintRetVar);
        }

        if (linkedlist){
            ExpandedType tySource = llvm->ast->expandType(sourceScope->findDefinition(source));
            assert(tySource->__operator == TypeOperator::ARRAY && "Linked list implementation has to have ARRAY type");
            assert(tySource->__operands.size());

            return Instructions(context).compileStructIndex(index, ExpandedType(TypeAnnotation(tySource->__operands.at(0))), linkedlist.fieldPointer);
        }
    }
};

Iterator*
Iterator::create(CompilePass::Context context, const Symbol& var){

    const Implementation& data = Query::queryImplementation(var);

    switch(data.impl){
        case ON_THE_FLY:
            return new IteratorForward<ON_THE_FLY>(context, var, data.extract<ON_THE_FLY>());

        default: assert(true);
    }
}
