//===-- NSIndexPath.cpp ---------------------------------------------------===//
//
// 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 "Cocoa.h"

#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/TypeSynthetic.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"

#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

static constexpr size_t PACKED_INDEX_SHIFT_64(size_t i) {
  return (60 - (13 * (4 - i)));
}

static constexpr size_t PACKED_INDEX_SHIFT_32(size_t i) {
  return (32 - (13 * (2 - i)));
}

class NSIndexPathSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
public:
  NSIndexPathSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
      : SyntheticChildrenFrontEnd(*valobj_sp.get()), m_descriptor_sp(nullptr),
        m_impl(), m_uint_star_type() {
    m_ptr_size =
        m_backend.GetTargetSP()->GetArchitecture().GetAddressByteSize();
  }

  ~NSIndexPathSyntheticFrontEnd() override = default;

  llvm::Expected<uint32_t> CalculateNumChildren() override {
    return m_impl.GetNumIndexes();
  }

  lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override {
    return m_impl.GetIndexAtIndex(idx, m_uint_star_type);
  }

  lldb::ChildCacheState Update() override {
    m_impl.Clear();

    auto type_system = m_backend.GetCompilerType().GetTypeSystem();
    if (!type_system)
      return lldb::ChildCacheState::eRefetch;

    auto ast = ScratchTypeSystemClang::GetForTarget(
        *m_backend.GetExecutionContextRef().GetTargetSP());
    if (!ast)
      return lldb::ChildCacheState::eRefetch;

    m_uint_star_type = ast->GetPointerSizedIntType(false);

    static ConstString g__indexes("_indexes");
    static ConstString g__length("_length");

    ProcessSP process_sp = m_backend.GetProcessSP();
    if (!process_sp)
      return lldb::ChildCacheState::eRefetch;

    ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp);

    if (!runtime)
      return lldb::ChildCacheState::eRefetch;

    ObjCLanguageRuntime::ClassDescriptorSP descriptor(
        runtime->GetClassDescriptor(m_backend));

    if (!descriptor.get() || !descriptor->IsValid())
      return lldb::ChildCacheState::eRefetch;

    uint64_t info_bits(0), value_bits(0), payload(0);

    if (descriptor->GetTaggedPointerInfo(&info_bits, &value_bits, &payload)) {
      m_impl.m_inlined.SetIndexes(payload, *process_sp);
      m_impl.m_mode = Mode::Inlined;
    } else {
      ObjCLanguageRuntime::ClassDescriptor::iVarDescriptor _indexes_id;
      ObjCLanguageRuntime::ClassDescriptor::iVarDescriptor _length_id;

      bool has_indexes(false), has_length(false);

      for (size_t x = 0; x < descriptor->GetNumIVars(); x++) {
        const auto &ivar = descriptor->GetIVarAtIndex(x);
        if (ivar.m_name == g__indexes) {
          _indexes_id = ivar;
          has_indexes = true;
        } else if (ivar.m_name == g__length) {
          _length_id = ivar;
          has_length = true;
        }

        if (has_length && has_indexes)
          break;
      }

      if (has_length && has_indexes) {
        m_impl.m_outsourced.m_indexes =
            m_backend
                .GetSyntheticChildAtOffset(_indexes_id.m_offset,
                                           m_uint_star_type.GetPointerType(),
                                           true)
                .get();
        ValueObjectSP length_sp(m_backend.GetSyntheticChildAtOffset(
            _length_id.m_offset, m_uint_star_type, true));
        if (length_sp) {
          m_impl.m_outsourced.m_count = length_sp->GetValueAsUnsigned(0);
          if (m_impl.m_outsourced.m_indexes)
            m_impl.m_mode = Mode::Outsourced;
        }
      }
    }
    return lldb::ChildCacheState::eRefetch;
  }

  bool MightHaveChildren() override { return m_impl.m_mode != Mode::Invalid; }

  llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
    auto optional_idx = ExtractIndexFromString(name.AsCString());
    if (!optional_idx) {
      return llvm::createStringError("Type has no child named '%s'",
                                     name.AsCString());
    }
    uint32_t idx = *optional_idx;
    if (idx >= CalculateNumChildrenIgnoringErrors())
      return llvm::createStringError("Type has no child named '%s'",
                                     name.AsCString());
    return idx;
  }

  lldb::ValueObjectSP GetSyntheticValue() override { return nullptr; }

protected:
  ObjCLanguageRuntime::ClassDescriptorSP m_descriptor_sp;

  enum class Mode { Inlined, Outsourced, Invalid };

  struct Impl {
    size_t GetNumIndexes() {
      switch (m_mode) {
      case Mode::Inlined:
        return m_inlined.GetNumIndexes();
      case Mode::Outsourced:
        return m_outsourced.m_count;
      default:
        return 0;
      }
    }

    lldb::ValueObjectSP GetIndexAtIndex(size_t idx,
                                        const CompilerType &desired_type) {
      if (idx >= GetNumIndexes())
        return nullptr;
      switch (m_mode) {
      default:
        return nullptr;
      case Mode::Inlined:
        return m_inlined.GetIndexAtIndex(idx, desired_type);
      case Mode::Outsourced:
        return m_outsourced.GetIndexAtIndex(idx);
      }
    }

    struct InlinedIndexes {
    public:
      void SetIndexes(uint64_t value, Process &p) {
        m_indexes = value;
        _lengthForInlinePayload(p.GetAddressByteSize());
        m_process = &p;
      }

      size_t GetNumIndexes() { return m_count; }

      lldb::ValueObjectSP GetIndexAtIndex(size_t idx,
                                          const CompilerType &desired_type) {
        if (!m_process)
          return nullptr;

        std::pair<uint64_t, bool> value(_indexAtPositionForInlinePayload(idx));
        if (!value.second)
          return nullptr;

        Value v;
        if (m_ptr_size == 8) {
          Scalar scalar((unsigned long long)value.first);
          v = Value(scalar);
        } else {
          Scalar scalar((unsigned int)value.first);
          v = Value(scalar);
        }

        v.SetCompilerType(desired_type);

        StreamString idx_name;
        idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);

        return ValueObjectConstResult::Create(
            m_process, v, ConstString(idx_name.GetString()));
      }

      void Clear() {
        m_indexes = 0;
        m_count = 0;
        m_ptr_size = 0;
        m_process = nullptr;
      }

      InlinedIndexes() {}

    private:
      uint64_t m_indexes = 0;
      size_t m_count = 0;
      uint32_t m_ptr_size = 0;
      Process *m_process = nullptr;

      // cfr. Foundation for the details of this code
      size_t _lengthForInlinePayload(uint32_t ptr_size) {
        m_ptr_size = ptr_size;
        if (m_ptr_size == 8)
          m_count = ((m_indexes >> 3) & 0x7);
        else
          m_count = ((m_indexes >> 3) & 0x3);
        return m_count;
      }

      std::pair<uint64_t, bool> _indexAtPositionForInlinePayload(size_t pos) {
        static const uint64_t PACKED_INDEX_MASK = ((1 << 13) - 1);
        if (m_ptr_size == 8) {
          switch (pos) {
          case 3:
          case 2:
          case 1:
          case 0:
            return {(m_indexes >> PACKED_INDEX_SHIFT_64(pos)) &
                        PACKED_INDEX_MASK,
                    true};
          default:
            return {0, false};
          }
        } else {
          switch (pos) {
          case 0:
          case 1:
            return {(m_indexes >> PACKED_INDEX_SHIFT_32(pos)) &
                        PACKED_INDEX_MASK,
                    true};
          default:
            return {0, false};
          }
        }
        return {0, false};
      }
    };

    struct OutsourcedIndexes {
      lldb::ValueObjectSP GetIndexAtIndex(size_t idx) {
        if (m_indexes) {
          ValueObjectSP index_sp(m_indexes->GetSyntheticArrayMember(idx, true));
          return index_sp;
        }
        return nullptr;
      }

      void Clear() {
        m_indexes = nullptr;
        m_count = 0;
      }

      OutsourcedIndexes() {}

      ValueObject *m_indexes = nullptr;
      size_t m_count = 0;
    };

    union {
      struct InlinedIndexes m_inlined;
      struct OutsourcedIndexes m_outsourced;
    };

    void Clear() {
      switch (m_mode) {
      case Mode::Inlined:
        m_inlined.Clear();
        break;
      case Mode::Outsourced:
        m_outsourced.Clear();
        break;
      case Mode::Invalid:
        break;
      }
      m_mode = Mode::Invalid;
    }

    Impl() {}

    Mode m_mode = Mode::Invalid;
  } m_impl;

  uint32_t m_ptr_size = 0;
  CompilerType m_uint_star_type;
};

namespace lldb_private {
namespace formatters {

SyntheticChildrenFrontEnd *
NSIndexPathSyntheticFrontEndCreator(CXXSyntheticChildren *,
                                    lldb::ValueObjectSP valobj_sp) {
  if (valobj_sp)
    return new NSIndexPathSyntheticFrontEnd(valobj_sp);
  return nullptr;
}

} // namespace formatters
} // namespace lldb_private
