//===-- VectorType.cpp ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/DataFormatters/VectorType.h"

#include "lldb/Core/ValueObject.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/TypeSystem.h"
#include "lldb/Target/Target.h"

#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/Log.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

static CompilerType GetCompilerTypeForFormat(lldb::Format format,
                                             CompilerType element_type,
                                             TypeSystem *type_system) {
  lldbassert(type_system && "type_system needs to be not NULL");

  switch (format) {
  case lldb::eFormatAddressInfo:
  case lldb::eFormatPointer:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(
        eEncodingUint, 8 * type_system->GetPointerByteSize());

  case lldb::eFormatBoolean:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeBool);

  case lldb::eFormatBytes:
  case lldb::eFormatBytesWithASCII:
  case lldb::eFormatChar:
  case lldb::eFormatCharArray:
  case lldb::eFormatCharPrintable:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeChar);

  case lldb::eFormatComplex /* lldb::eFormatComplexFloat */:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeFloatComplex);

  case lldb::eFormatCString:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeChar)
        .GetPointerType();

  case lldb::eFormatFloat:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeFloat);

  case lldb::eFormatHex:
  case lldb::eFormatHexUppercase:
  case lldb::eFormatOctal:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeInt);

  case lldb::eFormatHexFloat:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeFloat);

  case lldb::eFormatUnicode16:
  case lldb::eFormatUnicode32:

  case lldb::eFormatUnsigned:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeUnsignedInt);

  case lldb::eFormatVectorOfChar:
    return type_system->GetBasicTypeFromAST(lldb::eBasicTypeChar);

  case lldb::eFormatVectorOfFloat32:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingIEEE754,
                                                            32);

  case lldb::eFormatVectorOfFloat64:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingIEEE754,
                                                            64);

  case lldb::eFormatVectorOfSInt16:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingSint, 16);

  case lldb::eFormatVectorOfSInt32:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingSint, 32);

  case lldb::eFormatVectorOfSInt64:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingSint, 64);

  case lldb::eFormatVectorOfSInt8:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingSint, 8);

  case lldb::eFormatVectorOfUInt128:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 128);

  case lldb::eFormatVectorOfUInt16:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 16);

  case lldb::eFormatVectorOfUInt32:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32);

  case lldb::eFormatVectorOfUInt64:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64);

  case lldb::eFormatVectorOfUInt8:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8);

  case lldb::eFormatDefault:
    return element_type;

  case lldb::eFormatBinary:
  case lldb::eFormatComplexInteger:
  case lldb::eFormatDecimal:
  case lldb::eFormatEnum:
  case lldb::eFormatInstruction:
  case lldb::eFormatOSType:
  case lldb::eFormatVoid:
  default:
    return type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8);
  }
}

static lldb::Format GetItemFormatForFormat(lldb::Format format,
                                           CompilerType element_type) {
  switch (format) {
  case lldb::eFormatVectorOfChar:
    return lldb::eFormatChar;

  case lldb::eFormatVectorOfFloat32:
  case lldb::eFormatVectorOfFloat64:
    return lldb::eFormatFloat;

  case lldb::eFormatVectorOfSInt16:
  case lldb::eFormatVectorOfSInt32:
  case lldb::eFormatVectorOfSInt64:
  case lldb::eFormatVectorOfSInt8:
    return lldb::eFormatDecimal;

  case lldb::eFormatVectorOfUInt128:
  case lldb::eFormatVectorOfUInt16:
  case lldb::eFormatVectorOfUInt32:
  case lldb::eFormatVectorOfUInt64:
  case lldb::eFormatVectorOfUInt8:
    return lldb::eFormatUnsigned;

  case lldb::eFormatBinary:
  case lldb::eFormatComplexInteger:
  case lldb::eFormatDecimal:
  case lldb::eFormatEnum:
  case lldb::eFormatInstruction:
  case lldb::eFormatOSType:
  case lldb::eFormatVoid:
    return eFormatHex;

  case lldb::eFormatDefault: {
    // special case the (default, char) combination to actually display as an
    // integer value most often, you won't want to see the ASCII characters...
    // (and if you do, eFormatChar is a keystroke away)
    bool is_char = element_type.IsCharType();
    bool is_signed = false;
    element_type.IsIntegerType(is_signed);
    return is_char ? (is_signed ? lldb::eFormatDecimal : eFormatHex) : format;
  } break;

  default:
    return format;
  }
}

static size_t CalculateNumChildren(
    CompilerType container_type, CompilerType element_type,
    lldb_private::ExecutionContextScope *exe_scope =
        nullptr // does not matter here because all we trade in are basic types
    ) {
  llvm::Optional<uint64_t> container_size =
      container_type.GetByteSize(exe_scope);
  llvm::Optional<uint64_t> element_size = element_type.GetByteSize(exe_scope);

  if (container_size && element_size && *element_size) {
    if (*container_size % *element_size)
      return 0;
    return *container_size / *element_size;
  }
  return 0;
}

namespace lldb_private {
namespace formatters {

class VectorTypeSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
public:
  VectorTypeSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
      : SyntheticChildrenFrontEnd(*valobj_sp), m_parent_format(eFormatInvalid),
        m_item_format(eFormatInvalid), m_child_type(), m_num_children(0) {}

  ~VectorTypeSyntheticFrontEnd() override = default;

  size_t CalculateNumChildren() override { return m_num_children; }

  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override {
    if (idx >= CalculateNumChildren())
      return {};
    llvm::Optional<uint64_t> size = m_child_type.GetByteSize(nullptr);
    if (!size)
      return {};
    auto offset = idx * *size;
    StreamString idx_name;
    idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
    ValueObjectSP child_sp(m_backend.GetSyntheticChildAtOffset(
        offset, m_child_type, true, ConstString(idx_name.GetString())));
    if (!child_sp)
      return child_sp;

    child_sp->SetFormat(m_item_format);

    return child_sp;
  }

  bool Update() override {
    m_parent_format = m_backend.GetFormat();
    CompilerType parent_type(m_backend.GetCompilerType());
    CompilerType element_type;
    parent_type.IsVectorType(&element_type, nullptr);
    TypeSystem *type_system = nullptr;
    if (auto target_sp = m_backend.GetTargetSP()) {
      auto type_system_or_err =
          target_sp->GetScratchTypeSystemForLanguage(lldb::eLanguageTypeC);
      if (auto err = type_system_or_err.takeError()) {
        LLDB_LOG_ERROR(
            lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS),
            std::move(err), "Unable to update from scratch TypeSystem");
      } else {
        type_system = &type_system_or_err.get();
      }
    }
    m_child_type =
        ::GetCompilerTypeForFormat(m_parent_format, element_type, type_system);
    m_num_children = ::CalculateNumChildren(parent_type, m_child_type);
    m_item_format = GetItemFormatForFormat(m_parent_format, m_child_type);
    return false;
  }

  bool MightHaveChildren() override { return true; }

  size_t GetIndexOfChildWithName(ConstString name) override {
    const char *item_name = name.GetCString();
    uint32_t idx = ExtractIndexFromString(item_name);
    if (idx < UINT32_MAX && idx >= CalculateNumChildren())
      return UINT32_MAX;
    return idx;
  }

private:
  lldb::Format m_parent_format;
  lldb::Format m_item_format;
  CompilerType m_child_type;
  size_t m_num_children;
};

} // namespace formatters
} // namespace lldb_private

bool lldb_private::formatters::VectorTypeSummaryProvider(
    ValueObject &valobj, Stream &s, const TypeSummaryOptions &) {
  auto synthetic_children =
      VectorTypeSyntheticFrontEndCreator(nullptr, valobj.GetSP());
  if (!synthetic_children)
    return false;

  synthetic_children->Update();

  s.PutChar('(');
  bool first = true;

  size_t idx = 0, len = synthetic_children->CalculateNumChildren();

  for (; idx < len; idx++) {
    auto child_sp = synthetic_children->GetChildAtIndex(idx);
    if (!child_sp)
      continue;
    child_sp = child_sp->GetQualifiedRepresentationIfAvailable(
        lldb::eDynamicDontRunTarget, true);

    const char *child_value = child_sp->GetValueAsCString();
    if (child_value && *child_value) {
      if (first) {
        s.Printf("%s", child_value);
        first = false;
      } else {
        s.Printf(", %s", child_value);
      }
    }
  }

  s.PutChar(')');

  return true;
}

lldb_private::SyntheticChildrenFrontEnd *
lldb_private::formatters::VectorTypeSyntheticFrontEndCreator(
    CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
  if (!valobj_sp)
    return nullptr;
  return new VectorTypeSyntheticFrontEnd(valobj_sp);
}
