// Copyright 2017 The Chromium 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 "components/wallpaper/wallpaper_color_calculator.h"

#include <string>
#include <utility>

#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "components/wallpaper/wallpaper_color_calculator_observer.h"
#include "components/wallpaper/wallpaper_color_extraction_result.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/image/image_skia.h"

using LumaRange = color_utils::LumaRange;
using SaturationRange = color_utils::SaturationRange;

namespace wallpaper {

namespace {

// The largest image size, in pixels, to synchronously calculate the prominent
// color. This is a simple heuristic optimization because extraction on images
// smaller than this should run very quickly, and offloading the task to another
// thread would actually take longer.
const int kMaxPixelsForSynchronousCalculation = 100;

// Wrapper for color_utils::CalculateProminentColorsOfBitmap() that records
// wallpaper specific metrics.
//
// NOTE: |image| is intentionally a copy to ensure it exists for the duration of
// the calculation.
std::vector<SkColor> CalculateWallpaperColor(
    const gfx::ImageSkia image,
    const std::vector<color_utils::ColorProfile> color_profiles) {
  base::TimeTicks start_time = base::TimeTicks::Now();
  const std::vector<SkColor> prominent_colors =
      color_utils::CalculateProminentColorsOfBitmap(*image.bitmap(),
                                                    color_profiles);

  UMA_HISTOGRAM_TIMES("Ash.Wallpaper.ColorExtraction.Durations",
                      base::TimeTicks::Now() - start_time);
  WallpaperColorExtractionResult result = NUM_COLOR_EXTRACTION_RESULTS;
  for (size_t i = 0; i < color_profiles.size(); ++i) {
    bool is_result_transparent = prominent_colors[i] == SK_ColorTRANSPARENT;
    if (color_profiles[i].saturation == SaturationRange::VIBRANT) {
      switch (color_profiles[i].luma) {
        case LumaRange::DARK:
          result = is_result_transparent ? RESULT_DARK_VIBRANT_TRANSPARENT
                                         : RESULT_DARK_VIBRANT_OPAQUE;
          break;
        case LumaRange::NORMAL:
          result = is_result_transparent ? RESULT_NORMAL_VIBRANT_TRANSPARENT
                                         : RESULT_NORMAL_VIBRANT_OPAQUE;
          break;
        case LumaRange::LIGHT:
          result = is_result_transparent ? RESULT_LIGHT_VIBRANT_TRANSPARENT
                                         : RESULT_LIGHT_VIBRANT_OPAQUE;
          break;
      }
    } else {
      switch (color_profiles[i].luma) {
        case LumaRange::DARK:
          result = is_result_transparent ? RESULT_DARK_MUTED_TRANSPARENT
                                         : RESULT_DARK_MUTED_OPAQUE;
          break;
        case LumaRange::NORMAL:
          result = is_result_transparent ? RESULT_NORMAL_MUTED_TRANSPARENT
                                         : RESULT_NORMAL_MUTED_OPAQUE;
          break;
        case LumaRange::LIGHT:
          result = is_result_transparent ? RESULT_LIGHT_MUTED_TRANSPARENT
                                         : RESULT_LIGHT_MUTED_OPAQUE;
          break;
      }
    }
  }
  DCHECK_NE(NUM_COLOR_EXTRACTION_RESULTS, result);
  UMA_HISTOGRAM_ENUMERATION("Ash.Wallpaper.ColorExtractionResult2", result,
                            NUM_COLOR_EXTRACTION_RESULTS);

  return prominent_colors;
}

bool ShouldCalculateSync(const gfx::ImageSkia& image) {
  return image.width() * image.height() <= kMaxPixelsForSynchronousCalculation;
}

}  // namespace

WallpaperColorCalculator::WallpaperColorCalculator(
    const gfx::ImageSkia& image,
    const std::vector<color_utils::ColorProfile>& color_profiles,
    scoped_refptr<base::TaskRunner> task_runner)
    : image_(image),
      color_profiles_(color_profiles),
      task_runner_(std::move(task_runner)),
      weak_ptr_factory_(this) {
  prominent_colors_ =
      std::vector<SkColor>(color_profiles_.size(), SK_ColorTRANSPARENT);
}

WallpaperColorCalculator::~WallpaperColorCalculator() = default;

void WallpaperColorCalculator::AddObserver(
    WallpaperColorCalculatorObserver* observer) {
  observers_.AddObserver(observer);
}

void WallpaperColorCalculator::RemoveObserver(
    WallpaperColorCalculatorObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool WallpaperColorCalculator::StartCalculation() {
  if (ShouldCalculateSync(image_)) {
    const std::vector<SkColor> prominent_colors =
        CalculateWallpaperColor(image_, color_profiles_);
    NotifyCalculationComplete(prominent_colors);
    return true;
  }

  image_.MakeThreadSafe();
  if (base::PostTaskAndReplyWithResult(
          task_runner_.get(), FROM_HERE,
          base::Bind(&CalculateWallpaperColor, image_, color_profiles_),
          base::Bind(&WallpaperColorCalculator::OnAsyncCalculationComplete,
                     weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()))) {
    return true;
  }

  LOG(WARNING) << "PostSequencedWorkerTask failed. "
               << "Wallpaper prominent colors may not be calculated.";

  prominent_colors_ =
      std::vector<SkColor>(color_profiles_.size(), SK_ColorTRANSPARENT);
  return false;
}

void WallpaperColorCalculator::SetTaskRunnerForTest(
    scoped_refptr<base::TaskRunner> task_runner) {
  task_runner_ = task_runner;
}

void WallpaperColorCalculator::OnAsyncCalculationComplete(
    base::TimeTicks async_start_time,
    const std::vector<SkColor>& prominent_colors) {
  UMA_HISTOGRAM_TIMES("Ash.Wallpaper.ColorExtraction.UserDelay",
                      base::TimeTicks::Now() - async_start_time);
  NotifyCalculationComplete(prominent_colors);
}

void WallpaperColorCalculator::NotifyCalculationComplete(
    const std::vector<SkColor>& prominent_colors) {
  prominent_colors_ = prominent_colors;
  for (auto& observer : observers_)
    observer.OnColorCalculationComplete();

  // This could be deleted!
}

}  // namespace wallpaper
