// Copyright 2020 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 "ash/ambient/ui/ambient_background_image_view.h"

#include <memory>

#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/ui/ambient_info_view.h"
#include "ash/ambient/ui/ambient_shield_view.h"
#include "ash/ambient/ui/ambient_view_ids.h"
#include "ash/ambient/ui/media_string_view.h"
#include "ash/ambient/util/ambient_util.h"
#include "base/rand_util.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/view_class_properties.h"

namespace ash {

namespace {

// Appearance.
constexpr int kMediaStringMarginDip = 32;

// The dicretion to translate glanceable info views in the x/y coordinates.  `1`
// means positive translate, `-1` negative.
int translate_x_direction = 1;
int translate_y_direction = -1;
// The current x/y translation of glanceable info views in Dip.
int current_x_translation = 0;
int current_y_translation = 0;

gfx::ImageSkia ResizeImage(const gfx::ImageSkia& image,
                           const gfx::Size& view_size) {
  if (image.isNull())
    return gfx::ImageSkia();

  const double image_width = image.width();
  const double image_height = image.height();
  const double view_width = view_size.width();
  const double view_height = view_size.height();
  const double horizontal_ratio = view_width / image_width;
  const double vertical_ratio = view_height / image_height;
  const double image_ratio = image_height / image_width;
  const double view_ratio = view_height / view_width;

  // If the image and the container view has the same orientation, e.g. both
  // portrait, the |scale| will make the image filled the whole view with
  // possible cropping on one direction. If they are in different orientation,
  // the |scale| will display the image in the view without any cropping, but
  // with empty background.
  const double scale = (image_ratio - 1) * (view_ratio - 1) > 0
                           ? std::max(horizontal_ratio, vertical_ratio)
                           : std::min(horizontal_ratio, vertical_ratio);
  const gfx::Size& resized = gfx::ScaleToCeiledSize(image.size(), scale);
  return gfx::ImageSkiaOperations::CreateResizedImage(
      image, skia::ImageOperations::RESIZE_BEST, resized);
}

}  // namespace

AmbientBackgroundImageView::AmbientBackgroundImageView(
    AmbientViewDelegate* delegate)
    : delegate_(delegate) {
  DCHECK(delegate_);
  SetID(AmbientViewID::kAmbientBackgroundImageView);
  InitLayout();
}

AmbientBackgroundImageView::~AmbientBackgroundImageView() = default;

void AmbientBackgroundImageView::OnBoundsChanged(
    const gfx::Rect& previous_bounds) {
  if (!GetVisible())
    return;

  if (width() == 0)
    return;

  // When bounds changes, recalculate the visibility of related image view.
  UpdateRelatedImageViewVisibility();
}

void AmbientBackgroundImageView::OnViewBoundsChanged(
    views::View* observed_view) {
  if (observed_view == image_view_)
    SetResizedImage(image_view_, image_unscaled_);
  else
    SetResizedImage(related_image_view_, related_image_unscaled_);
}

void AmbientBackgroundImageView::UpdateImage(
    const gfx::ImageSkia& image,
    const gfx::ImageSkia& related_image) {
  image_unscaled_ = image;
  related_image_unscaled_ = related_image;

  UpdateGlanceableInfoPosition();

  const bool has_change = UpdateRelatedImageViewVisibility();

  // If there is no change in the visibility of related image view, call
  // SetResizedImages() directly. Otherwise it will be called from
  // OnViewBoundsChanged().
  if (!has_change) {
    SetResizedImage(image_view_, image_unscaled_);
    SetResizedImage(related_image_view_, related_image_unscaled_);
  }
}

void AmbientBackgroundImageView::UpdateImageDetails(
    const base::string16& details) {
  ambient_info_view_->UpdateImageDetails(details);
}

const gfx::ImageSkia& AmbientBackgroundImageView::GetCurrentImage() {
  return image_view_->GetImage();
}

gfx::Rect AmbientBackgroundImageView::GetImageBoundsForTesting() const {
  return image_view_->GetImageBounds();
}

gfx::Rect AmbientBackgroundImageView::GetRelatedImageBoundsForTesting() const {
  return related_image_view_->GetVisible()
             ? related_image_view_->GetImageBounds()
             : gfx::Rect();
}

void AmbientBackgroundImageView::ResetRelatedImageForTesting() {
  related_image_unscaled_ = gfx::ImageSkia();
  UpdateRelatedImageViewVisibility();
}

void AmbientBackgroundImageView::InitLayout() {
  static const views::FlexSpecification kUnboundedScaleToZero(
      views::MinimumFlexSizeRule::kScaleToZero,
      views::MaximumFlexSizeRule::kUnbounded);

  SetLayoutManager(std::make_unique<views::FillLayout>());

  // Inits container for images.
  image_container_ = AddChildView(std::make_unique<views::View>());
  views::FlexLayout* image_layout =
      image_container_->SetLayoutManager(std::make_unique<views::FlexLayout>());
  image_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
  image_layout->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
  image_layout->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
  image_view_ =
      image_container_->AddChildView(std::make_unique<views::ImageView>());
  // Set a place holder size for Flex layout to assign bounds.
  image_view_->SetPreferredSize(gfx::Size(1, 1));
  image_view_->SetProperty(views::kFlexBehaviorKey, kUnboundedScaleToZero);
  observed_views_.AddObservation(image_view_);

  related_image_view_ =
      image_container_->AddChildView(std::make_unique<views::ImageView>());
  // Set a place holder size for Flex layout to assign bounds.
  related_image_view_->SetPreferredSize(gfx::Size(1, 1));
  related_image_view_->SetProperty(views::kFlexBehaviorKey,
                                   kUnboundedScaleToZero);
  observed_views_.AddObservation(related_image_view_);

  // Set spacing between two images.
  related_image_view_->SetProperty(
      views::kMarginsKey, gfx::Insets(0, kMarginLeftOfRelatedImageDip, 0, 0));

  AddChildView(std::make_unique<AmbientShieldView>());

  ambient_info_view_ =
      AddChildView(std::make_unique<AmbientInfoView>(delegate_));

  gfx::Insets shadow_insets =
      gfx::ShadowValue::GetMargin(ambient::util::GetTextShadowValues());

  // Inits the media string view. The media string view is positioned on the
  // right-top corner of the container.
  views::View* media_string_view_container_ =
      AddChildView(std::make_unique<views::View>());
  views::BoxLayout* media_string_layout =
      media_string_view_container_->SetLayoutManager(
          std::make_unique<views::BoxLayout>(
              views::BoxLayout::Orientation::kVertical));
  media_string_layout->set_main_axis_alignment(
      views::BoxLayout::MainAxisAlignment::kStart);
  media_string_layout->set_cross_axis_alignment(
      views::BoxLayout::CrossAxisAlignment::kEnd);
  media_string_layout->set_inside_border_insets(
      gfx::Insets(kMediaStringMarginDip + shadow_insets.top(), 0, 0,
                  kMediaStringMarginDip + shadow_insets.right()));
  media_string_view_ = media_string_view_container_->AddChildView(
      std::make_unique<MediaStringView>());
  media_string_view_->SetVisible(false);
}

void AmbientBackgroundImageView::UpdateGlanceableInfoPosition() {
  constexpr int kStepDP = 5;
  constexpr int kMaxTranslationDip = 20;

  // Move the translation point randomly one step on each x/y direction.
  int x_increment = kStepDP * base::RandInt(0, 1);
  int y_increment = x_increment == 0 ? kStepDP : kStepDP * base::RandInt(0, 1);
  current_x_translation += translate_x_direction * x_increment;
  current_y_translation += translate_y_direction * y_increment;

  // If the translation point is out of bounds, reset it within bounds and
  // reverse the direction.
  if (current_x_translation < 0) {
    translate_x_direction = 1;
    current_x_translation = 0;
  } else if (current_x_translation > kMaxTranslationDip) {
    translate_x_direction = -1;
    current_x_translation = kMaxTranslationDip;
  }

  if (current_y_translation > 0) {
    translate_y_direction = -1;
    current_y_translation = 0;
  } else if (current_y_translation < -kMaxTranslationDip) {
    translate_y_direction = 1;
    current_y_translation = -kMaxTranslationDip;
  }

  gfx::Transform transform;
  transform.Translate(current_x_translation, current_y_translation);
  ambient_info_view_->SetTextTransform(transform);

  if (media_string_view_->GetVisible()) {
    gfx::Transform media_string_transform;
    media_string_transform.Translate(-current_x_translation,
                                     -current_y_translation);
    media_string_view_->layer()->SetTransform(media_string_transform);
  }
}

bool AmbientBackgroundImageView::UpdateRelatedImageViewVisibility() {
  const bool did_show_pair = related_image_view_->GetVisible();
  const bool show_pair = IsLandscapeOrientation() && HasPairedImages();
  related_image_view_->SetVisible(show_pair);
  return did_show_pair != show_pair;
}

void AmbientBackgroundImageView::SetResizedImage(
    views::ImageView* image_view,
    const gfx::ImageSkia& image_unscaled) {
  if (!image_view->GetVisible())
    return;

  if (image_unscaled.isNull())
    return;

  image_view->SetImage(ResizeImage(image_unscaled, image_view->size()));

  // Intend to update the image origin in image view.
  // There is no bounds change or preferred size change when updating image from
  // landscape to portrait when device is in portrait orientation because we
  // only show one photo. Call ResetImageSize() to trigger UpdateImageOrigin().
  image_view->ResetImageSize();
}

bool AmbientBackgroundImageView::IsLandscapeOrientation() const {
  return width() > height();
}

bool AmbientBackgroundImageView::HasPairedImages() const {
  return !image_unscaled_.isNull() && !related_image_unscaled_.isNull();
}

BEGIN_METADATA(AmbientBackgroundImageView, views::View)
END_METADATA

}  // namespace ash
