// 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_image_generator_impl.h"

#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/document_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
#include "third_party/blink/renderer/platform/graphics/image.h"

namespace blink {

namespace {

// This matches PaintWorklet's generator-ready accounting:
// registered_definitions_count_ starts at 1 for the first
// RegisterCSSPaintDefinition() call that creates the document definition,
// then increments for each additional matching worklet-scope registration.
// Off-thread paint requires one additional
// RegisterMainThreadDocumentPaintDefinition() registration/finalization step,
// so readiness for painting is kNumGlobalScopesPerThread + 1 in that mode.
unsigned RequiredDefinitionCountForPaint(const PaintWorklet& paint_worklet) {
  return paint_worklet.IsOffMainThread()
             ? PaintWorklet::kNumGlobalScopesPerThread + 1
             : PaintWorklet::kNumGlobalScopesPerThread;
}

}  // namespace

CSSPaintImageGenerator* CSSPaintImageGeneratorImpl::Create(
    const String& name,
    const Document& document,
    Observer* observer) {
  PaintWorklet* paint_worklet = PaintWorklet::From(*document.domWindow());

  DCHECK(paint_worklet);
  CSSPaintImageGeneratorImpl* generator;
  if (paint_worklet->GetDocumentDefinitionMap().Contains(name)) {
    generator =
        MakeGarbageCollected<CSSPaintImageGeneratorImpl>(paint_worklet, name);
  } else {
    generator = MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
        observer, paint_worklet, name);
    paint_worklet->AddPendingGenerator(name, generator);
  }

  return generator;
}

CSSPaintImageGeneratorImpl::CSSPaintImageGeneratorImpl(
    PaintWorklet* paint_worklet,
    const String& name)
    : CSSPaintImageGeneratorImpl(nullptr, paint_worklet, name) {}

CSSPaintImageGeneratorImpl::CSSPaintImageGeneratorImpl(
    Observer* observer,
    PaintWorklet* paint_worklet,
    const String& name)
    : observer_(observer), paint_worklet_(paint_worklet), name_(name) {}

CSSPaintImageGeneratorImpl::~CSSPaintImageGeneratorImpl() = default;

void CSSPaintImageGeneratorImpl::NotifyGeneratorReady() {
  DCHECK(observer_);
  observer_->PaintImageGeneratorReady();
}

scoped_refptr<Image> CSSPaintImageGeneratorImpl::Paint(
    const ImageResourceObserver& observer,
    const gfx::SizeF& container_size,
    const GCedCSSStyleValueVector* data) {
  return paint_worklet_->Paint(name_, observer, container_size, data);
}

bool CSSPaintImageGeneratorImpl::HasDocumentDefinition() const {
  return paint_worklet_->GetDocumentDefinitionMap().Contains(name_);
}

bool CSSPaintImageGeneratorImpl::GetValidDocumentDefinition(
    DocumentPaintDefinition*& definition) const {
  if (!HasDocumentDefinition())
    return false;
  definition = paint_worklet_->GetDocumentDefinitionMap().at(name_);
  if (!definition) {
    return false;
  }
  // In off-thread CSS Paint, we register CSSPaintDefinition on the worklet
  // thread first. Once the same CSSPaintDefinition is successfully registered
  // to all the paint worklet global scopes, we then post to the main thread and
  // register that CSSPaintDefinition on the main thread. So for the off-thread
  // case, as long as the DocumentPaintDefinition exists in the map, it should
  // be valid.
  if (paint_worklet_->IsOffMainThread()) {
    DCHECK(definition);
    return true;
  }
  if (definition && definition->GetRegisteredDefinitionCount() !=
                        PaintWorklet::kNumGlobalScopesPerThread) {
    definition = nullptr;
  }
  return definition;
}

unsigned CSSPaintImageGeneratorImpl::GetRegisteredDefinitionCountForTesting()
    const {
  if (!HasDocumentDefinition())
    return 0;
  DocumentPaintDefinition* definition =
      paint_worklet_->GetDocumentDefinitionMap().at(name_);
  return definition->GetRegisteredDefinitionCount();
}

const Vector<CSSPropertyID>&
CSSPaintImageGeneratorImpl::NativeInvalidationProperties() const {
  DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, empty_vector, ());
  DocumentPaintDefinition* definition;
  if (!GetValidDocumentDefinition(definition))
    return empty_vector;
  return definition->NativeInvalidationProperties();
}

const Vector<AtomicString>&
CSSPaintImageGeneratorImpl::CustomInvalidationProperties() const {
  DEFINE_STATIC_LOCAL(Vector<AtomicString>, empty_vector, ());
  DocumentPaintDefinition* definition;
  if (!GetValidDocumentDefinition(definition))
    return empty_vector;
  return definition->CustomInvalidationProperties();
}

bool CSSPaintImageGeneratorImpl::HasAlpha() const {
  DocumentPaintDefinition* definition;
  if (!GetValidDocumentDefinition(definition))
    return false;
  return definition->alpha();
}

const Vector<CSSSyntaxDefinition>&
CSSPaintImageGeneratorImpl::InputArgumentTypes() const {
  DEFINE_STATIC_LOCAL(Vector<CSSSyntaxDefinition>, empty_vector, ());
  DocumentPaintDefinition* definition;
  if (!GetValidDocumentDefinition(definition))
    return empty_vector;
  return definition->InputArgumentTypes();
}

bool CSSPaintImageGeneratorImpl::IsImageGeneratorReady() const {
  if (!HasDocumentDefinition()) {
    return false;
  }
  DocumentPaintDefinition* definition =
      paint_worklet_->GetDocumentDefinitionMap().at(name_);
  return definition && definition->GetRegisteredDefinitionCount() ==
                           RequiredDefinitionCountForPaint(*paint_worklet_);
}

int CSSPaintImageGeneratorImpl::WorkletId() const {
  return paint_worklet_->WorkletId();
}

void CSSPaintImageGeneratorImpl::Trace(Visitor* visitor) const {
  visitor->Trace(observer_);
  visitor->Trace(paint_worklet_);
  CSSPaintImageGenerator::Trace(visitor);
}

}  // namespace blink
