//===-- irtypeclass.cpp ---------------------------------------------------===//
//
//                         LDC â€“ the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/IR/DerivedTypes.h"

#include "aggregate.h"
#include "declaration.h"
#include "dsymbol.h"
#include "mtype.h"
#include "target.h"
#include "template.h"

#include "gen/irstate.h"
#include "gen/logger.h"
#include "gen/tollvm.h"
#include "gen/llvmhelpers.h"
#include "gen/functions.h"
#include "ir/irtypeclass.h"

IrTypeClass::IrTypeClass(ClassDeclaration *cd)
    : IrTypeAggr(cd), cd(cd), tc(static_cast<TypeClass *>(cd->type)) {
  vtbl_type = LLArrayType::get(getVoidPtrType(), cd->vtbl.dim);
}

void IrTypeClass::addClassData(AggrTypeBuilder &builder,
                               ClassDeclaration *currCd) {
  // First, recursively add the fields for our base class and interfaces, if
  // any.
  if (currCd->baseClass) {
    addClassData(builder, currCd->baseClass);
  }

  if (currCd->vtblInterfaces && currCd->vtblInterfaces->dim > 0) {
    // KLUDGE: The first pointer in the vtbl will be of type object.Interface;
    // extract that from the "well-known" object.TypeInfo_Class definition.
    // For C++ interfaces, this vtbl entry has to be omitted

    builder.alignCurrentOffset(Target::ptrsize);

    for (auto b : *currCd->vtblInterfaces) {
      IF_LOG Logger::println("Adding interface vtbl for %s",
                             b->sym->toPrettyChars());

      // add to the interface map
      addInterfaceToMap(b->sym, builder.currentFieldIndex());
      auto vtblTy = LLArrayType::get(getVoidPtrType(), b->sym->vtbl.dim);
      builder.addType(llvm::PointerType::get(vtblTy, 0), Target::ptrsize);

      ++num_interface_vtbls;
    }
  }

  // Finally, the data members for this class.
  builder.addAggregate(currCd);
}

IrTypeClass *IrTypeClass::get(ClassDeclaration *cd) {
  const auto t = new IrTypeClass(cd);
  cd->type->ctype = t;

  IF_LOG Logger::println("Building class type %s @ %s", cd->toPrettyChars(),
                         cd->loc.toChars());
  LOG_SCOPE;
  IF_LOG Logger::println("Instance size: %u", cd->structsize);

  // This class may contain an align declaration. See GitHub #726.
  t->packed = false;
  for (auto base = cd; base != nullptr && !t->packed; base = base->baseClass) {
    t->packed = isPacked(base);
  }

  AggrTypeBuilder builder(t->packed);

  // add vtbl
  builder.addType(llvm::PointerType::get(t->vtbl_type, 0), Target::ptrsize);

  if (cd->isInterfaceDeclaration()) {
    // interfaces are just a vtable
    t->num_interface_vtbls = cd->vtblInterfaces ? cd->vtblInterfaces->dim : 0;
  } else {
    // classes have monitor and fields
    if (!cd->isCPPclass() && !cd->isCPPinterface()) {
      // add monitor
      builder.addType(
          llvm::PointerType::get(llvm::Type::getInt8Ty(gIR->context()), 0),
          Target::ptrsize);
    }

    // add data members recursively
    t->addClassData(builder, cd);

    // add tail padding
    builder.addTailPadding(cd->structsize);
  }

  // set struct body and copy GEP indices
  isaStruct(t->type)->setBody(builder.defaultTypes(), t->packed);
  t->varGEPIndices = builder.varGEPIndices();

  IF_LOG Logger::cout() << "class type: " << *t->type << std::endl;

  return t;
}

llvm::Type *IrTypeClass::getLLType() { return llvm::PointerType::get(type, 0); }

llvm::Type *IrTypeClass::getMemoryLLType() { return type; }

size_t IrTypeClass::getInterfaceIndex(ClassDeclaration *inter) {
  auto it = interfaceMap.find(inter);
  if (it == interfaceMap.end()) {
    return ~0UL;
  }
  return it->second;
}

void IrTypeClass::addInterfaceToMap(ClassDeclaration *inter, size_t index) {
  // don't duplicate work or overwrite indices
  if (interfaceMap.find(inter) != interfaceMap.end()) {
    return;
  }

  // add this interface
  interfaceMap.insert(std::make_pair(inter, index));

  // add the direct base interfaces recursively - they
  // are accessed through the same index
  if (inter->interfaces.length > 0) {
    BaseClass *b = inter->interfaces.ptr[0];
    addInterfaceToMap(b->sym, index);
  }
}
