/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * llvmlayer.cpp
 *
 *      Author: pgess <v.melnychenko@xreate.org>
 */

/**
 * \file    llvmlayer.h
 * \brief   Wrapper over LLVM
 */

#include "ast.h"
#include "llvmlayer.h"
#include "ExternLayer.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/Support/TargetSelect.h"
#include <iostream>
#include <cmath>

using namespace llvm;
using namespace xreate;
using namespace std;

LLVMLayer::LLVMLayer(AST* root)
    :   llvmContext(),
        builder(llvmContext),
        ast(root),
        module(new llvm::Module(root->getModuleName(), llvmContext))
{
    layerExtern = new ExternLayer(this);
    layerExtern->init(root);
}

void*
LLVMLayer::getFunctionPointer(llvm::Function* function){
    uint64_t entryAddr = jit->getFunctionAddress(function->getName().str());
    return (void*) entryAddr;
}

void
LLVMLayer::initJit(){
    std::string ErrStr;
    LLVMInitializeNativeTarget();
    llvm::InitializeNativeTargetAsmPrinter();

    llvm::EngineBuilder builder(std::unique_ptr<llvm::Module>(module.release()));
    jit.reset(builder
        .setEngineKind(llvm::EngineKind::JIT)
        .setErrorStr(&ErrStr)
        .setVerifyModules(true)
        .create()
    );
}

void
LLVMLayer::print(){
    llvm::PassManager<llvm::Module> PM;
    PM.addPass(llvm::PrintModulePass(llvm::outs(), "banner"));
    llvm::AnalysisManager<llvm::Module> aman;
    PM.run(*module.get(), aman);
}

void
LLVMLayer::moveToGarbage(void *o)
{
    __garbage.push_back(o);
}

llvm::Type*
LLVMLayer::
toLLVMType(const ExpandedType& ty) const {
    std::map<int, llvm::StructType*> empty;
    return toLLVMType(ty, empty);
}

llvm::Type*
LLVMLayer::
toLLVMType(const ExpandedType& ty, std::map<int, llvm::StructType*>& conjuctions) const{
    TypeAnnotation t = ty;
    switch (t.__operator)
    {
        case TypeOperator::LIST:
        {
            assert(t.__operands.size()==1);

            TypeAnnotation elTy = t.__operands.at(0);
            return llvm::ArrayType::get(toLLVMType(ExpandedType(move(elTy)), conjuctions), t.__size);
        }

        case TypeOperator::LIST_NAMED:
        {
            std::vector<llvm::Type*> pack_;
            pack_.reserve(t.__operands.size());

            std::transform(t.__operands.begin(), t.__operands.end(), std::inserter(pack_, pack_.end()),
			   [this, &conjuctions](const TypeAnnotation& t){
				   return toLLVMType(ExpandedType(TypeAnnotation(t)), conjuctions);
			   });

            llvm::ArrayRef<llvm::Type*> pack(pack_);

            			//process recursive types:
            if (conjuctions.count(t.conjuctionId)) {
            	auto result = conjuctions[t.conjuctionId];
            	result->setBody(pack, false);

            	return result;
            }

            return llvm::StructType::get(llvmContext, pack, false);
        };

        case TypeOperator::LINK: {
        	llvm::StructType* conjuction = llvm::StructType::create(llvmContext);
        	int id = t.conjuctionId;

        	conjuctions.emplace(id, conjuction);
        	return conjuction;
        };

        case TypeOperator::CALL:
        {
            assert(false);
        };

        case TypeOperator::CUSTOM:
        {
            //Look in extern types
            clang::QualType qt = layerExtern->lookupType(t.__valueCustom);
            return layerExtern->toLLVMType(qt);
        };

        case TypeOperator::VARIANT: {
                /*  Variant Type Layout:
                *   struct {
                *       id: i8,       Holds stored variant id
                *       storage:      type of biggest variant
                *   }
                */
            uint64_t sizeStorage=0;
            llvm::Type* typStorageRaw = llvm::Type::getVoidTy(llvmContext);
            for(const TypeAnnotation& subtype: t.__operands){
                llvm::Type* subtypeRaw = toLLVMType(ExpandedType(subtype), conjuctions);

                uint64_t sizeSubtype = module->getDataLayout().getTypeStoreSize(subtypeRaw);
                if (sizeSubtype > sizeStorage){
                    sizeStorage = sizeSubtype;
                    typStorageRaw = subtypeRaw;
                }
            }

            std::vector<llvm::Type*> layout;
            layout.push_back(llvm::Type::getInt8Ty(llvmContext)); //id

            const bool flagHoldsData = sizeStorage > 0;
            if (flagHoldsData) {
                layout.push_back(typStorageRaw); //storage
            }

            return llvm::StructType::get(llvmContext, llvm::ArrayRef<llvm::Type*>(layout));
        }

        case TypeOperator::NONE: {
            switch (t.__value) {
                case TypePrimitive::I32:
                case TypePrimitive::Int:
                case TypePrimitive::Num:
                    return llvm::Type::getInt32Ty(llvmContext);

                case TypePrimitive::Bool:
                    return llvm::Type::getInt1Ty(llvmContext);

                case TypePrimitive::I8:
                    return llvm::Type::getInt8Ty(llvmContext);

                case TypePrimitive::I64:
                    return llvm::Type::getInt64Ty(llvmContext);

                case TypePrimitive::Float:
                    return llvm::Type::getDoubleTy(llvmContext);

                case TypePrimitive::String:
                    return llvm::Type::getInt8PtrTy(llvmContext);

                default:
                    assert(false);
            }
        }

        default:
            assert(false);
    }

    assert(false);
    return  nullptr;
}

bool TypeUtils::isStruct(const ExpandedType& ty){
    const TypeAnnotation& t = ty.get();

    if (t.__operator==TypeOperator::LIST_NAMED) {
        return true;
    }

    if (t.__operator != TypeOperator::CUSTOM) {
        return false;
    }

    clang::QualType tqual = llvm->layerExtern->lookupType(t.__valueCustom);
    const clang::Type * raw = tqual.getTypePtr();

        // TODO skip ALL the pointers until non-pointer type found
    if (raw->isStructureType()) return true;
    if (!raw->isAnyPointerType()) return false;

    clang::QualType pointee = raw->getPointeeType();
    return pointee->isStructureType();
}


bool TypeUtils::isPointer(const ExpandedType &ty) {
    if (ty.get().__operator != TypeOperator::CUSTOM) return false;

    clang::QualType qt = llvm->layerExtern->lookupType(ty.get().__valueCustom);
    return llvm->layerExtern->isPointer(qt);
}


std::vector<std::string>
TypeUtils::getStructFields(const ExpandedType &t) {
    return (t.get().__operator == TypeOperator::LIST_NAMED)
           ? t.get().fields
            : llvm->layerExtern->getStructFields(
                    llvm->layerExtern->lookupType(t.get().__valueCustom));
}


