// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"

#include <memory>

#include "base/check_op.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_paint_callback.h"
#include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
#include "third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h"
#include "third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h"
#include "third_party/blink/renderer/core/css/cssom/css_paint_worklet_input.h"
#include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h"
#include "third_party/blink/renderer/modules/csspaint/paint_size.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/graphics/paint_generated_image.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"

namespace blink {

namespace {

gfx::SizeF GetSpecifiedSize(const gfx::SizeF& size, float zoom) {
  float un_zoom_factor = 1 / zoom;
  auto un_zoom_fn = [un_zoom_factor](float a) -> float {
    return a * un_zoom_factor;
  };
  return gfx::SizeF(un_zoom_fn(size.width()), un_zoom_fn(size.height()));
}

std::unique_ptr<CrossThreadStyleValue> CreateUpdatedStyleValue(
    const PaintWorkletInput::PropertyValue& value,
    const CrossThreadStyleValue& old_style_value) {
  switch (old_style_value.GetType()) {
    case CrossThreadStyleValue::StyleValueType::kUnitType:
      DCHECK(value.float_value);
      return std::make_unique<CrossThreadUnitValue>(
          value.float_value.value(),
          To<CrossThreadUnitValue>(old_style_value).GetUnitType());
    case CrossThreadStyleValue::StyleValueType::kColorType:
      DCHECK(value.color_value);
      return std::make_unique<CrossThreadColorValue>(
          Color::FromSkColor4f(value.color_value.value()));
    case CrossThreadStyleValue::StyleValueType::kUnknownType:
    case CrossThreadStyleValue::StyleValueType::kUnparsedType:
    case CrossThreadStyleValue::StyleValueType::kKeywordType:
      NOTREACHED();
  }
}

}  // namespace

CSSPaintDefinition::CSSPaintDefinition(
    ScriptState* script_state,
    V8NoArgumentConstructor* constructor,
    V8PaintCallback* paint,
    const Vector<CSSPropertyID>& native_invalidation_properties,
    const Vector<AtomicString>& custom_invalidation_properties,
    const Vector<CSSSyntaxDefinition>& input_argument_types,
    const PaintRenderingContext2DSettings* context_settings,
    PaintWorkletGlobalScope* global_scope)
    : script_state_(script_state),
      constructor_(constructor),
      paint_(paint),
      did_call_constructor_(false),
      context_settings_(context_settings),
      global_scope_(global_scope) {
  native_invalidation_properties_ = native_invalidation_properties;
  custom_invalidation_properties_ = custom_invalidation_properties;
  input_argument_types_ = input_argument_types;
}

CSSPaintDefinition::~CSSPaintDefinition() = default;

// PaintDefinition override
PaintRecord CSSPaintDefinition::Paint(
    const CompositorPaintWorkletInput* compositor_input,
    const CompositorPaintWorkletJob::AnimatedPropertyValues&
        animated_property_values) {
  const CSSPaintWorkletInput* input =
      To<CSSPaintWorkletInput>(compositor_input);
  PaintWorkletStylePropertyMap* style_map =
      MakeGarbageCollected<PaintWorkletStylePropertyMap>(input->StyleMapData());
  GCedCSSStyleValueVector* paint_arguments =
      MakeGarbageCollected<GCedCSSStyleValueVector>();
  for (const auto& style_value : input->ParsedInputArguments()) {
    paint_arguments->push_back(style_value->ToCSSStyleValue());
  }

  ApplyAnimatedPropertyOverrides(style_map, animated_property_values);

  return Paint(input->GetSize(), input->EffectiveZoom(), style_map,
               paint_arguments);
}

PaintRecord CSSPaintDefinition::Paint(
    const gfx::SizeF& container_size,
    float zoom,
    StylePropertyMapReadOnly* style_map,
    const GCedCSSStyleValueVector* paint_arguments) {
  const gfx::SizeF specified_size = GetSpecifiedSize(container_size, zoom);
  ScriptState::Scope scope(script_state_);

  MaybeCreatePaintInstance();
  // We may have failed to create an instance, in which case produce an
  // invalid image.
  if (instance_.IsEmpty())
    return PaintRecord();

  v8::Isolate* isolate = script_state_->GetIsolate();

  // Do subpixel snapping for the |container_size|.
  auto* rendering_context = MakeGarbageCollected<PaintRenderingContext2D>(
      ToRoundedSize(container_size), context_settings_, zoom, global_scope_);
  PaintSize* paint_size = MakeGarbageCollected<PaintSize>(specified_size);

  if (!paint_arguments) {
    DEFINE_THREAD_SAFE_STATIC_LOCAL(
        ThreadSpecific<Persistent<GCedCSSStyleValueVector>>,
        static_empty_arguments, {});
    Persistent<GCedCSSStyleValueVector>& empty_arguments =
        *static_empty_arguments;
    if (!empty_arguments) [[unlikely]] {
      empty_arguments = MakeGarbageCollected<GCedCSSStyleValueVector>();
      LEAK_SANITIZER_IGNORE_OBJECT(&empty_arguments);
    }
    paint_arguments = empty_arguments.Get();
  }

  v8::TryCatch try_catch(isolate);
  try_catch.SetVerbose(true);

  // The paint function may have produced an error, in which case produce an
  // invalid image.
  if (paint_
          ->Invoke(instance_.Get(isolate), rendering_context, paint_size,
                   style_map, CSSStyleValueVector(std::move(*paint_arguments)))
          .IsNothing()) {
    return PaintRecord();
  }

  return rendering_context->GetRecord();
}

void CSSPaintDefinition::ApplyAnimatedPropertyOverrides(
    PaintWorkletStylePropertyMap* style_map,
    const CompositorPaintWorkletJob::AnimatedPropertyValues&
        animated_property_values) {
  auto& style_map_data = style_map->StyleMapData();
  for (const auto& [key, value] : animated_property_values) {
    DCHECK(value.has_value());
    String property_name = String::FromUtf8(key.custom_property_name.value());
    auto it = style_map_data.find(property_name);
    CHECK_NE(it, style_map_data.end());
    DCHECK(it->value);
    it->value = CreateUpdatedStyleValue(value, *it->value);
  }
}

void CSSPaintDefinition::MaybeCreatePaintInstance() {
  if (did_call_constructor_)
    return;
  did_call_constructor_ = true;

  DCHECK(instance_.IsEmpty());

  ScriptValue paint_instance;
  if (!constructor_->Construct().To(&paint_instance))
    return;

  instance_.Reset(constructor_->GetIsolate(), paint_instance.V8Value());
}

void CSSPaintDefinition::Trace(Visitor* visitor) const {
  visitor->Trace(constructor_);
  visitor->Trace(paint_);
  visitor->Trace(instance_);
  visitor->Trace(context_settings_);
  visitor->Trace(script_state_);
  visitor->Trace(global_scope_);
  PaintDefinition::Trace(visitor);
}

}  // namespace blink
