// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/common/viz_utils.h"

#include <algorithm>
#include <vector>

#include "base/system/sys_info.h"
#include "build/build_config.h"
#include "cc/base/features.h"
#include "cc/base/math_util.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/aggregated_render_pass_draw_quad.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/render_pass_draw_quad_internal.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rrect_f.h"

#if BUILDFLAG(IS_POSIX)
#include <poll.h>
#include <sys/resource.h>
#endif

namespace viz {



bool GatherFDStats(base::TimeDelta* delta_time_taken,
                   int* fd_max,
                   int* active_fd_count,
                   int* rlim_cur) {
#if !BUILDFLAG(IS_POSIX)
  return false;
#else   // BUILDFLAG(IS_POSIX)
  // https://stackoverflow.com/questions/7976769/
  // getting-count-of-current-used-file-descriptors-from-c-code
  base::ElapsedTimer timer;
  rlimit limit_data;
  getrlimit(RLIMIT_NOFILE, &limit_data);
  std::vector<pollfd> poll_data;
  constexpr int kMaxNumFDTested = 1 << 16;
  // |rlim_cur| is the soft max but is likely the value we can rely on instead
  // of the real max.
  *rlim_cur = static_cast<int>(limit_data.rlim_cur);
  *fd_max = std::max(1, std::min(*rlim_cur, kMaxNumFDTested));
  poll_data.resize(*fd_max);
  for (size_t i = 0; i < poll_data.size(); i++) {
    auto& each = poll_data[i];
    each.fd = static_cast<int>(i);
    each.events = 0;
    each.revents = 0;
  }

  poll(poll_data.data(), poll_data.size(), 0);
  *active_fd_count = 0;
  for (auto&& each : poll_data) {
    if (each.revents != POLLNVAL)
      (*active_fd_count)++;
  }
  *delta_time_taken = timer.Elapsed();
  return true;
#endif  // BUILDFLAG(IS_POSIX)
}
gfx::RectF ClippedQuadRectangleF(const DrawQuad* quad) {
  gfx::RectF quad_rect = cc::MathUtil::MapClippedRect(
      quad->shared_quad_state->quad_to_target_transform,
      gfx::RectF(quad->rect));
  if (quad->shared_quad_state->clip_rect)
    quad_rect.Intersect(gfx::RectF(*quad->shared_quad_state->clip_rect));
  return quad_rect;
}

gfx::Rect ClippedQuadRectangle(const DrawQuad* quad) {
  return gfx::ToEnclosingRect(ClippedQuadRectangleF(quad));
}

gfx::Rect GetTargetExpandedRectForPixelMovingFilters(
    const CompositorRenderPassDrawQuad& rpdq,
    const cc::FilterOperations& filters) {
  const SharedQuadState* shared_quad_state = rpdq.shared_quad_state;
  gfx::Rect expanded_rect = GetExpandedRectForPixelMovingFilters(rpdq, filters);
  return cc::MathUtil::MapEnclosingClippedRect(
      shared_quad_state->quad_to_target_transform, expanded_rect);
}

gfx::Rect GetExpandedRectForPixelMovingFilters(
    const CompositorRenderPassDrawQuad& rpdq,
    const cc::FilterOperations& filters) {
  SkMatrix local_matrix =
      SkMatrix::Translate(rpdq.filters_origin.x(), rpdq.filters_origin.y());
  local_matrix.postScale(rpdq.filters_scale.x(), rpdq.filters_scale.y());

  return filters.MapRect(rpdq.visible_rect, local_matrix);
}

gfx::Rect GetTargetExpandedRectForPixelMovingFilters(
    const AggregatedRenderPassDrawQuad& rpdq) {
  const SharedQuadState* shared_quad_state = rpdq.shared_quad_state;
  gfx::Rect expanded_rect = GetExpandedRectForPixelMovingFilters(rpdq);
  return cc::MathUtil::MapEnclosingClippedRect(
      shared_quad_state->quad_to_target_transform, expanded_rect);
}

gfx::Rect GetExpandedRectForPixelMovingFilters(
    const AggregatedRenderPassDrawQuad& rpdq) {
  SkMatrix local_matrix =
      SkMatrix::Translate(rpdq.filters_origin.x(), rpdq.filters_origin.y());
  local_matrix.postScale(rpdq.filters_scale.x(), rpdq.filters_scale.y());

  return rpdq.filters.MapRect(rpdq.visible_rect, local_matrix);
}

gfx::Transform GetViewTransitionTransform(
    gfx::Rect shared_element_quad,
    gfx::Rect view_transition_content_output) {
  gfx::Transform view_transition_transform;

  view_transition_transform.Translate(shared_element_quad.x(),
                                      shared_element_quad.y());

  view_transition_transform.Scale(
      shared_element_quad.width() /
          static_cast<SkScalar>(view_transition_content_output.width()),
      shared_element_quad.height() /
          static_cast<SkScalar>(view_transition_content_output.height()));

  view_transition_transform.Translate(-view_transition_content_output.x(),
                                      -view_transition_content_output.y());

  return view_transition_transform;
}

bool QuadRoundedCornersBoundsIntersects(const DrawQuad* quad,
                                        const gfx::RectF& target_quad) {
  const SharedQuadState* sqs = quad->shared_quad_state;
  const gfx::MaskFilterInfo& mask_filter_info = sqs->mask_filter_info;

  // There is no rounded corner set.
  if (!mask_filter_info.HasRoundedCorners()) {
    return false;
  }

  const gfx::RRectF& rounded_corner_bounds =
      mask_filter_info.rounded_corner_bounds();

  const gfx::RRectF::Corner corners[] = {
      gfx::RRectF::Corner::kUpperLeft, gfx::RRectF::Corner::kUpperRight,
      gfx::RRectF::Corner::kLowerRight, gfx::RRectF::Corner::kLowerLeft};
  for (auto c : corners) {
    if (rounded_corner_bounds.CornerBoundingRect(c).Intersects(target_quad)) {
      return true;
    }
  }
  return false;
}

void SetCopyOutputRequestResultSize(CopyOutputRequest* request,
                                    const gfx::Rect& src_rect,
                                    const gfx::Size& output_size,
                                    const gfx::Size& surface_size_in_pixels) {
  CHECK(request);
  if (!src_rect.IsEmpty()) {
    request->set_area(src_rect);
  }
  if (output_size.IsEmpty()) {
    return;
  }
  // The CopyOutputRequest API does not allow fixing the output size. Instead
  // we have the set area and scale in such a way that it would result in the
  // desired output size.
  if (!request->has_area()) {
    request->set_area(gfx::Rect(surface_size_in_pixels));
  }
  request->set_result_selection(gfx::Rect(output_size));
  const gfx::Rect& area = request->area();
  // Viz would normally return an empty result for an empty area.
  // However, this guard here is still necessary to protect against setting
  // an illegal scaling ratio.
  if (area.IsEmpty()) {
    return;
  }
  request->SetScaleRatio(
      gfx::Vector2d(area.width(), area.height()),
      gfx::Vector2d(output_size.width(), output_size.height()));
}

}  // namespace viz
