/* 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_ARRAY:
    {
        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_RECORD:
    {
        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);
    };

    //DEBT omit ID field in case of single variant.
    case TypeOperator::VARIANT:
    {
        /*  Variant Type Layout:
         *   {
         *       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);
            if (subtypeRaw->isVoidTy()) continue;

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

        case TypePrimitive::Invalid:
            return llvm::Type::getVoidTy(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_RECORD) {
        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_RECORD)
        ? t.get().fields
        : llvm->layerExtern->getStructFields(
        llvm->layerExtern->lookupType(t.get().__valueCustom));
}


