// 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 "cc/tiles/image_controller.h"

#include <utility>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/trace_event/trace_event.h"
#include "cc/base/completion_event.h"
#include "cc/tiles/tile_task_manager.h"

namespace cc {

ImageController::ImageDecodeRequestId
    ImageController::s_next_image_decode_queue_id_ = 1;

ImageController::ImageController(
    scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
    scoped_refptr<base::SequencedTaskRunner> worker_task_runner)
    : worker_task_runner_(std::move(worker_task_runner)) {
  worker_state_ = std::make_unique<WorkerState>(std::move(origin_task_runner),
                                                weak_ptr_factory_.GetWeakPtr());
}

ImageController::~ImageController() {
  StopWorkerTasks();
  for (auto& request : orphaned_decode_requests_)
    std::move(request.callback).Run(request.id, ImageDecodeResult::FAILURE);
  if (worker_task_runner_) {
    // Delete `worker_state_` on `worker_task_runner_` (or elsewhere via the
    // callback's destructor if `worker_task_runner_` stopped accepting tasks).
    worker_task_runner_->PostTask(
        FROM_HERE, base::DoNothingWithBoundArgs(std::move(worker_state_)));
  }
}

ImageController::WorkerState::WorkerState(
    scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
    base::WeakPtr<ImageController> weak_ptr)
    : origin_task_runner(std::move(origin_task_runner)), weak_ptr(weak_ptr) {}
ImageController::WorkerState::~WorkerState() = default;

void ImageController::StopWorkerTasks() {
  // We can't have worker threads without a cache_ or a worker_task_runner_, so
  // terminate early.
  if (!cache_ || !worker_task_runner_)
    return;

  base::AutoLock hold(worker_state_->lock);

  // If a worker task is running, post a task and wait for its completion to
  // "flush" the queue.
  if (worker_state_->task_state == WorkerTaskState::kRunningTask) {
    base::AutoUnlock release(worker_state_->lock);
    CompletionEvent completion_event;
    worker_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&CompletionEvent::Signal,
                                  base::Unretained(&completion_event)));
    completion_event.Wait();
  }

  // Now, begin cleanup.

  // Unlock all of the locked images (note that this vector would only be
  // populated if we actually need to unref the image.
  for (auto& image_pair : requested_locked_images_)
    cache_->UnrefImage(image_pair.second);
  requested_locked_images_.clear();

  // Now, complete the tasks that already ran but haven't completed. These would
  // be posted in the run loop, but since we invalidated the weak ptrs, we need
  // to run everything manually.
  for (auto& request_to_complete : worker_state_->requests_needing_completion) {
    ImageDecodeRequest& request = request_to_complete.second;

    // The task (if one exists) would have run already, we just need to make
    // sure it was completed. Multiple requests for the same image use the same
    // task so it could have already been completed.
    if (request.task && !request.task->HasCompleted()) {
      request.task->OnTaskCompleted();
      request.task->DidComplete();
    }

    if (request.need_unref)
      cache_->UnrefImage(request.draw_image);

    // Orphan the request so that we can still run it when a new cache is set.
    request.task = nullptr;
    request.need_unref = false;
    orphaned_decode_requests_.push_back(std::move(request));
  }
  worker_state_->requests_needing_completion.clear();

  // Finally, complete all of the tasks that never started running. This is
  // similar to the |requests_needing_completion_|, but happens at a different
  // stage in the pipeline.
  for (auto& request_pair : worker_state_->image_decode_queue) {
    ImageDecodeRequest& request = request_pair.second;

    if (request.task) {
      // This task may have run via a different request, so only cancel it if
      // it's "new". That is, the same task could have been referenced by
      // several different image deque requests for the same image.
      if (request.task->state().IsNew())
        request.task->state().DidCancel();

      if (!request.task->HasCompleted()) {
        request.task->OnTaskCompleted();
        request.task->DidComplete();
      }
    }

    if (request.need_unref)
      cache_->UnrefImage(request.draw_image);

    // Orphan the request so that we can still run it when a new cache is set.
    request.task = nullptr;
    request.need_unref = false;
    orphaned_decode_requests_.push_back(std::move(request));
  }
  worker_state_->image_decode_queue.clear();
}

void ImageController::SetImageDecodeCache(ImageDecodeCache* cache) {
  DCHECK(!cache_ || !cache);

  if (!cache) {
    SetPredecodeImages(std::vector<DrawImage>(),
                       ImageDecodeCache::TracingInfo());
    StopWorkerTasks();
    image_cache_max_limit_bytes_ = 0u;
    image_cache_client_id_ = 0u;
  }

  cache_ = cache;

  if (cache_) {
    DCHECK_EQ(image_cache_client_id_, 0u);
    image_cache_client_id_ = cache_->GenerateClientId();
    image_cache_max_limit_bytes_ = cache_->GetMaximumMemoryLimitBytes();
    GenerateTasksForOrphanedRequests();
  }
}

void ImageController::ConvertImagesToTasks(
    std::vector<DrawImage>* sync_decoded_images,
    std::vector<scoped_refptr<TileTask>>* tasks,
    bool* has_at_raster_images,
    bool* has_hardware_accelerated_jpeg_candidates,
    bool* has_hardware_accelerated_webp_candidates,
    const ImageDecodeCache::TracingInfo& tracing_info) {
  DCHECK(cache_);
  *has_at_raster_images = false;
  *has_hardware_accelerated_jpeg_candidates = false;
  *has_hardware_accelerated_webp_candidates = false;
  for (auto it = sync_decoded_images->begin();
       it != sync_decoded_images->end();) {
    // PaintWorklet images should not be included in this set; they have already
    // been painted before raster and so do not need raster-time work.
    DCHECK(!it->paint_image().IsPaintWorklet());

    ImageDecodeCache::TaskResult result = cache_->GetTaskForImageAndRef(
        image_cache_client_id_, *it, tracing_info);
    *has_at_raster_images |= result.is_at_raster_decode;

    ImageType image_type =
        it->paint_image().GetImageHeaderMetadata()
            ? it->paint_image().GetImageHeaderMetadata()->image_type
            : ImageType::kInvalid;
    *has_hardware_accelerated_jpeg_candidates |=
        (result.can_do_hardware_accelerated_decode &&
         image_type == ImageType::kJPEG);
    *has_hardware_accelerated_webp_candidates |=
        (result.can_do_hardware_accelerated_decode &&
         image_type == ImageType::kWEBP);

    if (result.task)
      tasks->push_back(std::move(result.task));
    if (result.need_unref)
      ++it;
    else
      it = sync_decoded_images->erase(it);
  }
}

void ImageController::UnrefImages(const std::vector<DrawImage>& images) {
  for (auto& image : images)
    cache_->UnrefImage(image);
}

void ImageController::ReduceMemoryUsage() {
  DCHECK(cache_);
  cache_->ReduceCacheUsage();
}

std::vector<scoped_refptr<TileTask>> ImageController::SetPredecodeImages(
    std::vector<DrawImage> images,
    const ImageDecodeCache::TracingInfo& tracing_info) {
  std::vector<scoped_refptr<TileTask>> new_tasks;
  // The images here are in a pre-decode area: we decode them in advance, but
  // they're not dependencies for raster tasks. If these images do end up
  // getting rasterized, we will still have a chance to record the raster
  // scheduling delay UMAs when we create and run the raster task.
  bool has_at_raster_images = false;
  bool has_hardware_accelerated_jpeg_candidates = false;
  bool has_hardware_accelerated_webp_candidates = false;
  ConvertImagesToTasks(&images, &new_tasks, &has_at_raster_images,
                       &has_hardware_accelerated_jpeg_candidates,
                       &has_hardware_accelerated_webp_candidates, tracing_info);
  UnrefImages(predecode_locked_images_);
  predecode_locked_images_ = std::move(images);
  return new_tasks;
}

ImageController::ImageDecodeRequestId ImageController::QueueImageDecode(
    const DrawImage& draw_image,
    ImageDecodedCallback callback) {
  // We must not receive any image requests if we have no worker.
  CHECK(worker_task_runner_);

  // Generate the next id.
  ImageDecodeRequestId id = s_next_image_decode_queue_id_++;

  DCHECK(draw_image.paint_image());
  bool is_image_lazy = draw_image.paint_image().IsLazyGenerated();

  // Get the tasks for this decode.
  ImageDecodeCache::TaskResult result(
      /*need_unref=*/false,
      /*is_at_raster_decode=*/false,
      /*can_do_hardware_accelerated_decode=*/false);
  if (is_image_lazy)
    result = cache_->GetOutOfRasterDecodeTaskForImageAndRef(
        image_cache_client_id_, draw_image);
  // If we don't need to unref this, we don't actually have a task.
  DCHECK(result.need_unref || !result.task);

  // Schedule the task and signal that there is more work.
  base::AutoLock hold(worker_state_->lock);
  worker_state_->image_decode_queue[id] =
      ImageDecodeRequest(id, draw_image, std::move(callback),
                         std::move(result.task), result.need_unref);
  ScheduleImageDecodeOnWorkerIfNeeded();

  return id;
}

void ImageController::UnlockImageDecode(ImageDecodeRequestId id) {
  // If the image exists, ie we actually need to unlock it, then do so.
  auto it = requested_locked_images_.find(id);
  if (it == requested_locked_images_.end())
    return;

  UnrefImages({std::move(it->second)});
  requested_locked_images_.erase(it);
}

// static
void ImageController::ProcessNextImageDecodeOnWorkerThread(
    WorkerState* worker_state) {
  TRACE_EVENT0("cc", "ImageController::ProcessNextImageDecodeOnWorkerThread");

  base::AutoLock hold(worker_state->lock);
  DCHECK_EQ(worker_state->task_state, WorkerTaskState::kQueuedTask);
  worker_state->task_state = WorkerTaskState::kRunningTask;

  // If we don't have any work, abort.
  if (worker_state->image_decode_queue.empty()) {
    worker_state->task_state = WorkerTaskState::kNoTask;
    return;
  }

  // Take the next request from the queue.
  auto decode_it = worker_state->image_decode_queue.begin();
  DCHECK(decode_it != worker_state->image_decode_queue.end());
  scoped_refptr<TileTask> decode_task = decode_it->second.task;
  ImageDecodeRequestId decode_id = decode_it->second.id;

  // Notify that the task will need completion. Note that there are two cases
  // where we process this. First, we might complete this task as a response to
  // the posted task below. Second, we might complete it in StopWorkerTasks().
  // In either case, the task would have already run (either post task happens
  // after running, or the thread was already joined which means the task ran).
  // This means that we can put the decode into |requests_needing_completion_|
  // here before actually running the task.
  worker_state->requests_needing_completion[decode_id] =
      std::move(decode_it->second);

  worker_state->image_decode_queue.erase(decode_it);

  // Run the task if we need to run it. If the task state isn't new, then there
  // is another task that is responsible for finishing it and cleaning up (and
  // it already ran); we just need to post a completion callback. Note that the
  // other tasks's completion will also run first, since the requests are
  // ordered. So, when we process this task's completion, we won't actually do
  // anything with the task and simply issue the callback.
  if (decode_task && decode_task->state().IsNew()) {
    base::AutoUnlock release(worker_state->lock);
    decode_task->state().DidSchedule();
    decode_task->state().DidStart();
    decode_task->RunOnWorkerThread();
    decode_task->state().DidFinish();
  }

  worker_state->origin_task_runner->PostTask(
      FROM_HERE, base::BindOnce(&ImageController::ImageDecodeCompleted,
                                worker_state->weak_ptr, decode_id));

  DCHECK_EQ(worker_state->task_state, WorkerTaskState::kRunningTask);
  worker_state->task_state = WorkerTaskState::kNoTask;
}

void ImageController::ImageDecodeCompleted(ImageDecodeRequestId id) {
  ImageDecodedCallback callback;
  ImageDecodeResult result = ImageDecodeResult::SUCCESS;
  {
    base::AutoLock hold(worker_state_->lock);

    auto request_it = worker_state_->requests_needing_completion.find(id);
    // The request may have been completed by StopWorkerTasks().
    if (request_it == worker_state_->requests_needing_completion.end())
      return;
    id = request_it->first;
    ImageDecodeRequest& request = request_it->second;

    // First, Determine the status of the decode. This has to happen here, since
    // we conditionally move from the draw image below.
    // Also note that if we don't need an unref for a lazy decoded images, it
    // implies that we never attempted the decode. Some of the reasons for this
    // would be that the image is of an empty size, or if the image doesn't fit
    // into memory. In all cases, this implies that the decode was a failure.
    if (!request.draw_image.paint_image().IsLazyGenerated())
      result = ImageDecodeResult::DECODE_NOT_REQUIRED;
    else if (!request.need_unref)
      result = ImageDecodeResult::FAILURE;
    else
      result = ImageDecodeResult::SUCCESS;

    // If we need to unref this decode, then we have to put it into the locked
    // images vector.
    if (request.need_unref)
      requested_locked_images_[id] = std::move(request.draw_image);

    // If we have a task that isn't completed yet, we need to complete it.
    if (request.task && !request.task->HasCompleted()) {
      request.task->OnTaskCompleted();
      request.task->DidComplete();
    }

    // Finally, save the callback so we can run it without the lock, and erase
    // the request from |requests_needing_completion_|.
    callback = std::move(request.callback);
    worker_state_->requests_needing_completion.erase(request_it);

    ScheduleImageDecodeOnWorkerIfNeeded();
  }

  // Finally run the requested callback.
  std::move(callback).Run(id, result);
}

void ImageController::GenerateTasksForOrphanedRequests() {
  base::AutoLock hold(worker_state_->lock);
  DCHECK_EQ(0u, worker_state_->image_decode_queue.size());
  DCHECK_EQ(0u, worker_state_->requests_needing_completion.size());
  DCHECK(cache_);

  for (auto& request : orphaned_decode_requests_) {
    DCHECK(!request.task);
    DCHECK(!request.need_unref);
    if (request.draw_image.paint_image().IsLazyGenerated()) {
      // Get the task for this decode.
      ImageDecodeCache::TaskResult result =
          cache_->GetOutOfRasterDecodeTaskForImageAndRef(image_cache_client_id_,
                                                         request.draw_image);
      request.need_unref = result.need_unref;
      request.task = result.task;
    }
    worker_state_->image_decode_queue[request.id] = std::move(request);
  }

  orphaned_decode_requests_.clear();
  ScheduleImageDecodeOnWorkerIfNeeded();
}

void ImageController::ScheduleImageDecodeOnWorkerIfNeeded() {
  if (worker_state_->task_state == WorkerTaskState::kNoTask &&
      !worker_state_->image_decode_queue.empty()) {
    worker_state_->task_state = WorkerTaskState::kQueuedTask;
    // base::Unretained is safe because `worker_state_` is guaranteed to be
    // deleted from a task posted to `worker_task_runner_` after this (see
    // ~ImageController).
    worker_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&ImageController::ProcessNextImageDecodeOnWorkerThread,
                       base::Unretained(worker_state_.get())));
  }
}

ImageController::ImageDecodeRequest::ImageDecodeRequest() = default;
ImageController::ImageDecodeRequest::ImageDecodeRequest(
    ImageDecodeRequestId id,
    const DrawImage& draw_image,
    ImageDecodedCallback callback,
    scoped_refptr<TileTask> task,
    bool need_unref)
    : id(id),
      draw_image(draw_image),
      callback(std::move(callback)),
      task(std::move(task)),
      need_unref(need_unref) {}
ImageController::ImageDecodeRequest::ImageDecodeRequest(
    ImageDecodeRequest&& other) = default;
ImageController::ImageDecodeRequest::~ImageDecodeRequest() = default;

ImageController::ImageDecodeRequest& ImageController::ImageDecodeRequest::
operator=(ImageDecodeRequest&& other) = default;

}  // namespace cc