// 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 "chrome/browser/ui/webui/settings/custom_home_pages_table_model.h"

#include <stddef.h>

#include <algorithm>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/history/core/browser/history_service.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#endif

struct CustomHomePagesTableModel::Entry {
  Entry() : task_id(base::CancelableTaskTracker::kBadTaskId) {}

  // URL of the page.
  GURL url;

  // Page title.  If this is empty, we'll display the URL as the entry.
  std::u16string title;

  // If not |base::CancelableTaskTracker::kBadTaskId|, indicates we're loading
  // the title for the page.
  base::CancelableTaskTracker::TaskId task_id;
};

CustomHomePagesTableModel::CustomHomePagesTableModel(Profile* profile)
    : profile_(profile),
      observer_(nullptr),
      num_outstanding_title_lookups_(0) {}

CustomHomePagesTableModel::~CustomHomePagesTableModel() = default;

void CustomHomePagesTableModel::SetURLs(const std::vector<GURL>& urls) {
  entries_.resize(urls.size());
  for (size_t i = 0; i < urls.size(); ++i) {
    entries_[i].url = urls[i];
    entries_[i].title.erase();
  }
  LoadAllTitles();
}

void CustomHomePagesTableModel::AddWithoutNotification(size_t index,
                                                       const GURL& url) {
  DCHECK(index <= RowCount());
  entries_.insert(entries_.begin() + index, Entry());
  entries_[index].url = url;
}

void CustomHomePagesTableModel::Add(size_t index, const GURL& url) {
  AddWithoutNotification(index, url);
  LoadTitle(&(entries_[index]));
  if (observer_) {
    observer_->OnItemsAdded(index, 1);
  }
}

void CustomHomePagesTableModel::RemoveWithoutNotification(size_t index) {
  DCHECK(index < RowCount());
  Entry* entry = &(entries_[index]);
  // Cancel any pending load requests now so we don't deref a bogus pointer when
  // we get the loaded notification.
  if (entry->task_id != base::CancelableTaskTracker::kBadTaskId) {
    task_tracker_.TryCancel(entry->task_id);
    entry->task_id = base::CancelableTaskTracker::kBadTaskId;
  }
  entries_.erase(entries_.begin() + index);
}

void CustomHomePagesTableModel::Remove(size_t index) {
  RemoveWithoutNotification(index);
  if (observer_) {
    observer_->OnItemsRemoved(index, 1);
  }
}

void CustomHomePagesTableModel::SetToCurrentlyOpenPages(
    content::WebContents* ignore_contents) {
  // Remove the current entries.
  while (RowCount()) {
    RemoveWithoutNotification(0);
  }

  // Add tabs from appropriate browser windows.
  size_t add_index = 0;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [this, ignore_contents, &add_index](BrowserWindowInterface* browser) {
        if (!ShouldIncludeBrowser(browser)) {
          return true;
        }
        TabStripModel* const tab_model = browser->GetTabStripModel();
        for (int tab_index = 0; tab_index < tab_model->count(); ++tab_index) {
          content::WebContents* const contents =
              tab_model->GetWebContentsAt(tab_index);
          if (contents == ignore_contents) {
            continue;
          }
          const GURL url = contents->GetURL();
          if (!url.is_empty() &&
              !url.SchemeIs(content::kChromeDevToolsScheme)) {
            AddWithoutNotification(add_index++, url);
          }
        }
        return true;
      });
  LoadAllTitles();
}

std::vector<GURL> CustomHomePagesTableModel::GetURLs() {
  std::vector<GURL> urls(entries_.size());
  for (size_t i = 0; i < entries_.size(); ++i) {
    urls[i] = entries_[i].url;
  }
  return urls;
}

size_t CustomHomePagesTableModel::RowCount() {
  return entries_.size();
}

std::u16string CustomHomePagesTableModel::GetText(size_t row, int column_id) {
  DCHECK(column_id == 0);
  DCHECK(row < RowCount());
  return entries_[row].title.empty() ? FormattedURL(row) : entries_[row].title;
}

std::u16string CustomHomePagesTableModel::GetTooltip(size_t row) {
  return entries_[row].title.empty()
             ? std::u16string()
             : l10n_util::GetStringFUTF16(IDS_SETTINGS_ON_STARTUP_PAGE_TOOLTIP,
                                          entries_[row].title,
                                          FormattedURL(row));
}

void CustomHomePagesTableModel::SetObserver(ui::TableModelObserver* observer) {
  observer_ = observer;
}

bool CustomHomePagesTableModel::ShouldIncludeBrowser(
    BrowserWindowInterface* browser) {
  // Do not include incognito browsers.
  if (browser->GetProfile() != profile_) {
    return false;
  }
#if BUILDFLAG(IS_CHROMEOS)
  // Do not include the Settings window.
  if (chrome::SettingsWindowManager::GetInstance()->IsSettingsBrowser(
          browser)) {
    return false;
  }
#endif
  return true;
}

void CustomHomePagesTableModel::LoadTitle(Entry* entry) {
  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(profile_,
                                           ServiceAccessType::EXPLICIT_ACCESS);
  if (history_service) {
    entry->task_id = history_service->QueryURL(
        entry->url,
        base::BindOnce(&CustomHomePagesTableModel::OnGotTitle,
                       base::Unretained(this), entry->url, false),
        &task_tracker_);
  }
}

void CustomHomePagesTableModel::LoadAllTitles() {
  history::HistoryService* history_service =
      HistoryServiceFactory::GetForProfile(profile_,
                                           ServiceAccessType::EXPLICIT_ACCESS);
  // It's possible for multiple LoadAllTitles() queries to be inflight we want
  // to make sure everything is resolved before updating the observer or we risk
  // getting rendering glitches.
  num_outstanding_title_lookups_ += entries_.size();
  for (Entry& entry : entries_) {
    if (history_service) {
      entry.task_id = history_service->QueryURL(
          entry.url,
          base::BindOnce(&CustomHomePagesTableModel::OnGotOneOfManyTitles,
                         base::Unretained(this), entry.url),
          &task_tracker_);
    }
  }
  if (entries_.empty()) {
    observer_->OnModelChanged();
  }
}

void CustomHomePagesTableModel::OnGotOneOfManyTitles(
    const GURL& entry_url,
    history::QueryURLResult result) {
  OnGotTitle(entry_url, false, std::move(result));
  DCHECK_GE(num_outstanding_title_lookups_, 1);
  if (--num_outstanding_title_lookups_ == 0 && observer_) {
    observer_->OnModelChanged();
  }
}

void CustomHomePagesTableModel::OnGotTitle(const GURL& entry_url,
                                           bool observable,
                                           history::QueryURLResult result) {
  Entry* entry = nullptr;
  size_t entry_index = 0;
  for (size_t i = 0; i < entries_.size(); ++i) {
    if (entries_[i].url == entry_url) {
      entry = &entries_[i];
      entry_index = i;
      break;
    }
  }
  if (!entry) {
    // The URLs changed before we were called back.
    return;
  }
  entry->task_id = base::CancelableTaskTracker::kBadTaskId;
  if (result.success && !result.row.title().empty()) {
    entry->title = result.row.title();
    if (observer_ && observable) {
      observer_->OnItemsChanged(entry_index, 1);
    }
  }
}

std::u16string CustomHomePagesTableModel::FormattedURL(size_t row) const {
  std::u16string url = url_formatter::FormatUrl(entries_[row].url);
  url = base::i18n::GetDisplayStringInLTRDirectionality(url);
  return url;
}
