#ifndef CODEINSTRUCTIONS_H
#define CODEINSTRUCTIONS_H

#include "ast.h"

#include "llvmlayer.h"
#include "pass/compilepass.h"
#include "compilation/advanced.h"

#include "query/context.h"
#include "query/containers.h"
#include "query/ptrvalid.h"

namespace xreate {
    namespace containers {

using namespace llvm;

class Iterator{
public :
    virtual llvm::Value* begin() =0;
    virtual llvm::Value* end() = 0;
    virtual llvm::Value* get(llvm::Value* index,const std::string& hintRetVar="") = 0;
    virtual llvm::Value* advance(llvm::Value* index, const std::string& hintRetVar="")=0;
    virtual ~Iterator(){};

    static Iterator* create(xreate::compilation::Context context, const xreate::Symbol& var);
};

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)
    compilation::CodeScopeUnit* sourceUnit;
    compilation::FunctionUnit* function; //TODO is used somewhere?
    const Expression& sourceDecl;
    compilation::Context context;
    llvm::Type* sourceRawType =nullptr;

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

    llvm::Value* begin() {
        switch(sourceDecl.op) {
            case xreate::Operator::LIST:
            {
            	sourceRawType = llvm::Type::getInt32Ty(llvm::getGlobalContext());
                return llvm::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;
            };

        default: break;
        }

        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);
                llvm::Value* valueEndOfRange =  sourceUnit->process(sourceDecl.operands.at(1));
                llvm::Value* valueConstOne = llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), 1);
                
                return llvm->builder.CreateAdd(valueEndOfRange, valueConstOne);
            };

        default: break;
        }

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

        assert(false && "Unknown declaration");
        return nullptr;
    }

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

        switch (currentDecl.op) {
            case xreate::Operator::LIST:             {
                //TODO re check is it right scope(source) to compile currentDecl. Provide unittests.
                //llvm::Value* currentValue = sourceUnit->process(currentDecl);
                return xreate::compilation::Advanced(context).compileArrayIndex(current, std::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);
                compilation::CodeScopeUnit* unitLoop = function->getScopeUnit(scopeLoop);
                unitLoop->bindArg(elIn, std::move(varEl));
                return unitLoop->compile();
            }

            case xreate::Operator::NONE: {
                //TODO review iterator determination strategy for case of Expression::BINDING
                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);
            };

            default: break;
        }

        if (linkedlist){
            return index;
        }

        assert(false && "Unknown declaration");
        return nullptr;
    }

    llvm::Value* advance(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);

            default: break;
        }

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

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

        assert(false && "Unknown declaration");
        return nullptr;
    }
};
}}

#endif //CODEINSTRUCTIONS_H
