/* 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/.
 *
 * typeinference.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on April 16, 2017, 10:13 AM
 */

/**
 *  \file typeinference.h
 *  \brief Type inference analysis
 */

#include "typeinference.h"
#include "llvmlayer.h"
#include "transcendlayer.h"

#include "llvm/IR/Function.h"
#include "llvm/IR/DerivedTypes.h"

using namespace std;

namespace xreate{
namespace typeinference{

//TODO type conversion:
//a)    automatically expand types int -> bigger int; int -> floating
//b)    detect exact type of `num` based on max used numeral / function type
//c)    warning if need to truncate (allow/dissallow based on annotations)

llvm::Value*
doAutomaticTypeConversion(llvm::Value* source, llvm::Type* tyTarget, llvm::IRBuilder<>& builder) {
    if(tyTarget->isIntegerTy() && source->getType()->isIntegerTy()) {
        llvm::IntegerType* tyTargetInt = llvm::dyn_cast<llvm::IntegerType>(tyTarget);
        llvm::IntegerType* tySourceInt = llvm::dyn_cast<llvm::IntegerType>(source->getType());

        if(tyTargetInt->getBitWidth() < tySourceInt->getBitWidth()) {
            return builder.CreateCast(llvm::Instruction::Trunc, source, tyTarget);
        }

        if(tyTargetInt->getBitWidth() > tySourceInt->getBitWidth()) {
            return builder.CreateCast(llvm::Instruction::SExt, source, tyTarget);
        }
    }

    if(source->getType()->isIntegerTy() && tyTarget->isFloatingPointTy()) {
        return builder.CreateCast(llvm::Instruction::SIToFP, source, tyTarget);
    }
    
    if (source->getType()->isStructTy() && tyTarget->isIntegerTy()){
        llvm::StructType* sourceST = llvm::cast<llvm::StructType>(source->getType());
        if(sourceST->getNumElements() == 1) {
            llvm::Value* sourceElRaw = builder.CreateExtractValue(source, llvm::ArrayRef<unsigned>({0}));
            return doAutomaticTypeConversion(sourceElRaw, tyTarget, builder);
        }
    }

    return source;
}

ExpandedType
getType(const Expression& expression, const AST& ast) {
    if(expression.type.isValid()) {
        return ast.expandType(expression.type);
    }

    if(expression.__state == Expression::IDENT) {
        Symbol s = Attachments::get<IdentifierSymbol>(expression);
        return getType(CodeScope::getDefinition(s), ast);
    }

    if(Attachments::exists<TypeInferred>(expression)) {
        return Attachments::get<TypeInferred>(expression);
    }

    if(expression.__state == Expression::NUMBER) {
        return ExpandedType(TypeAnnotation(TypePrimitive::I32));
    }


    assert(false && "Type can't be determined for an expression");
}

}
} //end of namespace xreate::typeinference
