/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/core/page/drag_image.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "cc/paint/skia_paint_canvas.h"
#include "skia/ext/image_operations.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/layout/layout_theme_font_provider.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/font_description.h"
#include "third_party/blink/renderer/platform/fonts/font_metrics.h"
#include "third_party/blink/renderer/platform/fonts/plain_text_painter.h"
#include "third_party/blink/renderer/platform/fonts/string_truncator.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/text/bidi_paragraph.h"
#include "third_party/blink/renderer/platform/text/text_run.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"

namespace blink {

namespace {

const float kDragLabelBorderX = 4;
// Keep border_y in synch with DragController::LinkDragBorderInset.
const float kDragLabelBorderY = 2;
const float kLabelBorderYOffset = 2;

const float kMaxDragLabelWidth = 300;
const float kMaxDragLabelStringWidth =
    (kMaxDragLabelWidth - 2 * kDragLabelBorderX);

const float kDragLinkLabelFontSize = 11;
const float kDragLinkUrlFontSize = 10;

}  // anonymous namespace

gfx::Vector2dF DragImage::ClampedImageScale(const gfx::Size& image_size,
                                            const gfx::Size& size,
                                            const gfx::Size& max_size) {
  // Non-uniform scaling for size mapping.
  gfx::Vector2dF image_scale(
      static_cast<float>(size.width()) / image_size.width(),
      static_cast<float>(size.height()) / image_size.height());

  // Uniform scaling for clamping.
  const float clamp_scale_x =
      size.width() > max_size.width()
          ? static_cast<float>(max_size.width()) / size.width()
          : 1;
  const float clamp_scale_y =
      size.height() > max_size.height()
          ? static_cast<float>(max_size.height()) / size.height()
          : 1;
  image_scale.Scale(std::min(clamp_scale_x, clamp_scale_y));

  return image_scale;
}

std::unique_ptr<DragImage> DragImage::Create(
    Image* image,
    RespectImageOrientationEnum should_respect_image_orientation,
    InterpolationQuality interpolation_quality,
    float opacity,
    gfx::Vector2dF image_scale) {
  if (!image)
    return nullptr;

  PaintImage paint_image = image->PaintImageForCurrentFrame();
  if (!paint_image)
    return nullptr;

  ImageOrientation orientation;
  auto* bitmap_image = DynamicTo<BitmapImage>(image);
  if (should_respect_image_orientation == kRespectImageOrientation &&
      bitmap_image)
    orientation = bitmap_image->Orientation();

  SkBitmap bm;
  paint_image = Image::ResizeAndOrientImage(
      paint_image, orientation, image_scale, opacity, interpolation_quality,
      SkColorSpace::MakeSRGB());
  if (!paint_image || !paint_image.GetSwSkImage()->asLegacyBitmap(&bm))
    return nullptr;

  return base::WrapUnique(new DragImage(bm, interpolation_quality));
}

static Font* DeriveDragLabelFont(int size, FontSelectionValue font_weight) {
  const AtomicString& family =
      LayoutThemeFontProvider::SystemFontFamily(CSSValueID::kNone);

  FontDescription description;
  description.SetFamily(
      FontFamily(family, FontFamily::InferredTypeFor(family)));
  description.SetWeight(font_weight);
  description.SetSpecifiedSize(size);
  description.SetComputedSize(size);
  return MakeGarbageCollected<Font>(description);
}

// static
std::unique_ptr<DragImage> DragImage::Create(const KURL& url,
                                             const String& in_label,
                                             float device_scale_factor) {
  const Font* label_font =
      DeriveDragLabelFont(kDragLinkLabelFontSize, kBoldWeightValue);
  const SimpleFontData* label_font_data = label_font->PrimaryFont();
  DCHECK(label_font_data);
  const Font* url_font =
      DeriveDragLabelFont(kDragLinkUrlFontSize, kNormalWeightValue);
  const SimpleFontData* url_font_data = url_font->PrimaryFont();
  DCHECK(url_font_data);

  if (!label_font_data || !url_font_data)
    return nullptr;

  bool draw_url_string = true;
  bool clip_url_string = false;
  bool clip_label_string = false;
  float max_drag_label_string_width_dip =
      kMaxDragLabelStringWidth / device_scale_factor;

  String url_string = url.GetString();
  String label = in_label.StripWhiteSpace();
  if (label.empty()) {
    draw_url_string = false;
    label = url_string;
  }
  PlainTextPainter& text_painter = PlainTextPainter::Shared();

  // First step is drawing the link drag image width.
  gfx::Size label_size(
      text_painter.ComputeInlineSize(TextRun(label), *label_font),
      label_font_data->GetFontMetrics().Ascent() +
          label_font_data->GetFontMetrics().Descent());

  if (label_size.width() > max_drag_label_string_width_dip) {
    label_size.set_width(max_drag_label_string_width_dip);
    clip_label_string = true;
  }

  gfx::Size url_string_size;
  gfx::Size image_size(label_size.width() + kDragLabelBorderX * 2,
                       label_size.height() + kDragLabelBorderY * 2);

  if (draw_url_string) {
    url_string_size.set_width(text_painter.ComputeInlineSizeWithoutBidi(
        TextRun(url_string), *url_font));
    url_string_size.set_height(url_font_data->GetFontMetrics().Ascent() +
                               url_font_data->GetFontMetrics().Descent());
    image_size.set_height(image_size.height() + url_string_size.height());
    if (url_string_size.width() > max_drag_label_string_width_dip) {
      image_size.set_width(max_drag_label_string_width_dip);
      clip_url_string = true;
    } else {
      image_size.set_width(
          std::max(label_size.width(), url_string_size.width()) +
          kDragLabelBorderX * 2);
    }
  }

  // We now know how big the image needs to be, so we create and
  // fill the background
  gfx::Size scaled_image_size =
      gfx::ScaleToFlooredSize(image_size, device_scale_factor);

  SkSurfaceProps surface_props;
  sk_sp<SkSurface> surface =
      SkSurfaces::Raster(SkImageInfo::MakeN32Premul(scaled_image_size.width(),
                                                    scaled_image_size.height()),
                         &surface_props);
  if (!surface) {
    return nullptr;
  }

  cc::SkiaPaintCanvas canvas(surface->getCanvas());
  canvas.scale(device_scale_factor, device_scale_factor);

  const float kDragLabelRadius = 5;

  gfx::Rect rect(image_size);
  cc::PaintFlags background_paint;
  background_paint.setColor(SkColorSetRGB(140, 140, 140));
  background_paint.setAntiAlias(true);
  SkRRect rrect;
  rrect.setRectXY(SkRect::MakeWH(image_size.width(), image_size.height()),
                  kDragLabelRadius, kDragLabelRadius);
  canvas.drawRRect(rrect, background_paint);

  // Draw the text
  cc::PaintFlags text_paint;
  if (draw_url_string) {
    if (clip_url_string) {
      url_string = StringTruncator::CenterTruncate(
          url_string, image_size.width() - (kDragLabelBorderX * 2.0f),
          *url_font);
    }
    gfx::PointF text_pos(
        kDragLabelBorderX,
        image_size.height() -
            (kLabelBorderYOffset + url_font_data->GetFontMetrics().Descent()));
    TextRun text_run(url_string);
    text_painter.DrawWithoutBidi(text_run, *url_font, canvas, text_pos,
                                 text_paint);
  }

  if (clip_label_string) {
    label = StringTruncator::RightTruncate(
        label, image_size.width() - (kDragLabelBorderX * 2.0f), *label_font);
  }

  TextRun text_run(label, BidiParagraph::BaseDirectionForStringOrLtr(label));
  gfx::Point text_pos(
      kDragLabelBorderX,
      kDragLabelBorderY + label_font->GetFontDescription().ComputedPixelSize());
  if (text_run.Direction() == TextDirection::kRtl) {
    float text_width = text_painter.ComputeInlineSize(text_run, *label_font);
    int available_width = image_size.width() - kDragLabelBorderX * 2;
    text_pos.set_x(available_width - ceilf(text_width));
  }
  text_painter.DrawWithBidiReorder(text_run, 0, text_run.length(), *label_font,
                                   Font::kDoNotPaintIfFontNotReady, canvas,
                                   gfx::PointF(text_pos), text_paint);

  scoped_refptr<Image> image =
      UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
  return DragImage::Create(image.get(), kRespectImageOrientation);
}

DragImage::DragImage(const SkBitmap& bitmap,
                     InterpolationQuality interpolation_quality)
    : bitmap_(bitmap), interpolation_quality_(interpolation_quality) {}

DragImage::~DragImage() = default;

void DragImage::Scale(float scale_x, float scale_y) {
  skia::ImageOperations::ResizeMethod resize_method =
      interpolation_quality_ == kInterpolationNone
          ? skia::ImageOperations::RESIZE_BOX
          : skia::ImageOperations::RESIZE_LANCZOS3;
  int image_width = scale_x * bitmap_.width();
  int image_height = scale_y * bitmap_.height();
  bitmap_ = skia::ImageOperations::Resize(bitmap_, resize_method, image_width,
                                          image_height);
}

}  // namespace blink
