//===-- GoLanguageRuntime.cpp --------------------------------------*- C++
//-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "GoLanguageRuntime.h"

#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Scalar.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectMemory.h"
#include "lldb/Symbol/GoASTContext.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Symbol/TypeList.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Status.h"
#include "llvm/ADT/Twine.h"

#include <vector>

using namespace lldb;
using namespace lldb_private;

namespace {
ValueObjectSP GetChild(ValueObject &obj, const char *name,
                       bool dereference = true) {
  ConstString name_const_str(name);
  ValueObjectSP result = obj.GetChildMemberWithName(name_const_str, true);
  if (dereference && result && result->IsPointerType()) {
    Status err;
    result = result->Dereference(err);
    if (err.Fail())
      result.reset();
  }
  return result;
}

ConstString ReadString(ValueObject &str, Process *process) {
  ConstString result;
  ValueObjectSP data = GetChild(str, "str", false);
  ValueObjectSP len = GetChild(str, "len");
  if (len && data) {
    Status err;
    lldb::addr_t addr = data->GetPointerValue();
    if (addr == LLDB_INVALID_ADDRESS)
      return result;
    uint64_t byte_size = len->GetValueAsUnsigned(0);
    char *buf = new char[byte_size + 1];
    buf[byte_size] = 0;
    size_t bytes_read = process->ReadMemory(addr, buf, byte_size, err);
    if (!(err.Fail() || bytes_read != byte_size))
      result = ConstString(buf, bytes_read);
    delete[] buf;
  }
  return result;
}

ConstString ReadTypeName(ValueObjectSP type, Process *process) {
  if (ValueObjectSP uncommon = GetChild(*type, "x")) {
    ValueObjectSP name = GetChild(*uncommon, "name");
    ValueObjectSP package = GetChild(*uncommon, "pkgpath");
    if (name && name->GetPointerValue() != 0 && package &&
        package->GetPointerValue() != 0) {
      ConstString package_const_str = ReadString(*package, process);
      ConstString name_const_str = ReadString(*name, process);
      if (package_const_str.GetLength() == 0)
        return name_const_str;
      return ConstString((package_const_str.GetStringRef() + "." +
                          name_const_str.GetStringRef())
                             .str());
    }
  }
  ValueObjectSP name = GetChild(*type, "_string");
  if (name)
    return ReadString(*name, process);
  return ConstString("");
}

CompilerType LookupRuntimeType(ValueObjectSP type, ExecutionContext *exe_ctx,
                               bool *is_direct) {
  uint8_t kind = GetChild(*type, "kind")->GetValueAsUnsigned(0);
  *is_direct = GoASTContext::IsDirectIface(kind);
  if (GoASTContext::IsPointerKind(kind)) {
    CompilerType type_ptr = type->GetCompilerType().GetPointerType();
    Status err;
    ValueObjectSP elem =
        type->CreateValueObjectFromAddress("elem", type->GetAddressOf() +
                                                       type->GetByteSize(),
                                           *exe_ctx, type_ptr)
            ->Dereference(err);
    if (err.Fail())
      return CompilerType();
    bool tmp_direct;
    return LookupRuntimeType(elem, exe_ctx, &tmp_direct).GetPointerType();
  }
  Target *target = exe_ctx->GetTargetPtr();
  Process *process = exe_ctx->GetProcessPtr();

  ConstString const_typename = ReadTypeName(type, process);
  if (const_typename.GetLength() == 0)
    return CompilerType();

  SymbolContext sc;
  TypeList type_list;
  llvm::DenseSet<SymbolFile *> searched_symbol_files;
  uint32_t num_matches = target->GetImages().FindTypes(
      sc, const_typename, false, 2, searched_symbol_files, type_list);
  if (num_matches > 0) {
    return type_list.GetTypeAtIndex(0)->GetFullCompilerType();
  }
  return CompilerType();
}
}

bool GoLanguageRuntime::CouldHaveDynamicValue(ValueObject &in_value) {
  return GoASTContext::IsGoInterface(in_value.GetCompilerType());
}

bool GoLanguageRuntime::GetDynamicTypeAndAddress(
    ValueObject &in_value, lldb::DynamicValueType use_dynamic,
    TypeAndOrName &class_type_or_name, Address &dynamic_address,
    Value::ValueType &value_type) {
  value_type = Value::eValueTypeScalar;
  class_type_or_name.Clear();
  if (CouldHaveDynamicValue(in_value)) {
    Status err;
    ValueObjectSP iface = in_value.GetStaticValue();
    ValueObjectSP data_sp = GetChild(*iface, "data", false);
    if (!data_sp)
      return false;

    if (ValueObjectSP tab = GetChild(*iface, "tab"))
      iface = tab;
    ValueObjectSP type = GetChild(*iface, "_type");
    if (!type) {
      return false;
    }

    bool direct;
    ExecutionContext exe_ctx(in_value.GetExecutionContextRef());
    CompilerType final_type = LookupRuntimeType(type, &exe_ctx, &direct);
    if (!final_type)
      return false;
    if (direct) {
      class_type_or_name.SetCompilerType(final_type);
    } else {
      // TODO: implement reference types or fix caller to support dynamic types
      // that aren't pointers
      // so we don't have to introduce this extra pointer.
      class_type_or_name.SetCompilerType(final_type.GetPointerType());
    }

    dynamic_address.SetLoadAddress(data_sp->GetPointerValue(),
                                   exe_ctx.GetTargetPtr());

    return true;
  }
  return false;
}

TypeAndOrName
GoLanguageRuntime::FixUpDynamicType(const TypeAndOrName &type_and_or_name,
                                    ValueObject &static_value) {
  return type_and_or_name;
}

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
LanguageRuntime *
GoLanguageRuntime::CreateInstance(Process *process,
                                  lldb::LanguageType language) {
  if (language == eLanguageTypeGo)
    return new GoLanguageRuntime(process);
  else
    return NULL;
}

void GoLanguageRuntime::Initialize() {
  PluginManager::RegisterPlugin(GetPluginNameStatic(), "Go Language Runtime",
                                CreateInstance);
}

void GoLanguageRuntime::Terminate() {
  PluginManager::UnregisterPlugin(CreateInstance);
}

lldb_private::ConstString GoLanguageRuntime::GetPluginNameStatic() {
  static ConstString g_name("golang");
  return g_name;
}

//------------------------------------------------------------------
// PluginInterface protocol
//------------------------------------------------------------------
lldb_private::ConstString GoLanguageRuntime::GetPluginName() {
  return GetPluginNameStatic();
}

uint32_t GoLanguageRuntime::GetPluginVersion() { return 1; }
