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

#include "chrome/browser/ui/views/tabs/tab_strip_layout.h"

#include <stddef.h>
#include <string>

#include "base/strings/string_number_conversions.h"
#include "chrome/browser/ui/tabs/tab_types.h"
#include "chrome/browser/ui/views/tabs/tab_layout_state.h"
#include "chrome/browser/ui/views/tabs/tab_width_constraints.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"

namespace {

// Returns a string with the width of each gfx::Rect in |tab_bounds|, separated
// by spaces.
std::string TabWidthsAsString(const std::vector<gfx::Rect>& tab_bounds) {
  std::string result;
  for (const auto& bounds : tab_bounds) {
    if (!result.empty())
      result += " ";
    result += base::NumberToString(bounds.width());
  }
  return result;
}

// Returns a string with the x-coordinate of each gfx::Rect in |tab_bounds|,
// separated by spaces.
std::string TabXPositionsAsString(const std::vector<gfx::Rect>& tab_bounds) {
  std::string result;
  for (const auto& bounds : tab_bounds) {
    if (!result.empty())
      result += " ";
    result += base::NumberToString(bounds.x());
  }
  return result;
}

struct TestCase {
  int num_pinned_tabs = 0;
  int num_tabs = 0;
  int active_index = 0;
  int tabstrip_width = 0;
};

constexpr int kStandardWidth = 100;
constexpr int kTabHeight = 10;
constexpr int kMinActiveWidth = 20;
constexpr int kMinInactiveWidth = 14;
constexpr int kPinnedWidth = 10;
constexpr int kTabOverlap = 4;

std::vector<gfx::Rect> CalculateTabBounds(TestCase test_case) {
  TabLayoutConstants layout_constants;
  layout_constants.tab_height = kTabHeight;
  layout_constants.tab_overlap = kTabOverlap;

  TabSizeInfo size_info;
  size_info.pinned_tab_width = kPinnedWidth;
  size_info.min_active_width = kMinActiveWidth;
  size_info.min_inactive_width = kMinInactiveWidth;
  size_info.standard_width = kStandardWidth;

  std::vector<TabWidthConstraints> tab_states;
  for (int tab_index = 0; tab_index < test_case.num_tabs; tab_index++) {
    TabLayoutState ideal_animation_state = TabLayoutState(
        TabOpen::kOpen,
        tab_index < test_case.num_pinned_tabs ? TabPinned::kPinned
                                              : TabPinned::kUnpinned,
        tab_index == test_case.active_index ? TabActive::kActive
                                            : TabActive::kInactive);
    tab_states.emplace_back(ideal_animation_state, layout_constants, size_info);
  }

  return CalculateTabBounds(layout_constants, tab_states,
                            test_case.tabstrip_width);
}

}  // namespace

// These tests verify that layout behaves correctly in various situations. In
// particular we want layout to adhere to the following constraints:
// * Tabs are the standard size given by TabSizeInfo when there's room.
// * Tabs are never smaller than the minimum sizes given by TabSizeInfo, even if
//   there isn't enough room.
// * Pinned tabs are always the width given by TabSizeInfo.
// * Remainder pixels (leftover when the available width is distributed evenly)
//   are distributed from left to right.
// * And otherwise tabs shrink to fit the available width.

TEST(TabStripLayoutTest, Basics) {
  TestCase test_case;
  test_case.tabstrip_width = 1000;
  test_case.num_tabs = 3;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("100 100 100", TabWidthsAsString(bounds));
  EXPECT_EQ("0 96 192", TabXPositionsAsString(bounds));
  for (const auto& b : bounds) {
    EXPECT_EQ(0, b.y());
    EXPECT_EQ(kTabHeight, b.height());
  }
}

TEST(TabStripLayoutTest, AllPinnedTabs) {
  TestCase test_case;
  test_case.tabstrip_width = 1000;
  test_case.num_pinned_tabs = test_case.num_tabs = 3;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("10 10 10", TabWidthsAsString(bounds));
  EXPECT_EQ("0 6 12", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, MixedPinnedAndNormalTabs) {
  TestCase test_case;
  test_case.tabstrip_width = 1000;
  test_case.num_tabs = 3;
  test_case.num_pinned_tabs = 1;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("10 100 100", TabWidthsAsString(bounds));
  EXPECT_EQ("0 6 102", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, MiddleWidth) {
  TestCase test_case;
  test_case.tabstrip_width = 100;
  test_case.num_tabs = 4;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("28 28 28 28", TabWidthsAsString(bounds));
  EXPECT_EQ("0 24 48 72", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, MiddleWidthAndPinnedTab) {
  TestCase test_case;
  test_case.tabstrip_width = 100;
  test_case.num_tabs = 3;
  test_case.num_pinned_tabs = 1;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("10 49 49", TabWidthsAsString(bounds));
  EXPECT_EQ("0 6 51", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, MiddleWidthRounded) {
  TestCase test_case;
  test_case.tabstrip_width = 102;
  test_case.num_tabs = 4;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("29 29 28 28", TabWidthsAsString(bounds));
  EXPECT_EQ("0 25 50 74", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, MiddleWidthRoundedAndPinnedTab) {
  TestCase test_case;
  test_case.tabstrip_width = 101;
  test_case.num_tabs = 3;
  test_case.num_pinned_tabs = 1;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("10 50 49", TabWidthsAsString(bounds));
  EXPECT_EQ("0 6 52", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, BelowMinActiveWidthOneTab) {
  TestCase test_case;
  test_case.tabstrip_width = 15;
  test_case.num_tabs = 1;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("20", TabWidthsAsString(bounds));
  EXPECT_EQ("0", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, BelowMinActiveWidth) {
  TestCase test_case;
  test_case.tabstrip_width = 90;
  test_case.num_tabs = 6;
  test_case.active_index = 3;

  auto bounds = CalculateTabBounds(test_case);
  EXPECT_EQ("18 18 18 20 18 18", TabWidthsAsString(bounds));
  EXPECT_EQ("0 14 28 42 58 72", TabXPositionsAsString(bounds));
}

TEST(TabStripLayoutTest, BelowMinActiveWidthRounded) {
  TestCase test_case;
  test_case.tabstrip_width = 93;
  test_case.num_tabs = 6;
  test_case.active_index = 3;

  EXPECT_EQ("19 19 19 20 18 18",
            TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, BelowMinActiveWidthActivePinnedTab) {
  TestCase test_case;
  test_case.tabstrip_width = 85;
  test_case.num_tabs = 6;
  test_case.num_pinned_tabs = 1;

  EXPECT_EQ("10 19 19 19 19 19",
            TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, BelowMinActiveWidthInactivePinnedTab) {
  TestCase test_case;
  test_case.tabstrip_width = 82;
  test_case.num_tabs = 6;
  test_case.num_pinned_tabs = 1;
  test_case.active_index = 2;

  EXPECT_EQ("10 18 20 18 18 18",
            TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, BelowMinActiveWidthActivePinnedTabRounded) {
  TestCase test_case;
  test_case.tabstrip_width = 86;
  test_case.num_tabs = 6;
  test_case.num_pinned_tabs = 1;

  EXPECT_EQ("10 20 19 19 19 19",
            TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, NotEnoughSpace) {
  TestCase test_case;
  test_case.tabstrip_width = 10;
  test_case.num_tabs = 3;

  EXPECT_EQ("20 14 14", TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, NotEnoughSpaceAllPinnedTabs) {
  TestCase test_case;
  test_case.tabstrip_width = 10;
  test_case.num_tabs = 3;
  test_case.num_pinned_tabs = 3;

  EXPECT_EQ("10 10 10", TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, NotEnoughSpaceMixedPinnedAndNormalTabs) {
  TestCase test_case;
  test_case.tabstrip_width = 10;
  test_case.num_tabs = 3;
  test_case.num_pinned_tabs = 1;

  EXPECT_EQ("10 14 14", TabWidthsAsString(CalculateTabBounds(test_case)));
}

TEST(TabStripLayoutTest, ExactlyEnoughSpaceAllPinnedTabs) {
  TestCase test_case;
  test_case.num_tabs = 2;
  test_case.num_pinned_tabs = 2;
  test_case.tabstrip_width = 2 * kPinnedWidth - kTabOverlap;

  // We want to check the case where the necessary strip width equals the
  // available width.
  auto bounds = CalculateTabBounds(test_case);

  EXPECT_EQ("10 10", TabWidthsAsString(bounds));

  // Validate that the tabstrip width is indeeed exactly enough to hold two
  // pinned tabs.
  EXPECT_EQ(test_case.tabstrip_width, bounds[1].right());
}
