#include "compilation/containers.h"

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

Iterator*
Iterator::create(xreate::compilation::Context context, const xreate::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>());

    case SOLID:
        return new IteratorForward<SOLID>(context, var, data.extract<SOLID>());

    default: assert(true);
}

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

llvm::Value*
IteratorForward<ON_THE_FLY>::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*
IteratorForward<ON_THE_FLY>::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*
IteratorForward<ON_THE_FLY>::get(Value* index,const std::string& hintRetVar){
    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->compileSymbol(current);
            return xreate::compilation::Advanced(context).compileArrayIndex(currentValue, 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*
IteratorForward<ON_THE_FLY>::advance(Value* index, const std::string& hintRetVar){
    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;
}

//const ImplementationRec<ON_THE_FLY>& implementation

IteratorForward<SOLID>::IteratorForward(const compilation::Context& ctx,  const xreate::Symbol& symbolContainer, const ImplementationRec<SOLID>& implementation)
    : Iterator(), llvm(ctx.pass->man->llvm), __length(implementation.size)
{
    __container = ctx.function->getScopeUnit(symbolContainer.scope)->compileSymbol(symbolContainer);
}

llvm::Value*
IteratorForward<SOLID>::begin(){
    //0
    return llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), 0);
}

llvm::Value*
IteratorForward<SOLID>::end(){
    //length
    return llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), __length);
}

llvm::Value*
IteratorForward<SOLID>::get(llvm::Value* index,const std::string& hintRetVar){
    //GEP[index]]

    llvm::Type* tyNum = llvm::Type::getInt32Ty(llvm::getGlobalContext());
    llvm::Value* pResult = llvm->builder.CreateGEP(__container, ArrayRef<Value *>(std::vector<Value*>{ConstantInt::get(tyNum, 0), index}));
    return llvm->builder.CreateLoad(pResult, hintRetVar);
}

llvm::Value*
IteratorForward<SOLID>::advance(llvm::Value* index, const std::string& hintRetVar){
    //index + 1

    llvm::Type* tyNum = llvm::Type::getInt32Ty(llvm::getGlobalContext());
    return llvm->builder.CreateAdd(index, llvm::ConstantInt::get(tyNum, 1), hintRetVar);
}
