// Copyright (c) 2011 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 "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h"

#include <gtk/gtk.h>

#include "base/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "testing/platform_test.h"

namespace {

static const float kLargeWidth = 10000;

const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80);
const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);

}  // namespace

class AutocompletePopupViewGtkTest : public PlatformTest {
 public:
  AutocompletePopupViewGtkTest() { }

  virtual void SetUp() {
    PlatformTest::SetUp();

    window_ = gtk_window_new(GTK_WINDOW_POPUP);
    layout_ = gtk_widget_create_pango_layout(window_, NULL);
  }

  virtual void TearDown() {
    g_object_unref(layout_);
    gtk_widget_destroy(window_);

    PlatformTest::TearDown();
  }

  // The google C++ Testing Framework documentation suggests making
  // accessors in the fixture so that each test doesn't need to be a
  // friend of the class being tested.  This method just proxies the
  // call through after adding the fixture's layout_.
  void SetupLayoutForMatch(
      const string16& text,
      const AutocompleteMatch::ACMatchClassifications& classifications,
      const GdkColor* base_color,
      const GdkColor* dim_color,
      const GdkColor* url_color,
      const std::string& prefix_text) {
    AutocompletePopupViewGtk::SetupLayoutForMatch(layout_,
                                                  text,
                                                  classifications,
                                                  base_color,
                                                  dim_color,
                                                  url_color,
                                                  prefix_text);
  }

  struct RunInfo {
    PangoAttribute* attr_;
    guint length_;
    RunInfo() : attr_(NULL), length_(0) { }
  };

  RunInfo RunInfoForAttrType(guint location,
                             guint end_location,
                             PangoAttrType type) {
    RunInfo retval;

    PangoAttrList* attrs = pango_layout_get_attributes(layout_);
    if (!attrs)
      return retval;

    PangoAttrIterator* attr_iter = pango_attr_list_get_iterator(attrs);
    if (!attr_iter)
      return retval;

    for (gboolean more = true, findNextStart = false;
        more;
        more = pango_attr_iterator_next(attr_iter)) {
      PangoAttribute* attr = pango_attr_iterator_get(attr_iter, type);

      // This iterator segment doesn't have any elements of the
      // desired type; keep looking.
      if (!attr)
        continue;

      // Skip attribute ranges before the desired start point.
      if (attr->end_index <= location)
        continue;

      // If the matching type went past the iterator segment, then set
      // the length to the next start - location.
      if (findNextStart) {
        // If the start is still less than the location, then reset
        // the match.  Otherwise, check that the new attribute is, in
        // fact different before shortening the run length.
        if (attr->start_index <= location) {
          findNextStart = false;
        } else if (!pango_attribute_equal(retval.attr_, attr)) {
          retval.length_ = attr->start_index - location;
          break;
        }
      }

      gint start_range, end_range;
      pango_attr_iterator_range(attr_iter,
                                &start_range,
                                &end_range);

      // Now we have a match.  May need to keep going to shorten
      // length if we reach a new item of the same type.
      retval.attr_ = attr;
      if (attr->end_index > (guint)end_range) {
        retval.length_ = end_location - location;
        findNextStart = true;
      } else {
        retval.length_ = attr->end_index - location;
        break;
      }
    }

    pango_attr_iterator_destroy(attr_iter);
    return retval;
  }

  guint RunLengthForAttrType(guint location,
                            guint end_location,
                            PangoAttrType type) {
    RunInfo info = RunInfoForAttrType(location,
                                      end_location,
                                      type);
    return info.length_;
  }

  gboolean RunHasAttribute(guint location,
                           guint end_location,
                           PangoAttribute* attribute) {
    RunInfo info = RunInfoForAttrType(location,
                                      end_location,
                                      attribute->klass->type);

    return info.attr_ && pango_attribute_equal(info.attr_, attribute);
  }

  gboolean RunHasColor(guint location,
                       guint end_location,
                       const GdkColor& color) {
    PangoAttribute* attribute =
        pango_attr_foreground_new(color.red,
                                  color.green,
                                  color.blue);

    gboolean retval = RunHasAttribute(location,
                                      end_location,
                                      attribute);

    pango_attribute_destroy(attribute);

    return retval;
  }

  gboolean RunHasWeight(guint location,
                        guint end_location,
                        PangoWeight weight) {
    PangoAttribute* attribute = pango_attr_weight_new(weight);

    gboolean retval = RunHasAttribute(location,
                                      end_location,
                                      attribute);

    pango_attribute_destroy(attribute);

    return retval;
  }

  GtkWidget* window_;
  PangoLayout* layout_;

 private:
  DISALLOW_COPY_AND_ASSIGN(AutocompletePopupViewGtkTest);
};

// Simple inputs with no matches should result in styled output who's
// text matches the input string, with the passed-in color, and
// nothing bolded.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringNoMatch) {
  const string16 kContents = ASCIIToUTF16("This is a test");

  AutocompleteMatch::ACMatchClassifications classifications;

  SetupLayoutForMatch(kContents,
                      classifications,
                      &kContentTextColor,
                      &kDimContentTextColor,
                      &kURLTextColor,
                      std::string());

  EXPECT_EQ(kContents.size(),
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));

  EXPECT_TRUE(RunHasColor(0U,
                          kContents.size(),
                          kContentTextColor));

  // This part's a little wacky - either we don't have a weight, or
  // the weight run is the entire string and is NORMAL
  guint weightLength = RunLengthForAttrType(0U,
                                           kContents.size(),
                                           PANGO_ATTR_WEIGHT);
  if (weightLength) {
    EXPECT_EQ(kContents.size(), weightLength);
    EXPECT_TRUE(RunHasWeight(0U,
                             kContents.size(),
                             PANGO_WEIGHT_NORMAL));
  }
}

// Identical to DecorateMatchedStringNoMatch, except test that URL
// style gets a different color than we passed in.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLNoMatch) {
  const string16 kContents = ASCIIToUTF16("This is a test");
  AutocompleteMatch::ACMatchClassifications classifications;

  classifications.push_back(
      ACMatchClassification(0U, ACMatchClassification::URL));

  SetupLayoutForMatch(kContents,
                      classifications,
                      &kContentTextColor,
                      &kDimContentTextColor,
                      &kURLTextColor,
                      std::string());

  EXPECT_EQ(kContents.size(),
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(0U,
                          kContents.size(),
                          kURLTextColor));

  // This part's a little wacky - either we don't have a weight, or
  // the weight run is the entire string and is NORMAL
  guint weightLength = RunLengthForAttrType(0U,
                                           kContents.size(),
                                           PANGO_ATTR_WEIGHT);
  if (weightLength) {
    EXPECT_EQ(kContents.size(), weightLength);
    EXPECT_TRUE(RunHasWeight(0U,
                             kContents.size(),
                             PANGO_WEIGHT_NORMAL));
  }
}

// Test that DIM works as expected.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringDimNoMatch) {
  const string16 kContents = ASCIIToUTF16("This is a test");
  // Dim "is".
  const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
  // Make sure nobody messed up the inputs.
  EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());

  // Push each run onto classifications.
  AutocompleteMatch::ACMatchClassifications classifications;
  classifications.push_back(
      ACMatchClassification(0U, ACMatchClassification::NONE));
  classifications.push_back(
      ACMatchClassification(runLength1, ACMatchClassification::DIM));
  classifications.push_back(
      ACMatchClassification(runLength1 + runLength2,
                            ACMatchClassification::NONE));

  SetupLayoutForMatch(kContents,
                      classifications,
                      &kContentTextColor,
                      &kDimContentTextColor,
                      &kURLTextColor,
                      std::string());

  // Check the runs have expected color and length.
  EXPECT_EQ(runLength1,
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(0U,
                          kContents.size(),
                          kContentTextColor));
  EXPECT_EQ(runLength2,
            RunLengthForAttrType(runLength1,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(runLength1,
                          kContents.size(),
                          kDimContentTextColor));
  EXPECT_EQ(runLength3,
            RunLengthForAttrType(runLength1 + runLength2,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(runLength1 + runLength2,
                          kContents.size(),
                          kContentTextColor));

  // This part's a little wacky - either we don't have a weight, or
  // the weight run is the entire string and is NORMAL
  guint weightLength = RunLengthForAttrType(0U,
                                           kContents.size(),
                                           PANGO_ATTR_WEIGHT);
  if (weightLength) {
    EXPECT_EQ(kContents.size(), weightLength);
    EXPECT_TRUE(RunHasWeight(0U,
                             kContents.size(),
                             PANGO_WEIGHT_NORMAL));
  }
}

// Test that the matched run gets bold-faced, but keeps the same
// color.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringMatch) {
  const string16 kContents = ASCIIToUTF16("This is a test");
  // Match "is".
  const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
  // Make sure nobody messed up the inputs.
  EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());

  // Push each run onto classifications.
  AutocompleteMatch::ACMatchClassifications classifications;
  classifications.push_back(
      ACMatchClassification(0U, ACMatchClassification::NONE));
  classifications.push_back(
      ACMatchClassification(runLength1, ACMatchClassification::MATCH));
  classifications.push_back(
      ACMatchClassification(runLength1 + runLength2,
                            ACMatchClassification::NONE));

  SetupLayoutForMatch(kContents,
                      classifications,
                      &kContentTextColor,
                      &kDimContentTextColor,
                      &kURLTextColor,
                      std::string());

  // Check the runs have expected weight and length.
  EXPECT_EQ(runLength1,
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_WEIGHT));
  EXPECT_TRUE(RunHasWeight(0U,
                          kContents.size(),
                          PANGO_WEIGHT_NORMAL));
  EXPECT_EQ(runLength2,
            RunLengthForAttrType(runLength1,
                                 kContents.size(),
                                 PANGO_ATTR_WEIGHT));
  EXPECT_TRUE(RunHasWeight(runLength1,
                          kContents.size(),
                          PANGO_WEIGHT_BOLD));
  EXPECT_EQ(runLength3,
            RunLengthForAttrType(runLength1 + runLength2,
                                 kContents.size(),
                                 PANGO_ATTR_WEIGHT));
  EXPECT_TRUE(RunHasWeight(runLength1 + runLength2,
                          kContents.size(),
                          PANGO_WEIGHT_NORMAL));

  // The entire string should be the same, normal color.
  EXPECT_EQ(kContents.size(),
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(0U,
                          kContents.size(),
                          kContentTextColor));
}

// Just like DecorateMatchedStringURLMatch, this time with URL style.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLMatch) {
  const string16 kContents = ASCIIToUTF16("http://hello.world/");
  // Match "hello".
  const guint runLength1 = 7, runLength2 = 5, runLength3 = 7;
  // Make sure nobody messed up the inputs.
  EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());

  // Push each run onto classifications.
  AutocompleteMatch::ACMatchClassifications classifications;
  classifications.push_back(
      ACMatchClassification(0U, ACMatchClassification::URL));
  const int kURLMatch =
      ACMatchClassification::URL | ACMatchClassification::MATCH;
  classifications.push_back(
      ACMatchClassification(runLength1,
                            kURLMatch));
  classifications.push_back(
      ACMatchClassification(runLength1 + runLength2,
                            ACMatchClassification::URL));

  SetupLayoutForMatch(kContents,
                      classifications,
                      &kContentTextColor,
                      &kDimContentTextColor,
                      &kURLTextColor,
                      std::string());

  // One color for the entire string, and it's not the one we passed
  // in.
  EXPECT_EQ(kContents.size(),
            RunLengthForAttrType(0U,
                                 kContents.size(),
                                 PANGO_ATTR_FOREGROUND));
  EXPECT_TRUE(RunHasColor(0U,
                          kContents.size(),
                          kURLTextColor));
}