// Copyright 2017 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 "src/debug/debug-scope-iterator.h"

#include "src/api.h"
#include "src/debug/debug.h"
#include "src/debug/liveedit.h"
#include "src/frames-inl.h"
#include "src/isolate.h"
#include "src/wasm/wasm-objects-inl.h"

namespace v8 {

std::unique_ptr<debug::ScopeIterator> debug::ScopeIterator::CreateForFunction(
    v8::Isolate* v8_isolate, v8::Local<v8::Function> v8_func) {
  internal::Handle<internal::JSReceiver> receiver =
      internal::Handle<internal::JSReceiver>::cast(Utils::OpenHandle(*v8_func));

  // Besides JSFunction and JSBoundFunction, {v8_func} could be an
  // ObjectTemplate with a CallAsFunctionHandler. We only handle plain
  // JSFunctions.
  if (!receiver->IsJSFunction()) return nullptr;

  internal::Handle<internal::JSFunction> function =
      internal::Handle<internal::JSFunction>::cast(receiver);

  // Blink has function objects with callable map, JS_SPECIAL_API_OBJECT_TYPE
  // but without context on heap.
  if (!function->has_context()) return nullptr;
  return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
      reinterpret_cast<internal::Isolate*>(v8_isolate), function));
}

std::unique_ptr<debug::ScopeIterator>
debug::ScopeIterator::CreateForGeneratorObject(
    v8::Isolate* v8_isolate, v8::Local<v8::Object> v8_generator) {
  internal::Handle<internal::Object> generator =
      Utils::OpenHandle(*v8_generator);
  DCHECK(generator->IsJSGeneratorObject());
  return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
      reinterpret_cast<internal::Isolate*>(v8_isolate),
      internal::Handle<internal::JSGeneratorObject>::cast(generator)));
}

namespace internal {

DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
                                       FrameInspector* frame_inspector)
    : iterator_(isolate, frame_inspector) {
  if (!Done() && ShouldIgnore()) Advance();
}

DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
                                       Handle<JSFunction> function)
    : iterator_(isolate, function) {
  if (!Done() && ShouldIgnore()) Advance();
}

DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
                                       Handle<JSGeneratorObject> generator)
    : iterator_(isolate, generator) {
  if (!Done() && ShouldIgnore()) Advance();
}

bool DebugScopeIterator::Done() { return iterator_.Done(); }

void DebugScopeIterator::Advance() {
  DCHECK(!Done());
  iterator_.Next();
  while (!Done() && ShouldIgnore()) {
    iterator_.Next();
  }
}

bool DebugScopeIterator::ShouldIgnore() {
  // Almost always Script scope will be empty, so just filter out that noise.
  // Also drop empty Block, Eval and Script scopes, should we get any.
  DCHECK(!Done());
  debug::ScopeIterator::ScopeType type = GetType();
  if (type != debug::ScopeIterator::ScopeTypeBlock &&
      type != debug::ScopeIterator::ScopeTypeScript &&
      type != debug::ScopeIterator::ScopeTypeEval &&
      type != debug::ScopeIterator::ScopeTypeModule) {
    return false;
  }

  // TODO(kozyatinskiy): make this function faster.
  Handle<JSObject> value;
  if (!iterator_.ScopeObject().ToHandle(&value)) return false;
  Handle<FixedArray> keys =
      KeyAccumulator::GetKeys(value, KeyCollectionMode::kOwnOnly,
                              ENUMERABLE_STRINGS,
                              GetKeysConversion::kConvertToString)
          .ToHandleChecked();
  return keys->length() == 0;
}

v8::debug::ScopeIterator::ScopeType DebugScopeIterator::GetType() {
  DCHECK(!Done());
  return static_cast<v8::debug::ScopeIterator::ScopeType>(iterator_.Type());
}

v8::Local<v8::Object> DebugScopeIterator::GetObject() {
  DCHECK(!Done());
  Handle<JSObject> value;
  if (iterator_.ScopeObject().ToHandle(&value)) {
    return Utils::ToLocal(value);
  }
  return v8::Local<v8::Object>();
}

v8::Local<v8::Function> DebugScopeIterator::GetFunction() {
  DCHECK(!Done());
  Handle<JSFunction> closure = iterator_.GetClosure();
  if (closure.is_null()) return v8::Local<v8::Function>();
  return Utils::ToLocal(closure);
}

debug::Location DebugScopeIterator::GetStartLocation() {
  DCHECK(!Done());
  Handle<JSFunction> closure = iterator_.GetClosure();
  if (closure.is_null()) return debug::Location();
  Object* obj = closure->shared()->script();
  if (!obj->IsScript()) return debug::Location();
  return ToApiHandle<v8::debug::Script>(handle(Script::cast(obj)))
      ->GetSourceLocation(iterator_.start_position());
}

debug::Location DebugScopeIterator::GetEndLocation() {
  DCHECK(!Done());
  Handle<JSFunction> closure = iterator_.GetClosure();
  if (closure.is_null()) return debug::Location();
  Object* obj = closure->shared()->script();
  if (!obj->IsScript()) return debug::Location();
  return ToApiHandle<v8::debug::Script>(handle(Script::cast(obj)))
      ->GetSourceLocation(iterator_.end_position());
}

bool DebugScopeIterator::SetVariableValue(v8::Local<v8::String> name,
                                          v8::Local<v8::Value> value) {
  DCHECK(!Done());
  return iterator_.SetVariableValue(Utils::OpenHandle(*name),
                                    Utils::OpenHandle(*value));
}

DebugWasmScopeIterator::DebugWasmScopeIterator(Isolate* isolate,
                                               StandardFrame* frame,
                                               int inlined_frame_index)
    : isolate_(isolate),
      frame_(frame),
      inlined_frame_index_(inlined_frame_index),
      type_(debug::ScopeIterator::ScopeTypeGlobal) {}

bool DebugWasmScopeIterator::Done() {
  return type_ != debug::ScopeIterator::ScopeTypeGlobal &&
         type_ != debug::ScopeIterator::ScopeTypeLocal;
}

void DebugWasmScopeIterator::Advance() {
  DCHECK(!Done());
  if (type_ == debug::ScopeIterator::ScopeTypeGlobal) {
    type_ = debug::ScopeIterator::ScopeTypeLocal;
  } else {
    // We use ScopeTypeWith type as marker for done.
    type_ = debug::ScopeIterator::ScopeTypeWith;
  }
}

v8::debug::ScopeIterator::ScopeType DebugWasmScopeIterator::GetType() {
  DCHECK(!Done());
  return type_;
}

v8::Local<v8::Object> DebugWasmScopeIterator::GetObject() {
  DCHECK(!Done());
  Handle<WasmDebugInfo> debug_info(
      WasmInterpreterEntryFrame::cast(frame_)->wasm_instance()->debug_info(),
      isolate_);
  switch (type_) {
    case debug::ScopeIterator::ScopeTypeGlobal:
      return Utils::ToLocal(WasmDebugInfo::GetGlobalScopeObject(
          debug_info, frame_->fp(), inlined_frame_index_));
    case debug::ScopeIterator::ScopeTypeLocal:
      return Utils::ToLocal(WasmDebugInfo::GetLocalScopeObject(
          debug_info, frame_->fp(), inlined_frame_index_));
    default:
      return v8::Local<v8::Object>();
  }
  return v8::Local<v8::Object>();
}

v8::Local<v8::Function> DebugWasmScopeIterator::GetFunction() {
  DCHECK(!Done());
  return v8::Local<v8::Function>();
}

debug::Location DebugWasmScopeIterator::GetStartLocation() {
  DCHECK(!Done());
  return debug::Location();
}

debug::Location DebugWasmScopeIterator::GetEndLocation() {
  DCHECK(!Done());
  return debug::Location();
}

bool DebugWasmScopeIterator::SetVariableValue(v8::Local<v8::String> name,
                                              v8::Local<v8::Value> value) {
  DCHECK(!Done());
  return false;
}
}  // namespace internal
}  // namespace v8