// Copyright 2022 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/mediastream/media_stream_set.h"

#include "base/functional/bind.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream.h"
#include "third_party/blink/renderer/modules/mediastream/screen_capture_media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/user_media_request.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/modules/screen_details/screen_detailed.h"
#include "third_party/blink/renderer/modules/screen_details/screen_details.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include "ui/display/types/display_constants.h"

namespace blink {

namespace {

ScreenDetailed* FindScreenDetailedByDisplayId(
    ScreenDetails* screen_details,
    std::optional<int64_t> display_id) {
  if (display_id == display::kInvalidDisplayId) {
    return nullptr;
  }

  auto screen_iterator =
      std::ranges::find_if(screen_details->screens(),
                           [display_id](const ScreenDetailed* screen_detailed) {
                             return *display_id == screen_detailed->DisplayId();
                           });

  return (screen_iterator != screen_details->screens().end()) ? *screen_iterator
                                                              : nullptr;
}

}  // namespace

MediaStreamSet* MediaStreamSet::Create(
    ExecutionContext* context,
    const GCedMediaStreamDescriptorVector& stream_descriptors,
    UserMediaRequestType request_type,
    MediaStreamSetInitializedCallback callback) {
  DCHECK(IsMainThread());

  return MakeGarbageCollected<MediaStreamSet>(
      context, stream_descriptors, request_type, std::move(callback));
}

MediaStreamSet::MediaStreamSet(
    ExecutionContext* context,
    const GCedMediaStreamDescriptorVector& stream_descriptors,
    UserMediaRequestType request_type,
    MediaStreamSetInitializedCallback callback)
    : ExecutionContextClient(context),
      media_streams_to_initialize_count_(stream_descriptors.size()),
      media_streams_initialized_callback_(std::move(callback)) {
  DCHECK(IsMainThread());

  if (request_type == UserMediaRequestType::kAllScreensMedia) {
    InitializeGetAllScreensMediaStreams(context, stream_descriptors);
    return;
  }

  if (stream_descriptors.empty()) {
    // No streams -> all streams are initialized, meaning the set
    // itself is fully initialized.
    context->GetTaskRunner(TaskType::kInternalMedia)
        ->PostTask(FROM_HERE,
                   BindOnce(&MediaStreamSet::OnMediaStreamSetInitialized,
                            WrapPersistent(this)));
    return;
  }

  // The set will be initialized when all of its streams are initialized.
  // When the last stream is initialized, its callback will trigger
  // a call to OnMediaStreamSetInitialized.
  for (wtf_size_t stream_index = 0; stream_index < stream_descriptors.size();
       ++stream_index) {
    MediaStream::Create(context, stream_descriptors[stream_index],
                        /*track=*/nullptr,
                        BindOnce(&MediaStreamSet::OnMediaStreamInitialized,
                                 WrapPersistent(this)));
  }
}

void MediaStreamSet::Trace(Visitor* visitor) const {
  visitor->Trace(initialized_media_streams_);
  ExecutionContextClient::Trace(visitor);
}

void MediaStreamSet::InitializeGetAllScreensMediaStreams(
    ExecutionContext* context,
    const GCedMediaStreamDescriptorVector& stream_descriptors) {
  DCHECK(IsMainThread());

  LocalDOMWindow* const window = To<LocalDOMWindow>(context);
  DCHECK(window);

  // TODO(crbug.com/1358949): Move the generation of the |ScreenDetails| object
  // next to the generation of the descriptors and store them as members to
  // avoid race conditions. Further, match the getAllScreensMedia API and the
  // window placement API by unique IDs instead of assuming the same order.
  ScreenDetails* const screen_details =
      MakeGarbageCollected<ScreenDetails>(window);
  const bool screen_details_match_descriptors =
      screen_details->screens().size() == stream_descriptors.size();
  for (wtf_size_t stream_index = 0; stream_index < stream_descriptors.size();
       ++stream_index) {
    MediaStreamDescriptor* const descriptor = stream_descriptors[stream_index];
    DCHECK_EQ(1u, descriptor->NumberOfVideoComponents());

    ScreenDetailed* screen = FindScreenDetailedByDisplayId(
        screen_details,
        descriptor->VideoComponent(0u)->Source()->GetDisplayId());

    MediaStreamTrack* video_track =
        MakeGarbageCollected<ScreenCaptureMediaStreamTrack>(
            context, descriptor->VideoComponent(0u),
            screen_details_match_descriptors ? screen_details : nullptr,
            screen);
    initialized_media_streams_.push_back(
        MediaStream::Create(context, descriptor, {}, {video_track}));
  }
  context->GetTaskRunner(TaskType::kInternalMedia)
      ->PostTask(FROM_HERE,
                 BindOnce(&MediaStreamSet::OnMediaStreamSetInitialized,
                          WrapPersistent(this)));
}

void MediaStreamSet::OnMediaStreamSetInitialized() {
  DCHECK(IsMainThread());

  std::move(std::move(media_streams_initialized_callback_))
      .Run(initialized_media_streams_);
}

// TODO(crbug.com/1300883): Clean up other streams if one stream capture
// results in an error. This is only required for getAllScreensMedia.
// Currently existing functionality generates only one stream which is not
// affected by this change.
void MediaStreamSet::OnMediaStreamInitialized(
    MediaStream* initialized_media_stream) {
  DCHECK(IsMainThread());
  DCHECK_LT(initialized_media_streams_.size(),
            media_streams_to_initialize_count_);

  initialized_media_streams_.push_back(initialized_media_stream);
  if (initialized_media_streams_.size() == media_streams_to_initialize_count_) {
    OnMediaStreamSetInitialized();
  }
}

}  // namespace blink
