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

#import <Cocoa/Cocoa.h>

#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/find_bar/find_bar.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/find_result_waiter.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "ui/base/cocoa/find_pasteboard.h"

const char kSimple[] = "simple.html";

GURL GetURL(const std::string& filename) {
  return chrome_test_utils::GetTestUrl(
      base::FilePath().AppendASCII("find_in_page"),
      base::FilePath().AppendASCII(filename));
}

int WaitForFind(content::WebContents* web_contents, int* ordinal) {
  ui_test_utils::FindResultWaiter observer(web_contents);
  observer.Wait();
  if (ordinal) {
    *ordinal = observer.active_match_ordinal();
  }
  return observer.number_of_matches();
}

class FindBarPlatformHelperMacTest : public InProcessBrowserTest {
 public:
  FindBarPlatformHelperMacTest() {}

  FindBarPlatformHelperMacTest(const FindBarPlatformHelperMacTest&) = delete;
  FindBarPlatformHelperMacTest& operator=(const FindBarPlatformHelperMacTest&) =
      delete;

  ~FindBarPlatformHelperMacTest() override = default;

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    old_find_text_ = [[FindPasteboard sharedInstance] findText];
  }

  void TearDownOnMainThread() override {
    [[FindPasteboard sharedInstance] setFindText:old_find_text_];
    InProcessBrowserTest::TearDownOnMainThread();
  }

 private:
  NSString* old_find_text_ = nullptr;
};

// Tests that the find bar is populated with the pasteboard at construction.
IN_PROC_BROWSER_TEST_F(FindBarPlatformHelperMacTest,
                       FindBarPopulatedWithPasteboardOnConstruction) {
  ASSERT_FALSE(browser()->GetFeatures().HasFindBarController());

  NSString* initial_find_string = @"Initial String";
  [[FindPasteboard sharedInstance] setFindText:initial_find_string];

  FindBarController* find_bar_controller =
      browser()->GetFeatures().GetFindBarController();
  ASSERT_NE(nullptr, find_bar_controller);

  EXPECT_EQ(base::SysNSStringToUTF16(initial_find_string),
            find_bar_controller->find_bar()->GetFindText());
}

// Tests that the find bar text and results are updated as the global find
// pasteboard updates. The following bug used to exist:
// 1) Find some text.
// 2) Switch to another app and find something there to put it in the global
//    find pasteboard.
// 3) Switch back to chrome, make sure no text is selected, and open the
//    findbar. The bug caused the match counts from the previous search to
//    remain in the findbar and the old find results to remain highlighted.
IN_PROC_BROWSER_TEST_F(FindBarPlatformHelperMacTest,
                       FindBarUpdatedFromPasteboard) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetURL(kSimple)));

  FindBarController* find_bar_controller =
      browser()->GetFeatures().GetFindBarController();
  ASSERT_NE(nullptr, find_bar_controller);
  FindBar* find_bar = find_bar_controller->find_bar();
  ASSERT_NE(nullptr, find_bar);

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(nullptr, web_contents);
  find_in_page::FindTabHelper* helper =
      find_in_page::FindTabHelper::FromWebContents(web_contents);
  ASSERT_NE(nullptr, helper);

  int ordinal = -1;
  int find_request_id = helper->current_find_request_id();
  NSString* update_string = @"Update String";
  [[FindPasteboard sharedInstance] setFindText:update_string];

  // setFindText shouldn't trigger a find since the findbar isn't showing
  EXPECT_EQ(find_request_id, helper->current_find_request_id());
  EXPECT_EQ(base::SysNSStringToUTF16(update_string), find_bar->GetFindText());

  find_bar_controller->Show();
  // Showing the findbar should trigger a find request
  EXPECT_EQ(find_request_id + 1, helper->current_find_request_id());
  EXPECT_EQ(0, WaitForFind(web_contents, &ordinal));
  EXPECT_EQ(0, ordinal);

  NSString* next_string = @"some text";
  [[FindPasteboard sharedInstance] setFindText:next_string];
  // setFindText should trigger a find since the findbar is now showing
  EXPECT_EQ(find_request_id + 2, helper->current_find_request_id());
  EXPECT_EQ(1, WaitForFind(web_contents, &ordinal));
  EXPECT_EQ(0, ordinal);
  EXPECT_EQ(base::SysNSStringToUTF16(next_string), find_bar->GetFindText());

  NSString* empty_string = @"";
  [[FindPasteboard sharedInstance] setFindText:empty_string];
  // setting an empty string won't trigger a find, but it should clear
  // the find results
  EXPECT_EQ(find_request_id + 2, helper->current_find_request_id());
  EXPECT_EQ(-1, helper->find_result().number_of_matches());
  EXPECT_EQ(-1, helper->find_result().active_match_ordinal());
  EXPECT_EQ(std::u16string(),
            find_bar->GetFindBarTesting()->GetMatchCountText());
  EXPECT_EQ(base::SysNSStringToUTF16(empty_string), find_bar->GetFindText());
}
