// Copyright 2019 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/objects/js-regexp.h"

#include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/regexp/regexp.h"

namespace v8 {
namespace internal {
MaybeHandle<JSArray> JSRegExpResult::GetAndCacheIndices(
    Isolate* isolate, Handle<JSRegExpResult> regexp_result) {
  // Check for cached indices. We do a slow lookup and set of
  // the cached_indices_or_match_info and names fields just in
  // case they have been migrated to dictionaries.
  Handle<Object> indices_or_regexp(
      GetProperty(
          isolate, regexp_result,
          isolate->factory()->regexp_result_cached_indices_or_regexp_symbol())
          .ToHandleChecked());
  if (indices_or_regexp->IsJSRegExp()) {
    // Build and cache indices for next lookup.
    // TODO(joshualitt): Instead of caching the indices, we could call
    // ReconfigureToDataProperty on 'indices' setting its value to this
    // newly created array. However, care would have to be taken to ensure
    // a new map is not created each time.

    // Grab regexp, its last_index, and the original subject string from the
    // result and the re-execute the regexp to generate a new MatchInfo.
    Handle<JSRegExp> regexp(JSRegExp::cast(*indices_or_regexp), isolate);
    Handle<Object> input_object(
        GetProperty(isolate, regexp_result,
                    isolate->factory()->regexp_result_regexp_input_symbol())
            .ToHandleChecked());
    Handle<String> subject(String::cast(*input_object), isolate);
    Handle<Object> last_index_object(
        GetProperty(
            isolate, regexp_result,
            isolate->factory()->regexp_result_regexp_last_index_symbol())
            .ToHandleChecked());

    int capture_count = regexp->CaptureCount();
    Handle<RegExpMatchInfo> match_info =
        RegExpMatchInfo::New(isolate, capture_count);

    int last_index = Smi::ToInt(*last_index_object);
    Handle<Object> result;
    ASSIGN_RETURN_ON_EXCEPTION(
        isolate, result,
        RegExp::Exec(isolate, regexp, subject, last_index, match_info),
        JSArray);
    DCHECK_EQ(*result, *match_info);

    Handle<Object> maybe_names(
        GetProperty(isolate, regexp_result,
                    isolate->factory()->regexp_result_names_symbol())
            .ToHandleChecked());
    indices_or_regexp =
        JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);

    // Cache the result and clear the names array, last_index and subject.
    SetProperty(
        isolate, regexp_result,
        isolate->factory()->regexp_result_cached_indices_or_regexp_symbol(),
        indices_or_regexp)
        .ToHandleChecked();
    SetProperty(isolate, regexp_result,
                isolate->factory()->regexp_result_names_symbol(),
                isolate->factory()->undefined_value())
        .ToHandleChecked();
    SetProperty(isolate, regexp_result,
                isolate->factory()->regexp_result_regexp_last_index_symbol(),
                isolate->factory()->undefined_value())
        .ToHandleChecked();
    SetProperty(isolate, regexp_result,
                isolate->factory()->regexp_result_regexp_input_symbol(),
                isolate->factory()->undefined_value())
        .ToHandleChecked();
  }
  return Handle<JSArray>::cast(indices_or_regexp);
}

Handle<JSRegExpResultIndices> JSRegExpResultIndices::BuildIndices(
    Isolate* isolate, Handle<RegExpMatchInfo> match_info,
    Handle<Object> maybe_names) {
  Handle<JSRegExpResultIndices> indices(Handle<JSRegExpResultIndices>::cast(
      isolate->factory()->NewJSObjectFromMap(
          isolate->regexp_result_indices_map())));

  // Initialize indices length to avoid having a partially initialized object
  // should GC be triggered by creating a NewFixedArray.
  indices->set_length(Smi::zero());

  // Build indices array from RegExpMatchInfo.
  int num_indices = match_info->NumberOfCaptureRegisters();
  int num_results = num_indices >> 1;
  Handle<FixedArray> indices_array =
      isolate->factory()->NewFixedArray(num_results);
  JSArray::SetContent(indices, indices_array);

  for (int i = 0; i < num_results; i++) {
    int base_offset = i * 2;
    int start_offset = match_info->Capture(base_offset);
    int end_offset = match_info->Capture(base_offset + 1);

    // Any unmatched captures are set to undefined, otherwise we set them to a
    // subarray of the indices.
    if (start_offset == -1) {
      indices_array->set(i, ReadOnlyRoots(isolate).undefined_value());
    } else {
      Handle<FixedArray> indices_sub_array(
          isolate->factory()->NewFixedArray(2));
      indices_sub_array->set(0, Smi::FromInt(start_offset));
      indices_sub_array->set(1, Smi::FromInt(end_offset));
      Handle<JSArray> indices_sub_jsarray =
          isolate->factory()->NewJSArrayWithElements(indices_sub_array,
                                                     PACKED_SMI_ELEMENTS, 2);
      indices_array->set(i, *indices_sub_jsarray);
    }
  }

  // If there are no capture groups, set the groups property to undefined.
  FieldIndex groups_index = FieldIndex::ForDescriptor(
      indices->map(), InternalIndex(kGroupsDescriptorIndex));
  if (maybe_names->IsUndefined(isolate)) {
    indices->RawFastPropertyAtPut(groups_index,
                                  ReadOnlyRoots(isolate).undefined_value());
    return indices;
  }

  // Create a groups property which returns a dictionary of named captures to
  // their corresponding capture indices.
  Handle<FixedArray> names(Handle<FixedArray>::cast(maybe_names));
  int num_names = names->length() >> 1;
  Handle<NameDictionary> group_names = NameDictionary::New(isolate, num_names);
  for (int i = 0; i < num_names; i++) {
    int base_offset = i * 2;
    int name_offset = base_offset;
    int index_offset = base_offset + 1;
    Handle<String> name(String::cast(names->get(name_offset)), isolate);
    Handle<Smi> smi_index(Smi::cast(names->get(index_offset)), isolate);
    Handle<Object> capture_indices(indices_array->get(smi_index->value()),
                                   isolate);
    if (!capture_indices->IsUndefined(isolate)) {
      capture_indices = Handle<JSArray>::cast(capture_indices);
    }
    group_names = NameDictionary::Add(
        isolate, group_names, name, capture_indices, PropertyDetails::Empty());
  }

  // Convert group_names to a JSObject and store at the groups property of the
  // result indices.
  Handle<FixedArrayBase> elements = isolate->factory()->empty_fixed_array();
  Handle<HeapObject> null =
      Handle<HeapObject>::cast(isolate->factory()->null_value());
  Handle<JSObject> js_group_names =
      isolate->factory()->NewSlowJSObjectWithPropertiesAndElements(
          null, group_names, elements);
  indices->RawFastPropertyAtPut(groups_index, *js_group_names);
  return indices;
}

uint32_t JSRegExp::BacktrackLimit() const {
  CHECK_EQ(TypeTag(), IRREGEXP);
  return static_cast<uint32_t>(Smi::ToInt(DataAt(kIrregexpBacktrackLimit)));
}

}  // namespace internal
}  // namespace v8
