namespace {
using cobalt::bindings::testing::IndexedGetterInterface;
using cobalt::bindings::testing::V8cIndexedGetterInterface;
using cobalt::script::CallbackInterfaceTraits;
using cobalt::script::GlobalEnvironment;
using cobalt::script::ScriptValue;
using cobalt::script::ValueHandle;
using cobalt::script::ValueHandle;
using cobalt::script::ValueHandleHolder;
using cobalt::script::Wrappable;

using cobalt::script::v8c::EntryScope;
using cobalt::script::v8c::EscapableEntryScope;
using cobalt::script::v8c::FromJSValue;
using cobalt::script::v8c::kConversionFlagClamped;
using cobalt::script::v8c::kConversionFlagNullable;
using cobalt::script::v8c::kConversionFlagObjectOnly;
using cobalt::script::v8c::kConversionFlagRestricted;
using cobalt::script::v8c::kConversionFlagTreatNullAsEmptyString;
using cobalt::script::v8c::kConversionFlagTreatUndefinedAsEmptyString;
using cobalt::script::v8c::kNoConversionFlags;
using cobalt::script::v8c::NewInternalString;
using cobalt::script::v8c::ToJSValue;
using cobalt::script::v8c::TypeTraits;
using cobalt::script::v8c::V8cExceptionState;
using cobalt::script::v8c::V8cGlobalEnvironment;
using cobalt::script::v8c::V8cPropertyEnumerator;
using cobalt::script::v8c::WrapperFactory;
using cobalt::script::v8c::WrapperPrivate;
}  // namespace

namespace cobalt {
namespace bindings {
namespace testing {


namespace {

const int kInterfaceUniqueId = 27;





void IndexedPropertyGetterCallback(
    uint32_t index,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  if (index >= impl->length()) {
    // |index| is out of bounds, so return undefined.
    return;
  }

  if (!exception_state.is_exception_set()) {
    ToJSValue(isolate,
              impl->IndexedGetter(index),
              &result_value);
}
  info.GetReturnValue().Set(result_value);
}

void IndexedPropertyDescriptorCallback(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // TODO: Figure out under what conditions this gets called.  It's not
  // getting called in our tests.
  NOTIMPLEMENTED();
}

void IndexedPropertyEnumeratorCallback(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::Object> object = info.Holder();
  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  const uint32_t length = impl->length();
  v8::Local<v8::Array> array = v8::Array::New(isolate, length);
  for (uint32_t i = 0; i < length; ++i) {
    array->Set(context, i, v8::Integer::New(isolate, i)).Check();
  }
  info.GetReturnValue().Set(array);
}

void IndexedPropertyDefinerCallback(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  // TODO: Figure out under what conditions this gets called.  It's not
  // getting called in our tests.
  NOTIMPLEMENTED();
}


void IndexedPropertySetterCallback(
    uint32_t index,
    v8::Local<v8::Value> value,
    const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  if (index >= impl->length()) {
    return;
  }
  TypeTraits<uint32_t>::ConversionType native_value;
  FromJSValue(isolate, value, kNoConversionFlags,
              &exception_state, &native_value);
  if (exception_state.is_exception_set()) {
    return;
  }

  impl->IndexedSetter(index, native_value);
result_value = v8::Undefined(isolate);
  if (exception_state.is_exception_set()) {
    return;
  }
  info.GetReturnValue().Set(value);
}

void IndexedPropertyDeleterCallback(
    uint32_t index,
    const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  if (index >= impl->length()) {
    return;
  }

  impl->IndexedDeleter(index);
result_value = v8::Undefined(isolate);
  if (exception_state.is_exception_set()) {
    return;
  }
  info.GetReturnValue().Set(v8::Boolean::New(isolate, true));
}


void DummyConstructor(const v8::FunctionCallbackInfo<v8::Value>& info) {
  V8cExceptionState exception(info.GetIsolate());
  exception.SetSimpleException(
      script::kTypeError, "IndexedGetterInterface is not constructible.");
}



void lengthAttributeGetter(
    const v8::FunctionCallbackInfo<v8::Value>& info) {
  script::v8c::shared_bindings::AttributeGetterImpl<IndexedGetterInterface,
                                                    V8cIndexedGetterInterface>(
                    info,
                    false,
                    false,
                    [](v8::Isolate* isolate, IndexedGetterInterface* impl,
                       cobalt::script::ExceptionState& exception_state,
                       v8::Local<v8::Value>& result_value) {
  
      ToJSValue(isolate,
              impl->length(),
              &result_value);

  });
}




void indexedDeleterMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  if (!script::v8c::shared_bindings::object_implements_interface(V8cIndexedGetterInterface::GetTemplate(isolate), isolate, object)) {
    return;
  }
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  const size_t kMinArguments = 1;
  if (info.Length() < kMinArguments) {
    exception_state.SetSimpleException(script::kInvalidNumberOfArguments);
    return;
  }
  // Non-optional arguments
  TypeTraits<uint32_t >::ConversionType index;
  DCHECK_LT(0, info.Length());
  v8::Local<v8::Value> non_optional_value0 = info[0];
  FromJSValue(isolate,
              non_optional_value0,
              kNoConversionFlags,
              &exception_state, &index);
  if (exception_state.is_exception_set()) {
    return;
  }

  impl->IndexedDeleter(index);
result_value = v8::Undefined(isolate);

}



void indexedGetterMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  if (!script::v8c::shared_bindings::object_implements_interface(V8cIndexedGetterInterface::GetTemplate(isolate), isolate, object)) {
    return;
  }
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  const size_t kMinArguments = 1;
  if (info.Length() < kMinArguments) {
    exception_state.SetSimpleException(script::kInvalidNumberOfArguments);
    return;
  }
  // Non-optional arguments
  TypeTraits<uint32_t >::ConversionType index;
  DCHECK_LT(0, info.Length());
  v8::Local<v8::Value> non_optional_value0 = info[0];
  FromJSValue(isolate,
              non_optional_value0,
              kNoConversionFlags,
              &exception_state, &index);
  if (exception_state.is_exception_set()) {
    return;
  }

  if (!exception_state.is_exception_set()) {
    ToJSValue(isolate,
              impl->IndexedGetter(index),
              &result_value);
}
  if (!exception_state.is_exception_set()) {
    info.GetReturnValue().Set(result_value);
  }

}



void indexedSetterMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> object = info.Holder();
  if (!script::v8c::shared_bindings::object_implements_interface(V8cIndexedGetterInterface::GetTemplate(isolate), isolate, object)) {
    return;
  }
  V8cExceptionState exception_state{isolate};
  v8::Local<v8::Value> result_value;

  IndexedGetterInterface* impl =
          script::v8c::shared_bindings::get_impl_from_object<
             IndexedGetterInterface>(object);
  if (!impl) {
    return;
  }
  const size_t kMinArguments = 2;
  if (info.Length() < kMinArguments) {
    exception_state.SetSimpleException(script::kInvalidNumberOfArguments);
    return;
  }
  // Non-optional arguments
  TypeTraits<uint32_t >::ConversionType index;
  TypeTraits<uint32_t >::ConversionType value;
  DCHECK_LT(0, info.Length());
  v8::Local<v8::Value> non_optional_value0 = info[0];
  FromJSValue(isolate,
              non_optional_value0,
              kNoConversionFlags,
              &exception_state, &index);
  if (exception_state.is_exception_set()) {
    return;
  }
  DCHECK_LT(1, info.Length());
  v8::Local<v8::Value> non_optional_value1 = info[1];
  FromJSValue(isolate,
              non_optional_value1,
              kNoConversionFlags,
              &exception_state, &value);
  if (exception_state.is_exception_set()) {
    return;
  }

  impl->IndexedSetter(index, value);
result_value = v8::Undefined(isolate);

}



void InitializeTemplate(v8::Isolate* isolate) {
  // https://heycam.github.io/webidl/#interface-object
  // 3.6.1. Interface object
  //
  // The interface object for a given interface is a built-in function object.
  // It has properties that correspond to the constants and static operations
  // defined on that interface, as described in sections 3.6.6 Constants and
  // 3.6.8 Operations.
  //
  // If the interface is declared with a [Constructor] extended attribute,
  // then the interface object can be called as a constructor to create an
  // object that implements that interface. Calling that interface as a
  // function will throw an exception.
  //
  // Interface objects whose interfaces are not declared with a [Constructor]
  // extended attribute will throw when called, both as a function and as a
  // constructor.
  //
  // An interface object for a non-callback interface has an associated object
  // called the interface prototype object. This object has properties that
  // correspond to the regular attributes and regular operations defined on
  // the interface, and is described in more detail in 3.6.3 Interface
  // prototype object.
  v8::Local<v8::FunctionTemplate> function_template =
      v8::FunctionTemplate::New(
          isolate,
          DummyConstructor,
          v8::Local<v8::Value>(),
          v8::Local<v8::Signature>(),
          0);
  function_template->SetClassName(NewInternalString(isolate, "IndexedGetterInterface"));
  function_template->ReadOnlyPrototype();

  v8::Local<v8::ObjectTemplate> prototype_template = function_template->PrototypeTemplate();
  v8::Local<v8::ObjectTemplate> instance_template = function_template->InstanceTemplate();
  instance_template->SetInternalFieldCount(WrapperPrivate::kInternalFieldCount);

  V8cGlobalEnvironment* global_environment = V8cGlobalEnvironment::GetFromIsolate(isolate);
  global_environment->AddInterfaceData(kInterfaceUniqueId, function_template);


  // https://heycam.github.io/webidl/#es-constants
  // 3.6.6. Constants
  //
  // For each exposed constant defined on an interface A, there must be a
  // corresponding property. The property has the following characteristics:

  // https://heycam.github.io/webidl/#es-attributes
  // 3.6.7. Attributes
  //
  // For each exposed attribute of the interface there must exist a
  // corresponding property. The characteristics of this property are as
  // follows:
  {

    script::v8c::shared_bindings::set_property_for_nonconstructor_attribute(
                  isolate,
    // The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]:
    // true, [[Configurable]]: configurable }, where: configurable is false if
    // the attribute was declared with the [Unforgeable] extended attribute and
    // true otherwise;
                  true,
                  false,
                  false,
                  false,
                  function_template,
                  instance_template,
                  prototype_template,
                  "length"
                  ,lengthAttributeGetter
                  );

  }

  // https://heycam.github.io/webidl/#es-operations
  // 3.6.8. Operations
  //
  // For each unique identifier of an exposed operation defined on the
  // interface, there must exist a corresponding property, unless the effective
  // overload set for that identifier and operation and with an argument count
  // of 0 has no entries.
  //
  // The characteristics of this property are as follows:
  {
    // The name of the property is the identifier.
    v8::Local<v8::String> name = NewInternalString(
        isolate,
        "indexedDeleter");

    // The property has attributes { [[Writable]]: B, [[Enumerable]]: true,
    // [[Configurable]]: B }, where B is false if the operation is unforgeable
    // on the interface, and true otherwise.
    bool B = true;
    v8::PropertyAttribute attributes = static_cast<v8::PropertyAttribute>(
        B ? v8::None : (v8::ReadOnly | v8::DontDelete));

    v8::Local<v8::FunctionTemplate> method_template =
        v8::FunctionTemplate::New(isolate, indexedDeleterMethod);
    method_template->RemovePrototype();
    method_template->SetLength(1);

    // The location of the property is determined as follows:
    // Otherwise, the property exists solely on the interface's interface
    // prototype object.
    prototype_template->
        Set(name, method_template);

    // The value of the property is the result of creating an operation function
    // given the operation, the interface, and the relevant Realm of the object
    // that is the location of the property.

    // Note: that is, even if an includes statement was used to make an
    // operation available on the interface, we pass in the interface which
    // includes the interface mixin, and not the interface mixin on which the
    // operation was originally declared.
  }
  {
    // The name of the property is the identifier.
    v8::Local<v8::String> name = NewInternalString(
        isolate,
        "indexedGetter");

    // The property has attributes { [[Writable]]: B, [[Enumerable]]: true,
    // [[Configurable]]: B }, where B is false if the operation is unforgeable
    // on the interface, and true otherwise.
    bool B = true;
    v8::PropertyAttribute attributes = static_cast<v8::PropertyAttribute>(
        B ? v8::None : (v8::ReadOnly | v8::DontDelete));

    v8::Local<v8::FunctionTemplate> method_template =
        v8::FunctionTemplate::New(isolate, indexedGetterMethod);
    method_template->RemovePrototype();
    method_template->SetLength(1);

    // The location of the property is determined as follows:
    // Otherwise, the property exists solely on the interface's interface
    // prototype object.
    prototype_template->
        Set(name, method_template);

    // The value of the property is the result of creating an operation function
    // given the operation, the interface, and the relevant Realm of the object
    // that is the location of the property.

    // Note: that is, even if an includes statement was used to make an
    // operation available on the interface, we pass in the interface which
    // includes the interface mixin, and not the interface mixin on which the
    // operation was originally declared.
  }
  {
    // The name of the property is the identifier.
    v8::Local<v8::String> name = NewInternalString(
        isolate,
        "indexedSetter");

    // The property has attributes { [[Writable]]: B, [[Enumerable]]: true,
    // [[Configurable]]: B }, where B is false if the operation is unforgeable
    // on the interface, and true otherwise.
    bool B = true;
    v8::PropertyAttribute attributes = static_cast<v8::PropertyAttribute>(
        B ? v8::None : (v8::ReadOnly | v8::DontDelete));

    v8::Local<v8::FunctionTemplate> method_template =
        v8::FunctionTemplate::New(isolate, indexedSetterMethod);
    method_template->RemovePrototype();
    method_template->SetLength(2);

    // The location of the property is determined as follows:
    // Otherwise, the property exists solely on the interface's interface
    // prototype object.
    prototype_template->
        Set(name, method_template);

    // The value of the property is the result of creating an operation function
    // given the operation, the interface, and the relevant Realm of the object
    // that is the location of the property.

    // Note: that is, even if an includes statement was used to make an
    // operation available on the interface, we pass in the interface which
    // includes the interface mixin, and not the interface mixin on which the
    // operation was originally declared.
  }

  // https://heycam.github.io/webidl/#es-stringifier
  // 3.6.8.2. Stringifiers
  prototype_template->Set(
      v8::Symbol::GetToStringTag(isolate),
      NewInternalString(isolate, "IndexedGetterInterface"),
      static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum));




  {
    v8::IndexedPropertyHandlerConfiguration indexed_property_handler_configuration = {
      IndexedPropertyGetterCallback,
      IndexedPropertySetterCallback,
      IndexedPropertyDescriptorCallback,
      IndexedPropertyDeleterCallback,
      IndexedPropertyEnumeratorCallback,
      IndexedPropertyDefinerCallback
    };
    instance_template->SetHandler(indexed_property_handler_configuration);
  }

}

}  // namespace


v8::Local<v8::Object> V8cIndexedGetterInterface::CreateWrapper(
    v8::Isolate* isolate, const scoped_refptr<Wrappable>& wrappable) {
  EscapableEntryScope entry_scope(isolate);
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  V8cGlobalEnvironment* global_environment = V8cGlobalEnvironment::GetFromIsolate(isolate);
  if (!global_environment->HasInterfaceData(kInterfaceUniqueId)) {
    InitializeTemplate(isolate);
  }
  v8::Local<v8::FunctionTemplate> function_template = global_environment->GetInterfaceData(kInterfaceUniqueId);

  DCHECK(function_template->InstanceTemplate()->InternalFieldCount() == WrapperPrivate::kInternalFieldCount);
  v8::Local<v8::Object> object = function_template->InstanceTemplate()->NewInstance(context).ToLocalChecked();
  DCHECK(object->InternalFieldCount() == WrapperPrivate::kInternalFieldCount);

  // This |WrapperPrivate|'s lifetime will be managed by V8.
  new WrapperPrivate(isolate, wrappable, object);
  return entry_scope.Escape(object);
}


v8::Local<v8::FunctionTemplate> V8cIndexedGetterInterface::GetTemplate(v8::Isolate* isolate) {
  V8cGlobalEnvironment* global_environment = V8cGlobalEnvironment::GetFromIsolate(isolate);
  if (!global_environment->HasInterfaceData(kInterfaceUniqueId)) {
    InitializeTemplate(isolate);
  }
  return global_environment->GetInterfaceData(kInterfaceUniqueId);
}


}  // namespace testing
}  // namespace bindings
}  // namespace cobalt


