/* 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) return source;
  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;
}

/**
 * \brief Performs basic type inference to deduce the type of the given expression
 * 
 * Tries several strategies in the following order:
 *  - Looks at expression's type if it has one.
 *  - Looks at Attachment<TypeInferred> if it has one. This allows assign expression's type by analyses done elsewhere.
 *  - For a number literal assumes i32.
 *  
 * \param expression Infers the given expression's type.
 * \param ast AST instance.
 */ 
ExpandedType
getType(const Expression& expression, const TypeAnnotation& expectedT, const AST& ast) {
  if(expression.type.isValid()) {
    return ast.expandType(expression.type);
  }

  if(expectedT.isValid()){
    return ast.expandType(expectedT);
  }

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

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

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

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

ExpandedType
getSubtype(const ExpandedType& type, const std::string& index){
  for(size_t idx = 0; idx < type->fields.size(); ++idx){
    if (index == type->fields.at(idx)){
      return ExpandedType(type->__operands.at(idx));
    }
  }

  assert(false);
  return ExpandedType(TypeAnnotation());
}

}
} //end of namespace xreate::typeinference
