// Copyright 2016 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 <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display.h"
#include "ui/display/display_layout.h"
#include "ui/display/mojo/display_layout_struct_traits.h"
#include "ui/display/mojo/display_mode_struct_traits.h"
#include "ui/display/mojo/display_snapshot_struct_traits.h"
#include "ui/display/mojo/display_struct_traits.h"
#include "ui/display/mojo/gamma_ramp_rgb_entry_struct_traits.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/gamma_ramp_rgb_entry.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

namespace display {
namespace {

constexpr int64_t kDisplayId1 = 123;
constexpr int64_t kDisplayId2 = 456;
constexpr int64_t kDisplayId3 = 789;

void CheckDisplaysEqual(const Display& input, const Display& output) {
  EXPECT_NE(&input, &output);  // Make sure they aren't the same object.
  EXPECT_EQ(input.id(), output.id());
  EXPECT_EQ(input.bounds(), output.bounds());
  EXPECT_EQ(input.GetSizeInPixel(), output.GetSizeInPixel());
  EXPECT_EQ(input.work_area(), output.work_area());
  EXPECT_EQ(input.device_scale_factor(), output.device_scale_factor());
  EXPECT_EQ(input.rotation(), output.rotation());
  EXPECT_EQ(input.touch_support(), output.touch_support());
  EXPECT_EQ(input.accelerometer_support(), output.accelerometer_support());
  EXPECT_EQ(input.maximum_cursor_size(), output.maximum_cursor_size());
  EXPECT_EQ(input.color_depth(), output.color_depth());
  EXPECT_EQ(input.depth_per_component(), output.depth_per_component());
  EXPECT_EQ(input.is_monochrome(), output.is_monochrome());
}

void CheckDisplayLayoutsEqual(const DisplayLayout& input,
                              const DisplayLayout& output) {
  EXPECT_NE(&input, &output);  // Make sure they aren't the same object.
  EXPECT_EQ(input.placement_list, output.placement_list);
  EXPECT_EQ(input.default_unified, output.default_unified);
  EXPECT_EQ(input.primary_id, output.primary_id);
}

void CheckDisplayModesEqual(const DisplayMode* input,
                            const DisplayMode* output) {
  // DisplaySnapshot can have null DisplayModes, so if |input| is null then
  // |output| should be null too.
  if (input == nullptr && output == nullptr)
    return;

  EXPECT_NE(input, output);  // Make sure they aren't the same object.
  EXPECT_EQ(input->size(), output->size());
  EXPECT_EQ(input->is_interlaced(), output->is_interlaced());
  EXPECT_EQ(input->refresh_rate(), output->refresh_rate());
}

void CheckDisplaySnapShotMojoEqual(const DisplaySnapshot& input,
                                   const DisplaySnapshot& output) {
  // We want to test each component individually to make sure each data member
  // was correctly serialized and deserialized.
  EXPECT_NE(&input, &output);  // Make sure they aren't the same object.
  EXPECT_EQ(input.display_id(), output.display_id());
  EXPECT_EQ(input.origin(), output.origin());
  EXPECT_EQ(input.physical_size(), output.physical_size());
  EXPECT_EQ(input.type(), output.type());
  EXPECT_EQ(input.is_aspect_preserving_scaling(),
            output.is_aspect_preserving_scaling());
  EXPECT_EQ(input.has_overscan(), output.has_overscan());
  EXPECT_EQ(input.has_color_correction_matrix(),
            output.has_color_correction_matrix());
  EXPECT_EQ(input.display_name(), output.display_name());
  EXPECT_EQ(input.sys_path(), output.sys_path());
  EXPECT_EQ(input.product_id(), output.product_id());
  EXPECT_EQ(input.modes().size(), output.modes().size());

  for (size_t i = 0; i < input.modes().size(); i++)
    CheckDisplayModesEqual(input.modes()[i].get(), output.modes()[i].get());

  EXPECT_EQ(input.edid(), output.edid());

  CheckDisplayModesEqual(input.current_mode(), output.current_mode());
  CheckDisplayModesEqual(input.native_mode(), output.native_mode());

  EXPECT_EQ(input.maximum_cursor_size(), output.maximum_cursor_size());
}

// Test StructTrait serialization and deserialization for copyable type. |input|
// will be serialized and then deserialized into |output|.
template <class MojomType, class Type>
void SerializeAndDeserialize(const Type& input, Type* output) {
  MojomType::Deserialize(MojomType::Serialize(&input), output);
}

// Test StructTrait serialization and deserialization for move only type.
// |input| will be serialized and then deserialized into |output|.
template <class MojomType, class Type>
void SerializeAndDeserialize(Type&& input, Type* output) {
  MojomType::Deserialize(MojomType::Serialize(&input), output);
}
}  // namespace

TEST(DisplayStructTraitsTest, DefaultDisplayValues) {
  Display input(5);

  Display output;
  SerializeAndDeserialize<mojom::Display>(input, &output);

  CheckDisplaysEqual(input, output);
}

TEST(DisplayStructTraitsTest, SetAllDisplayValues) {
  const gfx::Rect bounds(100, 200, 500, 600);
  const gfx::Rect work_area(150, 250, 400, 500);
  const gfx::Size maximum_cursor_size(64, 64);

  Display input(246345234, bounds);
  input.set_work_area(work_area);
  input.set_device_scale_factor(2.0f);
  input.set_rotation(Display::ROTATE_270);
  input.set_touch_support(Display::TOUCH_SUPPORT_AVAILABLE);
  input.set_accelerometer_support(Display::ACCELEROMETER_SUPPORT_UNAVAILABLE);
  input.set_maximum_cursor_size(maximum_cursor_size);
  input.set_color_depth(input.color_depth() + 1);
  input.set_depth_per_component(input.depth_per_component() + 1);
  input.set_is_monochrome(!input.is_monochrome());

  Display output;
  SerializeAndDeserialize<mojom::Display>(input, &output);

  CheckDisplaysEqual(input, output);
}

TEST(DisplayStructTraitsTest, DefaultDisplayMode) {
  std::unique_ptr<DisplayMode> input =
      std::make_unique<DisplayMode>(gfx::Size(1024, 768), true, 61.0);

  std::unique_ptr<DisplayMode> output;
  SerializeAndDeserialize<mojom::DisplayMode>(input->Clone(), &output);

  CheckDisplayModesEqual(input.get(), output.get());
}

TEST(DisplayStructTraitsTest, DisplayPlacementFlushAtTop) {
  DisplayPlacement input;
  input.display_id = kDisplayId1;
  input.parent_display_id = kDisplayId2;
  input.position = DisplayPlacement::TOP;
  input.offset = 0;
  input.offset_reference = DisplayPlacement::TOP_LEFT;

  DisplayPlacement output;
  SerializeAndDeserialize<mojom::DisplayPlacement>(input, &output);

  EXPECT_EQ(input, output);
}

TEST(DisplayStructTraitsTest, DisplayPlacementWithOffset) {
  DisplayPlacement input;
  input.display_id = kDisplayId1;
  input.parent_display_id = kDisplayId2;
  input.position = DisplayPlacement::BOTTOM;
  input.offset = -100;
  input.offset_reference = DisplayPlacement::BOTTOM_RIGHT;

  DisplayPlacement output;
  SerializeAndDeserialize<mojom::DisplayPlacement>(input, &output);

  EXPECT_EQ(input, output);
}

TEST(DisplayStructTraitsTest, DisplayLayoutTwoExtended) {
  DisplayPlacement placement;
  placement.display_id = kDisplayId1;
  placement.parent_display_id = kDisplayId2;
  placement.position = DisplayPlacement::RIGHT;
  placement.offset = 0;
  placement.offset_reference = DisplayPlacement::TOP_LEFT;

  auto input = std::make_unique<DisplayLayout>();
  input->placement_list.push_back(placement);
  input->primary_id = kDisplayId2;
  input->default_unified = true;

  std::unique_ptr<DisplayLayout> output;
  SerializeAndDeserialize<mojom::DisplayLayout>(input->Copy(), &output);

  CheckDisplayLayoutsEqual(*input, *output);
}

TEST(DisplayStructTraitsTest, DisplayLayoutThreeExtended) {
  DisplayPlacement placement1;
  placement1.display_id = kDisplayId2;
  placement1.parent_display_id = kDisplayId1;
  placement1.position = DisplayPlacement::LEFT;
  placement1.offset = 0;
  placement1.offset_reference = DisplayPlacement::TOP_LEFT;

  DisplayPlacement placement2;
  placement2.display_id = kDisplayId3;
  placement2.parent_display_id = kDisplayId1;
  placement2.position = DisplayPlacement::RIGHT;
  placement2.offset = -100;
  placement2.offset_reference = DisplayPlacement::BOTTOM_RIGHT;

  auto input = std::make_unique<DisplayLayout>();
  input->placement_list.push_back(placement1);
  input->placement_list.push_back(placement2);
  input->primary_id = kDisplayId1;
  input->default_unified = false;

  std::unique_ptr<DisplayLayout> output;
  SerializeAndDeserialize<mojom::DisplayLayout>(input->Copy(), &output);

  CheckDisplayLayoutsEqual(*input, *output);
}

TEST(DisplayStructTraitsTest, BasicGammaRampRGBEntry) {
  const GammaRampRGBEntry input{259, 81, 16};

  GammaRampRGBEntry output;
  SerializeAndDeserialize<mojom::GammaRampRGBEntry>(input, &output);

  EXPECT_EQ(input.r, output.r);
  EXPECT_EQ(input.g, output.g);
  EXPECT_EQ(input.b, output.b);
}

// One display mode, current and native mode nullptr.
TEST(DisplayStructTraitsTest, DisplaySnapshotCurrentAndNativeModesNull) {
  // Prepare sample input with random values.
  const int64_t display_id = 7;
  const gfx::Point origin(1, 2);
  const gfx::Size physical_size(5, 9);
  const gfx::Size maximum_cursor_size(3, 5);
  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_DISPLAYPORT;
  const bool is_aspect_preserving_scaling = true;
  const bool has_overscan = true;
  const bool has_color_correction_matrix = true;
  const gfx::ColorSpace display_color_space = gfx::ColorSpace::CreateREC709();
  const std::string display_name("whatever display_name");
  const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("a/cb");
  const int64_t product_id = 19;

  const DisplayMode display_mode(gfx::Size(13, 11), true, 40.0f);

  DisplaySnapshot::DisplayModeList modes;
  modes.push_back(display_mode.Clone());

  const DisplayMode* current_mode = nullptr;
  const DisplayMode* native_mode = nullptr;
  const std::vector<uint8_t> edid = {1};

  std::unique_ptr<DisplaySnapshot> input = std::make_unique<DisplaySnapshot>(
      display_id, origin, physical_size, type, is_aspect_preserving_scaling,
      has_overscan, has_color_correction_matrix, display_color_space,
      display_name, sys_path, std::move(modes), edid, current_mode, native_mode,
      product_id, maximum_cursor_size);

  std::unique_ptr<DisplaySnapshot> output;
  SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);

  CheckDisplaySnapShotMojoEqual(*input, *output);
}

// One display mode that is the native mode and no current mode.
TEST(DisplayStructTraitsTest, DisplaySnapshotCurrentModeNull) {
  // Prepare sample input with random values.
  const int64_t display_id = 6;
  const gfx::Point origin(11, 32);
  const gfx::Size physical_size(55, 49);
  const gfx::Size maximum_cursor_size(13, 95);
  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_VGA;
  const bool is_aspect_preserving_scaling = true;
  const bool has_overscan = true;
  const bool has_color_correction_matrix = true;
  const gfx::ColorSpace display_color_space = gfx::ColorSpace::CreateREC709();
  const std::string display_name("whatever display_name");
  const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("z/b");
  const int64_t product_id = 9;

  const DisplayMode display_mode(gfx::Size(13, 11), true, 50.0f);

  DisplaySnapshot::DisplayModeList modes;
  modes.push_back(display_mode.Clone());

  const DisplayMode* current_mode = nullptr;
  const DisplayMode* native_mode = modes[0].get();
  const std::vector<uint8_t> edid = {1};

  std::unique_ptr<DisplaySnapshot> input = std::make_unique<DisplaySnapshot>(
      display_id, origin, physical_size, type, is_aspect_preserving_scaling,
      has_overscan, has_color_correction_matrix, display_color_space,
      display_name, sys_path, std::move(modes), edid, current_mode, native_mode,
      product_id, maximum_cursor_size);

  std::unique_ptr<DisplaySnapshot> output;
  SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);

  CheckDisplaySnapShotMojoEqual(*input, *output);
}

// Multiple display modes, both native and current mode set.
TEST(DisplayStructTraitsTest, DisplaySnapshotExternal) {
  // Prepare sample input from external display.
  const int64_t display_id = 9834293210466051;
  const gfx::Point origin(0, 1760);
  const gfx::Size physical_size(520, 320);
  const gfx::Size maximum_cursor_size(4, 5);
  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_HDMI;
  const bool is_aspect_preserving_scaling = false;
  const bool has_overscan = false;
  const bool has_color_correction_matrix = false;
  const std::string display_name("HP Z24i");
  const gfx::ColorSpace display_color_space = gfx::ColorSpace::CreateSRGB();
  const base::FilePath sys_path = base::FilePath::FromUTF8Unsafe("a/cb");
  const int64_t product_id = 139;

  const DisplayMode display_mode(gfx::Size(1024, 768), false, 60.0f);
  const DisplayMode display_current_mode(gfx::Size(1440, 900), false, 59.89f);
  const DisplayMode display_native_mode(gfx::Size(1920, 1200), false, 59.89f);

  DisplaySnapshot::DisplayModeList modes;
  modes.push_back(display_mode.Clone());
  modes.push_back(display_current_mode.Clone());
  modes.push_back(display_native_mode.Clone());

  const DisplayMode* current_mode = modes[1].get();
  const DisplayMode* native_mode = modes[2].get();
  const std::vector<uint8_t> edid = {2, 3, 4, 5};

  std::unique_ptr<DisplaySnapshot> input = std::make_unique<DisplaySnapshot>(
      display_id, origin, physical_size, type, is_aspect_preserving_scaling,
      has_overscan, has_color_correction_matrix, display_color_space,
      display_name, sys_path, std::move(modes), edid, current_mode, native_mode,
      product_id, maximum_cursor_size);

  std::unique_ptr<DisplaySnapshot> output;
  SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);

  CheckDisplaySnapShotMojoEqual(*input, *output);
}

TEST(DisplayStructTraitsTest, DisplaySnapshotInternal) {
  // Prepare sample input from Pixel's internal display.
  const int64_t display_id = 13761487533244416;
  const gfx::Point origin(0, 0);
  const gfx::Size physical_size(270, 180);
  const gfx::Size maximum_cursor_size(64, 64);
  const DisplayConnectionType type = DISPLAY_CONNECTION_TYPE_INTERNAL;
  const bool is_aspect_preserving_scaling = true;
  const bool has_overscan = false;
  const bool has_color_correction_matrix = false;
  const gfx::ColorSpace display_color_space =
      gfx::ColorSpace::CreateDisplayP3D65();
  const std::string display_name("");
  const base::FilePath sys_path;
  const int64_t product_id = 139;

  const DisplayMode display_mode(gfx::Size(2560, 1700), false, 95.96f);

  DisplaySnapshot::DisplayModeList modes;
  modes.push_back(display_mode.Clone());

  const DisplayMode* current_mode = modes[0].get();
  const DisplayMode* native_mode = modes[0].get();
  const std::vector<uint8_t> edid = {2, 3};

  std::unique_ptr<DisplaySnapshot> input = std::make_unique<DisplaySnapshot>(
      display_id, origin, physical_size, type, is_aspect_preserving_scaling,
      has_overscan, has_color_correction_matrix, display_color_space,
      display_name, sys_path, std::move(modes), edid, current_mode, native_mode,
      product_id, maximum_cursor_size);

  std::unique_ptr<DisplaySnapshot> output;
  SerializeAndDeserialize<mojom::DisplaySnapshot>(input->Clone(), &output);

  CheckDisplaySnapShotMojoEqual(*input, *output);
}

}  // namespace display
