/* 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)
    : ast(root), builder(getGlobalContext()) {
    module = new llvm::Module(root->getModuleName(), llvm::getGlobalContext());
    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)));
	jit = 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"));
    PM.run(*module);
}

llvm::BasicBlock*
LLVMLayer::initExceptionBlock(llvm::BasicBlock* blockException){
	initExceptionsSupport();

	PointerType* tyInt8P = PointerType::getInt8PtrTy(llvm::getGlobalContext());
    Value* nullInt8P = llvm::ConstantPointerNull::get(tyInt8P);


	builder.SetInsertPoint(blockException);
	llvm::Function* fAllocate = module->getFunction("__cxa_allocate_exception");
	llvm::Function* fThrow = module->getFunction("__cxa_throw");

	auto exception = builder.CreateCall(fAllocate, ConstantInt::get(IntegerType::getInt64Ty(getGlobalContext()), 4));
	vector<Value*> throwParams{exception, nullInt8P, nullInt8P};
	builder.CreateCall(fThrow, ArrayRef<Value*>(throwParams));
	builder.CreateUnreachable();

	return blockException;
}

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(llvm::getGlobalContext(), pack, false);
        };

        case TypeOperator::LINK: {
        	llvm::StructType* conjuction = llvm::StructType::create(llvm::getGlobalContext());
        	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(llvm::getGlobalContext());
            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(llvm::getGlobalContext())); //id

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

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

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

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

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

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

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

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

                default:
                    assert(false);
            }
        }

        default:
            assert(false);
    }

    assert(false);
    return  nullptr;
}

void
LLVMLayer::initExceptionsSupport(){
	Type* typInt8Ptr = PointerType::get(IntegerType::get(module->getContext(), 8), 0);

	if (!module->getFunction("__cxa_throw")) {
		std::vector<Type*>	fThrowSignature{typInt8Ptr, typInt8Ptr, typInt8Ptr};

		 FunctionType* tyFThrow = FunctionType::get(
		  /*Result=*/Type::getVoidTy(module->getContext()),
		  /*Params=*/fThrowSignature,
		  /*isVarArg=*/false);

		llvm::Function::Create(
	 /*Type=*/tyFThrow,
	 /*Linkage=*/GlobalValue::ExternalLinkage,
	 /*Name=*/"__cxa_throw", module); // (external, no body)
	}

	if (!module->getFunction("__cxa_allocate_exception")) {
		 std::vector<Type*>fAllocateSignature{IntegerType::get(module->getContext(), 64)};

		 FunctionType* tyFAllocate = FunctionType::get(
		  /*Result=*/typInt8Ptr,
		  /*Params=*/fAllocateSignature,
		  /*isVarArg=*/false);

		llvm::Function::Create(
	 /*Type=*/tyFAllocate,
	 /*Linkage=*/GlobalValue::ExternalLinkage,
	 /*Name=*/"__cxa_allocate_exception", module); // (external, no body)
	}
}

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


