// 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 "ui/gfx/geometry/resize_utils.h"

#include "base/check_op.h"
#include "base/cxx17_backports.h"
#include "base/numerics/safe_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

namespace gfx {
namespace {

// This function decides whether SizeRectToAspectRatio() will adjust the height
// to match the specified width (resizing horizontally) or vice versa (resizing
// vertically).
bool IsResizingHorizontally(ResizeEdge resize_edge) {
  switch (resize_edge) {
    case ResizeEdge::kLeft:
    case ResizeEdge::kRight:
    case ResizeEdge::kTopLeft:
    case ResizeEdge::kBottomLeft:
      return true;
    default:
      return false;
  }
}

}  // namespace

void SizeRectToAspectRatio(ResizeEdge resize_edge,
                           float aspect_ratio,
                           const Size& min_window_size,
                           const Size& max_window_size,
                           Rect* rect) {
  DCHECK_GT(aspect_ratio, 0.0f);
  DCHECK_GE(max_window_size.width(), min_window_size.width());
  DCHECK_GE(max_window_size.height(), min_window_size.height());
  DCHECK(rect->Contains(Rect(rect->origin(), min_window_size)))
      << rect->ToString() << " is smaller than the minimum size "
      << min_window_size.ToString();
  DCHECK(Rect(rect->origin(), max_window_size).Contains(*rect))
      << rect->ToString() << " is larger than the maximum size "
      << max_window_size.ToString();

  Size new_size = rect->size();
  if (IsResizingHorizontally(resize_edge)) {
    new_size.set_height(base::ClampRound(new_size.width() / aspect_ratio));
    if (min_window_size.height() > new_size.height() ||
        new_size.height() > max_window_size.height()) {
      new_size.set_height(base::clamp(new_size.height(),
                                      min_window_size.height(),
                                      max_window_size.height()));
      new_size.set_width(base::ClampRound(new_size.height() * aspect_ratio));
    }
  } else {
    new_size.set_width(base::ClampRound(new_size.height() * aspect_ratio));
    if (min_window_size.width() > new_size.width() ||
        new_size.width() > max_window_size.width()) {
      new_size.set_width(base::clamp(new_size.width(), min_window_size.width(),
                                     max_window_size.width()));
      new_size.set_height(base::ClampRound(new_size.width() / aspect_ratio));
    }
  }

  // The dimensions might still be outside of the allowed ranges at this point.
  // This happens when the aspect ratio makes it impossible to fit |rect|
  // within the size limits without letter-/pillarboxing.
  new_size.SetToMin(max_window_size);
  new_size.SetToMax(min_window_size);

  // |rect| bounds before sizing to aspect ratio.
  int left = rect->x();
  int top = rect->y();
  int right = rect->right();
  int bottom = rect->bottom();

  switch (resize_edge) {
    case ResizeEdge::kRight:
    case ResizeEdge::kBottom:
      right = new_size.width() + left;
      bottom = top + new_size.height();
      break;
    case ResizeEdge::kTop:
      right = new_size.width() + left;
      top = bottom - new_size.height();
      break;
    case ResizeEdge::kLeft:
    case ResizeEdge::kTopLeft:
      left = right - new_size.width();
      top = bottom - new_size.height();
      break;
    case ResizeEdge::kTopRight:
      right = left + new_size.width();
      top = bottom - new_size.height();
      break;
    case ResizeEdge::kBottomLeft:
      left = right - new_size.width();
      bottom = top + new_size.height();
      break;
    case ResizeEdge::kBottomRight:
      right = left + new_size.width();
      bottom = top + new_size.height();
      break;
  }

  rect->SetByBounds(left, top, right, bottom);
}

}  // namespace gfx
