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

#include "ui/display/display.h"

#include <algorithm>
#include <sstream>
#include <string>
#include <vector>

#include "vhaiup/getvhaiup.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "ui/display/display_switches.h"
#include "ui/display/util/display_util.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/geometry/size.h"

namespace display {
namespace {

// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------

//constexpr float kDisplaySizeAllowanceEpsilon = 0.01f;

// -----------------------------------------------------------------------------
// Cached forced device scale factor
// -----------------------------------------------------------------------------

int g_has_forced_device_scale_factor = -1;
float g_forced_device_scale_factor = -1.0f;

// -----------------------------------------------------------------------------
// Custom configuration helpers
// -----------------------------------------------------------------------------

int32_t GetCustomScreenWidth() {
  return std::stoi(
      getvhaiup::getvhaiup("screen_width", "int"));
}

int32_t GetCustomScreenHeight() {
  return std::stoi(
      getvhaiup::getvhaiup("screen_height", "int"));
}

float GetCustomScreenDipScale() {
  return static_cast<float>(
      std::stod(
          getvhaiup::getvhaiup("screen_dip_scale", "double")));
}

float GetCustomScreenXdpiX() {
  return static_cast<float>(
      std::stod(
          getvhaiup::getvhaiup("screen_xdpi_x", "double")));
}

float GetCustomScreenXdpiY() {
  return static_cast<float>(
      std::stod(
          getvhaiup::getvhaiup("screen_xdpi_y", "double")));
}

gfx::Size GetCustomScreenSize() {
  return gfx::Size(
      GetCustomScreenWidth(),
      GetCustomScreenHeight());
}

gfx::Rect ParseCustomRect(const char* key) {
  std::string value =
      getvhaiup::getvhaiup(key, "string");

  std::vector<int> values;
  std::stringstream ss(value);
  std::string item;

  while (std::getline(ss, item, ',')) {
    if (!item.empty()) {
      values.push_back(std::stoi(item));
    }
  }

  if (values.size() != 4) {
    LOG(ERROR) << "Invalid " << key << ": " << value;
    return gfx::Rect();
  }

  return gfx::Rect(
      gfx::Point(values[0], values[1]),
      gfx::Size(
          values[2] - values[0],
          values[3] - values[1]));
}

gfx::Rect GetCustomScreenBounds() {
  return ParseCustomRect("screen_bounds");
}

gfx::Rect GetCustomScreenWorkArea() {
  return ParseCustomRect("screen_works");
}

// -----------------------------------------------------------------------------
// Original Chromium helpers
// -----------------------------------------------------------------------------

bool HasForceDeviceScaleFactorImpl() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kForceDeviceScaleFactor);
}



const char* ToRotationString(
    display::Display::Rotation rotation) {
  switch (rotation) {
    case display::Display::ROTATE_0:
      return "0";

    case display::Display::ROTATE_90:
      return "90";

    case display::Display::ROTATE_180:
      return "180";

    case display::Display::ROTATE_270:
      return "270";
  }

  NOTREACHED();
}

}  // namespace

// -----------------------------------------------------------------------------
// Device scale factor
// -----------------------------------------------------------------------------

// static
float Display::GetForcedDeviceScaleFactor() {
  if (g_forced_device_scale_factor < 0) {
    g_forced_device_scale_factor =
        GetCustomScreenDipScale();
  }

  return g_forced_device_scale_factor;
}

// static
bool Display::HasForceDeviceScaleFactor() {
  if (g_has_forced_device_scale_factor == -1) {
    g_has_forced_device_scale_factor = 1;
  }

  return true;
}

// static
void Display::ResetForceDeviceScaleFactorForTesting() {
  g_has_forced_device_scale_factor = -1;
  g_forced_device_scale_factor = -1.0f;
}

// static
void Display::SetForceDeviceScaleFactor(double dsf) {
  g_has_forced_device_scale_factor = 1;

  // Custom configuration remains authoritative.
  g_forced_device_scale_factor =
      GetCustomScreenDipScale();
}

// -----------------------------------------------------------------------------
// Color profile
// -----------------------------------------------------------------------------

// static
gfx::ColorSpace Display::GetForcedRasterColorProfile() {
  DCHECK(HasForceRasterColorProfile());

  std::string value =
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kForceRasterColorProfile);

  return ForcedColorProfileStringToColorSpace(value);
}

// static
bool Display::HasForceRasterColorProfile() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kForceRasterColorProfile);
}

// static
bool Display::HasEnsureForcedColorProfile() {
  static bool has_ensure_forced_color_profile =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnsureForcedColorProfile);

  return has_ensure_forced_color_profile;
}

// -----------------------------------------------------------------------------
// Rotation
// -----------------------------------------------------------------------------

// static
display::Display::Rotation Display::DegreesToRotation(
    int degrees) {
  if (degrees == 0)
    return display::Display::ROTATE_0;

  if (degrees == 90)
    return display::Display::ROTATE_90;

  if (degrees == 180)
    return display::Display::ROTATE_180;

  if (degrees == 270)
    return display::Display::ROTATE_270;

  NOTREACHED();
}

// static
int Display::RotationToDegrees(
    display::Display::Rotation rotation) {
  switch (rotation) {
    case display::Display::ROTATE_0:
      return 0;

    case display::Display::ROTATE_90:
      return 90;

    case display::Display::ROTATE_180:
      return 180;

    case display::Display::ROTATE_270:
      return 270;
  }

  NOTREACHED();
}

// static
bool Display::IsValidRotation(int degrees) {
  return degrees == 0 ||
         degrees == 90 ||
         degrees == 180 ||
         degrees == 270;
}

// -----------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------

Display::Display()
    : Display(kInvalidDisplayId) {}

Display::Display(int64_t id)
    : Display(id, gfx::Rect()) {}

Display::Display(int64_t id, const gfx::Rect& bounds)
    : id_(id) {
  // Do these assignments in the constructor body rather than the initializer
  // list. This avoids -Wreorder-ctor regardless of the exact member order in
  // display.h.

  bounds_ =
      GetCustomScreenBounds();

  work_area_ =
      GetCustomScreenWorkArea();

  size_in_pixels_ =
      GetCustomScreenSize();

  device_scale_factor_ =
      GetCustomScreenDipScale();

  native_origin_ =
      GetCustomScreenBounds().origin();

  pixels_per_inch_x_ =
      GetCustomScreenXdpiX();

  pixels_per_inch_y_ =
      GetCustomScreenXdpiY();

  SetDisplayColorSpacesRef(
      GetDefaultDisplayColorSpacesRef());
}

Display::Display(const Display& other) = default;

Display::~Display() = default;

// -----------------------------------------------------------------------------
// Default display
// -----------------------------------------------------------------------------

// static
Display Display::GetDefaultDisplay() {
  return Display(
      kDefaultDisplayId,
      GetCustomScreenBounds());
}

// -----------------------------------------------------------------------------
// Rotation
// -----------------------------------------------------------------------------

int Display::RotationAsDegree() const {
  switch (rotation_) {
    case ROTATE_0:
      return 0;

    case ROTATE_90:
      return 90;

    case ROTATE_180:
      return 180;

    case ROTATE_270:
      return 270;
  }

  NOTREACHED();
}

// -----------------------------------------------------------------------------
// Color spaces
// -----------------------------------------------------------------------------

const gfx::DisplayColorSpaces&
Display::GetColorSpaces() const {
  return color_spaces_->color_spaces();
}

void Display::SetColorSpaces(
    const gfx::DisplayColorSpaces& color_spaces) {
  SetDisplayColorSpacesRef(
      base::MakeRefCounted<
          gfx::DisplayColorSpacesRef>(color_spaces));
}

// -----------------------------------------------------------------------------
// Work area
// -----------------------------------------------------------------------------

void Display::SetRotationAsDegree(int rotation) {
  switch (rotation) {
    case 0:
      rotation_ = ROTATE_0;
      break;

    case 90:
      rotation_ = ROTATE_90;
      break;

    case 180:
      rotation_ = ROTATE_180;
      break;

    case 270:
      rotation_ = ROTATE_270;
      break;

    default:
      NOTREACHED();
  }
}

int Display::PanelRotationAsDegree() const {
  return RotationToDegrees(panel_rotation());
}

gfx::Rect Display::GetLocalWorkArea() const {
  gfx::Rect local_work_area(size());

  local_work_area.Inset(
      GetWorkAreaInsets());

  return local_work_area;
}

gfx::Insets Display::GetWorkAreaInsets() const {
  const gfx::Rect& custom_bounds =
      bounds_;

  const gfx::Rect& custom_work_area =
      work_area_;

  return gfx::Insets::TLBR(
      custom_work_area.y() -
          custom_bounds.y(),

      custom_work_area.x() -
          custom_bounds.x(),

      custom_bounds.bottom() -
          custom_work_area.bottom(),

      custom_bounds.right() -
          custom_work_area.right());
}

// -----------------------------------------------------------------------------
// Scale + bounds
// -----------------------------------------------------------------------------

void Display::SetScaleAndBounds(
    float device_scale_factor,
    const gfx::Rect& bounds_in_pixel) {

  // Custom values are authoritative.

  device_scale_factor_ =
      GetCustomScreenDipScale();

  bounds_ =
      GetCustomScreenBounds();

  size_in_pixels_ =
      GetCustomScreenSize();

  native_origin_ =
      GetCustomScreenBounds().origin();

  work_area_ =
      GetCustomScreenWorkArea();

  pixels_per_inch_x_ =
      GetCustomScreenXdpiX();

  pixels_per_inch_y_ =
      GetCustomScreenXdpiY();
}

// -----------------------------------------------------------------------------
// Scale
// -----------------------------------------------------------------------------

void Display::SetScale(
    float device_scale_factor) {

  device_scale_factor_ =
      GetCustomScreenDipScale();
}

// -----------------------------------------------------------------------------
// Size
// -----------------------------------------------------------------------------

void Display::SetSize(
    const gfx::Size& size_in_pixel) {

  size_in_pixels_ =
      GetCustomScreenSize();

  bounds_ =
      GetCustomScreenBounds();

  work_area_ =
      GetCustomScreenWorkArea();

  native_origin_ =
      GetCustomScreenBounds().origin();

  device_scale_factor_ =
      GetCustomScreenDipScale();
}

// -----------------------------------------------------------------------------
// Work area update
// -----------------------------------------------------------------------------

void Display::UpdateWorkAreaFromInsets(
    const gfx::Insets& insets) {

  // Do not allow Chromium's calculated work area to replace the custom one.

  work_area_ =
      GetCustomScreenWorkArea();
}

// -----------------------------------------------------------------------------
// Pixel size
// -----------------------------------------------------------------------------

gfx::Size Display::GetSizeInPixel() const {
  return GetCustomScreenSize();
}

// -----------------------------------------------------------------------------
// String representation
// -----------------------------------------------------------------------------

std::string Display::ToString() const {
  return base::StringPrintf(
      "Display[%lld] bounds=[%s], workarea=[%s], scale=%g, "
      "rotation=%s, panel_rotation=%s %s %s",

      static_cast<long long int>(id_),

      bounds_.ToString().c_str(),

      work_area_.ToString().c_str(),

      GetCustomScreenDipScale(),

      ToRotationString(rotation_),

      ToRotationString(panel_rotation()),

      IsInternal() ? "internal" : "external",

      detected() ? "detected" : "not-detected");
}

// -----------------------------------------------------------------------------
// Internal display
// -----------------------------------------------------------------------------

bool Display::IsInternal() const {
  return is_valid() &&
         display::IsInternalDisplayId(id_);
}

// static
int64_t Display::InternalDisplayId() {
  auto& ids =
      GetInternalDisplayIds();

  DCHECK_EQ(1u, ids.size());

  return ids.size()
             ? *ids.begin()
             : kInvalidDisplayId;
}

// -----------------------------------------------------------------------------
// Comparison
// -----------------------------------------------------------------------------

bool Display::operator==(
    const Display& rhs) const {
  return EqualExceptForHdrHeadroom(*this, rhs) &&
         (color_spaces_ == rhs.color_spaces_ ||
          GetColorSpaces() == rhs.GetColorSpaces());
}

// static
bool Display::EqualExceptForHdrHeadroom(
    const Display& lhs,
    const Display& rhs) {

  return lhs.id_ == rhs.id_ &&
         lhs.bounds_ == rhs.bounds_ &&
         lhs.size_in_pixels_ == rhs.size_in_pixels_ &&
         lhs.native_origin_ == rhs.native_origin_ &&
         lhs.detected_ == rhs.detected_ &&
         lhs.work_area_ == rhs.work_area_ &&
         lhs.device_scale_factor_ ==
             rhs.device_scale_factor_ &&
         lhs.text_scale_multiplier_ ==
             rhs.text_scale_multiplier_ &&
         lhs.rotation_ == rhs.rotation_ &&
         lhs.touch_support_ ==
             rhs.touch_support_ &&
         lhs.accelerometer_support_ ==
             rhs.accelerometer_support_ &&
         lhs.maximum_cursor_size_ ==
             rhs.maximum_cursor_size_ &&
         (lhs.color_spaces_ ==
              rhs.color_spaces_ ||
          gfx::DisplayColorSpaces::
              EqualExceptForHdrHeadroom(
                  lhs.GetColorSpaces(),
                  rhs.GetColorSpaces())) &&
         lhs.color_depth_ ==
             rhs.color_depth_ &&
         lhs.depth_per_component_ ==
             rhs.depth_per_component_ &&
         lhs.is_monochrome_ ==
             rhs.is_monochrome_ &&
         lhs.display_frequency_ ==
             rhs.display_frequency_ &&
         lhs.label_ == rhs.label_;
}

// -----------------------------------------------------------------------------
// Color spaces
// -----------------------------------------------------------------------------

void Display::SetDisplayColorSpacesRef(
    scoped_refptr<const gfx::DisplayColorSpacesRef>
        color_spaces) {

  color_spaces_ =
      std::move(color_spaces);

  if (color_spaces_->color_spaces().SupportsHDR()) {
    color_depth_ =
        kHDR10BitsPerPixel;

    depth_per_component_ =
        kHDR10BitsPerComponent;
  } else {
    color_depth_ =
        kDefaultBitsPerPixel;

    depth_per_component_ =
        kDefaultBitsPerComponent;
  }
}

scoped_refptr<const gfx::DisplayColorSpacesRef>
Display::GetDefaultDisplayColorSpacesRef() {

  static const base::NoDestructor<
      scoped_refptr<
          const gfx::DisplayColorSpacesRef>>
      default_color_spaces_ref([] {

        auto color_space =
            gfx::ColorSpace::CreateSRGB();

#if !BUILDFLAG(IS_ANDROID)
        if (HasForceDisplayColorProfile()) {
          color_space =
              GetForcedDisplayColorProfile();
        }
#endif

        return base::MakeRefCounted<
            gfx::DisplayColorSpacesRef>(
            gfx::DisplayColorSpaces(
                color_space));

      }());

  return *default_color_spaces_ref;
}

// -----------------------------------------------------------------------------
// Bounds
// -----------------------------------------------------------------------------

const gfx::Rect& Display::bounds() const {
  return bounds_;
}

void Display::set_bounds(
    const gfx::Rect& bounds) {

  // Ignore the supplied value.

  bounds_ =
      GetCustomScreenBounds();

  native_origin_ =
      GetCustomScreenBounds().origin();

  size_in_pixels_ =
      GetCustomScreenSize();

  work_area_ =
      GetCustomScreenWorkArea();

  device_scale_factor_ =
      GetCustomScreenDipScale();
}

// -----------------------------------------------------------------------------
// Work area
// -----------------------------------------------------------------------------

const gfx::Rect& Display::work_area() const {
  return work_area_;
}

void Display::set_work_area(
    const gfx::Rect& work_area) {

  // Ignore the supplied value.

  work_area_ =
      GetCustomScreenWorkArea();
}

// -----------------------------------------------------------------------------
// Size in pixels
// -----------------------------------------------------------------------------

void Display::set_size_in_pixels(
    const gfx::Size& size) {

  // Ignore the supplied value.

  size_in_pixels_ =
      GetCustomScreenSize();
}

// -----------------------------------------------------------------------------
// Device scale factor
// -----------------------------------------------------------------------------

float Display::device_scale_factor() const {
  return GetCustomScreenDipScale();
}

void Display::set_device_scale_factor(
    float scale) {

  // Ignore the supplied value.

  device_scale_factor_ =
      GetCustomScreenDipScale();
}

// -----------------------------------------------------------------------------
// Pixels per inch
// -----------------------------------------------------------------------------

void Display::set_pixels_per_inch(
    float pixels_per_inch_x,
    float pixels_per_inch_y) {

  // Ignore the supplied values.

  pixels_per_inch_x_ =
      GetCustomScreenXdpiX();

  pixels_per_inch_y_ =
      GetCustomScreenXdpiY();
}

float Display::GetPixelsPerInchX() const {
  return GetCustomScreenXdpiX();
}

float Display::GetPixelsPerInchY() const {
  return GetCustomScreenXdpiY();
}

// -----------------------------------------------------------------------------
// Text scale
// -----------------------------------------------------------------------------

float Display::text_scale_multiplier() const {
  return text_scale_multiplier_;
}

void Display::set_text_scale_multiplier(
    float scale) {

  // Keep Chromium's normal text scale behavior.

  text_scale_multiplier_ =
      scale;
}

// -----------------------------------------------------------------------------
// Native origin
// -----------------------------------------------------------------------------

gfx::Point Display::native_origin() const {
  return native_origin_;
}

void Display::set_native_origin(
    const gfx::Point& native_origin) {

  // Custom bounds determine the native origin.

  native_origin_ =
      GetCustomScreenBounds().origin();
}

}  // namespace display