//===----------------------------------------------------------------------===//
//
// 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 "ScriptedFrame.h"

#include "lldb/Utility/DataBufferHeap.h"

using namespace lldb;
using namespace lldb_private;

void ScriptedFrame::CheckInterpreterAndScriptObject() const {
  lldbassert(m_script_object_sp && "Invalid Script Object.");
  lldbassert(GetInterface() && "Invalid Scripted Frame Interface.");
}

llvm::Expected<std::shared_ptr<ScriptedFrame>>
ScriptedFrame::Create(ScriptedThread &thread,
                      StructuredData::DictionarySP args_sp,
                      StructuredData::Generic *script_object) {
  if (!thread.IsValid())
    return llvm::createStringError("Invalid scripted thread.");

  thread.CheckInterpreterAndScriptObject();

  auto scripted_frame_interface =
      thread.GetInterface()->CreateScriptedFrameInterface();
  if (!scripted_frame_interface)
    return llvm::createStringError("failed to create scripted frame interface");

  llvm::StringRef frame_class_name;
  if (!script_object) {
    std::optional<std::string> class_name =
        thread.GetInterface()->GetScriptedFramePluginName();
    if (!class_name || class_name->empty())
      return llvm::createStringError(
          "failed to get scripted thread class name");
    frame_class_name = *class_name;
  }

  ExecutionContext exe_ctx(thread);
  auto obj_or_err = scripted_frame_interface->CreatePluginObject(
      frame_class_name, exe_ctx, args_sp, script_object);

  if (!obj_or_err)
    return llvm::createStringError(
        "failed to create script object: %s",
        llvm::toString(obj_or_err.takeError()).c_str());

  StructuredData::GenericSP owned_script_object_sp = *obj_or_err;

  if (!owned_script_object_sp->IsValid())
    return llvm::createStringError("created script object is invalid");

  lldb::user_id_t frame_id = scripted_frame_interface->GetID();

  lldb::addr_t pc = scripted_frame_interface->GetPC();
  SymbolContext sc;
  Address symbol_addr;
  if (pc != LLDB_INVALID_ADDRESS) {
    symbol_addr.SetLoadAddress(pc, &thread.GetProcess()->GetTarget());
    symbol_addr.CalculateSymbolContext(&sc);
  }

  std::optional<SymbolContext> maybe_sym_ctx =
      scripted_frame_interface->GetSymbolContext();
  if (maybe_sym_ctx) {
    sc = *maybe_sym_ctx;
  }

  StructuredData::DictionarySP reg_info =
      scripted_frame_interface->GetRegisterInfo();

  if (!reg_info)
    return llvm::createStringError(
        "failed to get scripted thread registers info");

  std::shared_ptr<DynamicRegisterInfo> register_info_sp =
      DynamicRegisterInfo::Create(
          *reg_info, thread.GetProcess()->GetTarget().GetArchitecture());

  lldb::RegisterContextSP reg_ctx_sp;

  std::optional<std::string> reg_data =
      scripted_frame_interface->GetRegisterContext();
  if (reg_data) {
    DataBufferSP data_sp(
        std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size()));

    if (!data_sp->GetByteSize())
      return llvm::createStringError("failed to copy raw registers data");

    std::shared_ptr<RegisterContextMemory> reg_ctx_memory =
        std::make_shared<RegisterContextMemory>(
            thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS);
    if (!reg_ctx_memory)
      return llvm::createStringError("failed to create a register context.");

    reg_ctx_memory->SetAllRegisterData(data_sp);
    reg_ctx_sp = reg_ctx_memory;
  }

  return std::make_shared<ScriptedFrame>(
      thread, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
      register_info_sp, owned_script_object_sp);
}

ScriptedFrame::ScriptedFrame(ScriptedThread &thread,
                             ScriptedFrameInterfaceSP interface_sp,
                             lldb::user_id_t id, lldb::addr_t pc,
                             SymbolContext &sym_ctx,
                             lldb::RegisterContextSP reg_ctx_sp,
                             std::shared_ptr<DynamicRegisterInfo> reg_info_sp,
                             StructuredData::GenericSP script_object_sp)
    : StackFrame(thread.shared_from_this(), /*frame_idx=*/id,
                 /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp,
                 /*cfa=*/0, /*pc=*/pc,
                 /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx),
      m_scripted_frame_interface_sp(interface_sp),
      m_script_object_sp(script_object_sp), m_register_info_sp(reg_info_sp) {}

ScriptedFrame::~ScriptedFrame() {}

const char *ScriptedFrame::GetFunctionName() {
  CheckInterpreterAndScriptObject();
  std::optional<std::string> function_name = GetInterface()->GetFunctionName();
  if (!function_name)
    return nullptr;
  return ConstString(function_name->c_str()).AsCString();
}

const char *ScriptedFrame::GetDisplayFunctionName() {
  CheckInterpreterAndScriptObject();
  std::optional<std::string> function_name =
      GetInterface()->GetDisplayFunctionName();
  if (!function_name)
    return nullptr;
  return ConstString(function_name->c_str()).AsCString();
}

bool ScriptedFrame::IsInlined() { return GetInterface()->IsInlined(); }

bool ScriptedFrame::IsArtificial() const {
  return GetInterface()->IsArtificial();
}

bool ScriptedFrame::IsHidden() { return GetInterface()->IsHidden(); }

lldb::ScriptedFrameInterfaceSP ScriptedFrame::GetInterface() const {
  return m_scripted_frame_interface_sp;
}

std::shared_ptr<DynamicRegisterInfo> ScriptedFrame::GetDynamicRegisterInfo() {
  CheckInterpreterAndScriptObject();

  if (!m_register_info_sp) {
    StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo();

    Status error;
    if (!reg_info)
      return ScriptedInterface::ErrorWithMessage<
          std::shared_ptr<DynamicRegisterInfo>>(
          LLVM_PRETTY_FUNCTION, "Failed to get scripted frame registers info.",
          error, LLDBLog::Thread);

    ThreadSP thread_sp = m_thread_wp.lock();
    if (!thread_sp || !thread_sp->IsValid())
      return ScriptedInterface::ErrorWithMessage<
          std::shared_ptr<DynamicRegisterInfo>>(
          LLVM_PRETTY_FUNCTION,
          "Failed to get scripted frame registers info: invalid thread.", error,
          LLDBLog::Thread);

    ProcessSP process_sp = thread_sp->GetProcess();
    if (!process_sp || !process_sp->IsValid())
      return ScriptedInterface::ErrorWithMessage<
          std::shared_ptr<DynamicRegisterInfo>>(
          LLVM_PRETTY_FUNCTION,
          "Failed to get scripted frame registers info: invalid process.",
          error, LLDBLog::Thread);

    m_register_info_sp = DynamicRegisterInfo::Create(
        *reg_info, process_sp->GetTarget().GetArchitecture());
  }

  return m_register_info_sp;
}