// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/v8windbg/base/utilities.h"

#include <comutil.h>
#include <oleauto.h>

#include <vector>

namespace {

HRESULT BoxObject(IDataModelManager* p_manager, IUnknown* p_object,
                  ModelObjectKind kind, IModelObject** pp_model_object) {
  *pp_model_object = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_UNKNOWN;
  vt_val.punkVal = p_object;

  HRESULT hr = p_manager->CreateIntrinsicObject(kind, &vt_val, pp_model_object);
  return hr;
}

}  // namespace

HRESULT CreateProperty(IDataModelManager* p_manager,
                       IModelPropertyAccessor* p_property,
                       IModelObject** pp_property_object) {
  return BoxObject(p_manager, p_property, ObjectPropertyAccessor,
                   pp_property_object);
}

HRESULT CreateMethod(IDataModelManager* p_manager, IModelMethod* p_method,
                     IModelObject** pp_method_object) {
  return BoxObject(p_manager, p_method, ObjectMethod, pp_method_object);
}

HRESULT UnboxProperty(IModelObject* object, IModelPropertyAccessor** result) {
  ModelObjectKind kind = (ModelObjectKind)-1;
  RETURN_IF_FAIL(object->GetKind(&kind));
  if (kind != ObjectPropertyAccessor) return E_FAIL;
  _variant_t variant;
  RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
  if (variant.vt != VT_UNKNOWN) return E_FAIL;
  WRL::ComPtr<IModelPropertyAccessor> accessor;
  RETURN_IF_FAIL(WRL::ComPtr<IUnknown>(variant.punkVal).As(&accessor));
  *result = accessor.Detach();
  return S_OK;
}

HRESULT CreateTypedIntrinsic(uint64_t value, IDebugHostType* type,
                             IModelObject** result) {
  // Figure out what kind of VARIANT we need to make.
  IntrinsicKind kind;
  VARTYPE carrier;
  RETURN_IF_FAIL(type->GetIntrinsicType(&kind, &carrier));

  VARIANT vt_val;
  switch (carrier) {
    case VT_BOOL:
      vt_val.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE;
      break;
    case VT_I1:
      vt_val.cVal = static_cast<int8_t>(value);
      break;
    case VT_UI1:
      vt_val.bVal = static_cast<uint8_t>(value);
      break;
    case VT_I2:
      vt_val.iVal = static_cast<int16_t>(value);
      break;
    case VT_UI2:
      vt_val.uiVal = static_cast<uint16_t>(value);
      break;
    case VT_INT:
      vt_val.intVal = static_cast<int>(value);
      break;
    case VT_UINT:
      vt_val.uintVal = static_cast<unsigned int>(value);
      break;
    case VT_I4:
      vt_val.lVal = static_cast<int32_t>(value);
      break;
    case VT_UI4:
      vt_val.ulVal = static_cast<uint32_t>(value);
      break;
    case VT_INT_PTR:
      vt_val.llVal = static_cast<intptr_t>(value);
      break;
    case VT_UINT_PTR:
      vt_val.ullVal = static_cast<uintptr_t>(value);
      break;
    case VT_I8:
      vt_val.llVal = static_cast<int64_t>(value);
      break;
    case VT_UI8:
      vt_val.ullVal = static_cast<uint64_t>(value);
      break;
    default:
      return E_FAIL;
  }
  vt_val.vt = carrier;
  return sp_data_model_manager->CreateTypedIntrinsicObject(&vt_val, type,
                                                           result);
}

HRESULT CreateULong64(ULONG64 value, IModelObject** pp_int) {
  HRESULT hr = S_OK;
  *pp_int = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_UI8;
  vt_val.ullVal = value;

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_int);
  return hr;
}

HRESULT UnboxULong64(IModelObject* object, ULONG64* value, bool convert) {
  ModelObjectKind kind = (ModelObjectKind)-1;
  RETURN_IF_FAIL(object->GetKind(&kind));
  if (kind != ObjectIntrinsic) return E_FAIL;
  _variant_t variant;
  RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
  if (convert) {
    RETURN_IF_FAIL(VariantChangeType(&variant, &variant, 0, VT_UI8));
  }
  if (variant.vt != VT_UI8) return E_FAIL;
  *value = variant.ullVal;
  return S_OK;
}

HRESULT GetInt32(IDebugHostConstant* object, int* value) {
  variant_t variant;
  RETURN_IF_FAIL(object->GetValue(&variant));

  if (variant.vt != VT_I4) return E_FAIL;
  *value = variant.lVal;
  return S_OK;
}

HRESULT CreateInt32(int value, IModelObject** pp_int) {
  HRESULT hr = S_OK;
  *pp_int = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_I4;
  vt_val.intVal = value;

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_int);
  return hr;
}

HRESULT CreateUInt32(uint32_t value, IModelObject** pp_int) {
  HRESULT hr = S_OK;
  *pp_int = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_UI4;
  vt_val.uintVal = value;

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_int);
  return hr;
}

HRESULT CreateBool(bool value, IModelObject** pp_val) {
  HRESULT hr = S_OK;
  *pp_val = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_BOOL;
  vt_val.boolVal = value;

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_val);
  return hr;
}

HRESULT CreateNumber(double value, IModelObject** pp_val) {
  HRESULT hr = S_OK;
  *pp_val = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_R8;
  vt_val.dblVal = value;

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_val);
  return hr;
}

HRESULT CreateString(std::u16string value, IModelObject** pp_val) {
  HRESULT hr = S_OK;
  *pp_val = nullptr;

  VARIANT vt_val;
  vt_val.vt = VT_BSTR;
  vt_val.bstrVal =
      ::SysAllocString(reinterpret_cast<const OLECHAR*>(value.c_str()));

  hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
                                                    pp_val);
  return hr;
}

HRESULT UnboxString(IModelObject* object, BSTR* value) {
  ModelObjectKind kind = (ModelObjectKind)-1;
  RETURN_IF_FAIL(object->GetKind(&kind));
  if (kind != ObjectIntrinsic) return E_FAIL;
  _variant_t variant;
  RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
  if (variant.vt != VT_BSTR) return E_FAIL;
  *value = variant.Detach().bstrVal;
  return S_OK;
}

HRESULT GetModelAtIndex(WRL::ComPtr<IModelObject>& sp_parent,
                        WRL::ComPtr<IModelObject>& sp_index,
                        IModelObject** p_result) {
  WRL::ComPtr<IIndexableConcept> sp_indexable_concept;
  RETURN_IF_FAIL(sp_parent->GetConcept(__uuidof(IIndexableConcept),
                                       &sp_indexable_concept, nullptr));

  std::vector<IModelObject*> p_indexers{sp_index.Get()};
  return sp_indexable_concept->GetAt(sp_parent.Get(), 1, p_indexers.data(),
                                     p_result, nullptr);
}

HRESULT GetCurrentThread(WRL::ComPtr<IDebugHostContext>& sp_host_context,
                         IModelObject** p_current_thread) {
  WRL::ComPtr<IModelObject> sp_boxed_context, sp_root_namespace;
  WRL::ComPtr<IModelObject> sp_debugger, sp_sessions, sp_processes, sp_threads;
  WRL::ComPtr<IModelObject> sp_curr_session, sp_curr_process;

  RETURN_IF_FAIL(BoxObject(sp_data_model_manager.Get(), sp_host_context.Get(),
                           ObjectContext, &sp_boxed_context));
  RETURN_IF_FAIL(sp_data_model_manager->GetRootNamespace(&sp_root_namespace));
  RETURN_IF_FAIL(
      sp_root_namespace->GetKeyValue(L"Debugger", &sp_debugger, nullptr));
  RETURN_IF_FAIL(sp_debugger->GetKeyValue(L"Sessions", &sp_sessions, nullptr));
  RETURN_IF_FAIL(
      GetModelAtIndex(sp_sessions, sp_boxed_context, &sp_curr_session));
  RETURN_IF_FAIL(
      sp_curr_session->GetKeyValue(L"Processes", &sp_processes, nullptr));
  RETURN_IF_FAIL(
      GetModelAtIndex(sp_processes, sp_boxed_context, &sp_curr_process));
  RETURN_IF_FAIL(
      sp_curr_process->GetKeyValue(L"Threads", &sp_threads, nullptr));
  return GetModelAtIndex(sp_threads, sp_boxed_context, p_current_thread);
}
