// Copyright (c) 2006-2008 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/back_forward_menu_model.h"

#include "chrome/browser/browser.h"
#include "chrome/browser/history_tab_ui.h"
#include "chrome/browser/navigation_controller.h"
#include "chrome/browser/navigation_entry.h"
#include "chrome/browser/tab_contents.h"
#include "chrome/browser/user_metrics.h"
#include "chrome/common/l10n_util.h"
#include "net/base/registry_controlled_domain.h"

#include "generated_resources.h"

const int BackForwardMenuModel::kMaxHistoryItems = 12;
const int BackForwardMenuModel::kMaxChapterStops = 5;

BackForwardMenuModel::BackForwardMenuModel(Browser* browser,
                                           ModelType model_type)
    : browser_(browser),
      test_tab_contents_(NULL),
      model_type_(model_type) {
}

BackForwardMenuModel::~BackForwardMenuModel() {
}

TabContents* BackForwardMenuModel::GetTabContents() const {
  // We use the test tab contents if the unit test has specified it.
  return test_tab_contents_ ? test_tab_contents_ :
                              browser_->GetSelectedTabContents();
}

int BackForwardMenuModel::GetHistoryItemCount() const {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int items = 0;

  if (model_type_ == FORWARD_MENU_DELEGATE) {
    // Only count items from n+1 to end (if n is current entry)
    items = controller->GetEntryCount() -
            controller->GetCurrentEntryIndex() - 1;
  } else {
    items = controller->GetCurrentEntryIndex();
  }

  if (items > kMaxHistoryItems)
    items = kMaxHistoryItems;
  else if (items < 0)
    items = 0;

  return items;
}

int BackForwardMenuModel::GetChapterStopCount(int history_items) const {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int chapter_stops = 0;
  int current_entry = controller->GetCurrentEntryIndex();

  if (history_items == kMaxHistoryItems) {
    int chapter_id = current_entry;
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      chapter_id += history_items;
    } else {
      chapter_id -= history_items;
    }

    do {
      chapter_id = GetIndexOfNextChapterStop(chapter_id,
          model_type_ == FORWARD_MENU_DELEGATE);
      if (chapter_id != -1)
        ++chapter_stops;
    } while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
  }

  return chapter_stops;
}

int BackForwardMenuModel::GetItemCount() const {
  int items = GetHistoryItemCount();

  if (items > 0) {
    int chapter_stops = 0;

    // Next, we count ChapterStops, if any.
    if (items == kMaxHistoryItems)
      chapter_stops = GetChapterStopCount(items);

    if (chapter_stops)
      items += chapter_stops + 1;  // Chapter stops also need a separator.

    // If the menu is not empty, add two positions in the end
    // for a separator and a "Show Full History" item.
    items += 2;
  }

  return items;
}

int BackForwardMenuModel::MenuIdToNavEntryIndex(int menu_id) const {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int history_items = GetHistoryItemCount();

  DCHECK(menu_id > 0);

  // Convert anything above the History items separator.
  if (menu_id <= history_items) {
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      // The |menu_id| is relative to our current position, so we need to add.
      menu_id += controller->GetCurrentEntryIndex();
    } else {
      // Back menu is reverse.
      menu_id = controller->GetCurrentEntryIndex() - menu_id;
    }
    return menu_id;
  }
  if (menu_id == history_items + 1)
    return -1;           // Don't translate the separator for history items.

  if (menu_id >= history_items + 1 + GetChapterStopCount(history_items) + 1)
    return -1;           // This is beyond the last chapter stop so we abort.

  // This menu item is a chapter stop located between the two separators.
  menu_id = FindChapterStop(history_items,
                            model_type_ == FORWARD_MENU_DELEGATE,
                            menu_id - history_items - 1 - 1);

  return menu_id;
}

NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int menu_id) const {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int index = MenuIdToNavEntryIndex(menu_id);
  return controller->GetEntryAtIndex(index);
}

std::wstring BackForwardMenuModel::GetLabel(int menu_id) const {
  // Return label "Show Full History" for the last item of the menu.
  if (menu_id == GetItemCount())
    return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);

  // Return an empty string for a separator.
  if (IsItemSeparator(menu_id))
    return L"";

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return entry->title();
}

const SkBitmap& BackForwardMenuModel::GetIcon(int menu_id) const {
  // Return NULL if the item doesn't have an icon
  if (!HasIcon(menu_id))
    return GetEmptyIcon();

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return entry->favicon().bitmap();
}

bool BackForwardMenuModel::IsItemSeparator(int menu_id) const {
  int history_items = GetHistoryItemCount();
  // If the menu_id is higher than the number of history items + separator,
  // we then consider if it is a chapter-stop entry.
  if (menu_id > history_items + 1) {
    // We either are in ChapterStop area, or at the end of the list (the "Show
    // Full History" link).
    int chapter_stops = GetChapterStopCount(history_items);
    if (chapter_stops == 0)
      return false;  // We must have reached the "Show Full History" link.
    // Otherwise, look to see if we have reached the separator for the
    // chapter-stops. If not, this is a chapter stop.
    return (menu_id == history_items + 1 +
                       chapter_stops + 1);
  }

  // Look to see if we have reached the separator for the history items.
  return menu_id == history_items + 1;
}

bool BackForwardMenuModel::HasIcon(int menu_id) const {
  // Using "id" not "id - 1" because the last item "Show Full History"
  // doesn't have an icon.
  return menu_id < GetItemCount() && !IsItemSeparator(menu_id);
}

bool BackForwardMenuModel::SupportsCommand(int menu_id) const {
  return menu_id - 1 < GetItemCount() && !IsItemSeparator(menu_id);
}

bool BackForwardMenuModel::IsCommandEnabled(int menu_id) const {
  return menu_id - 1 < GetItemCount() && !IsItemSeparator(menu_id);
}

std::wstring BackForwardMenuModel::BuildActionName(
    const std::wstring& action, int index) const {
  DCHECK(!action.empty());
  DCHECK(index >= -1);
  std::wstring metric_string;
  if (model_type_ == FORWARD_MENU_DELEGATE)
    metric_string += L"ForwardMenu_";
  else
    metric_string += L"BackMenu_";
  metric_string += action;
  if (index != -1)
    metric_string += IntToWString(index);
  return metric_string;
}

void BackForwardMenuModel::ExecuteCommand(int menu_id) {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  DCHECK(!IsItemSeparator(menu_id));

  // Execute the command for the last item: "Show Full History".
  if (menu_id == GetItemCount()) {
    UserMetrics::RecordComputedAction(BuildActionName(L"ShowFullHistory", -1),
                                      controller->profile());
    browser_->ShowNativeUITab(HistoryTabUI::GetURL());
    return;
  }

  // Log whether it was a history or chapter click.
  if (menu_id <= GetHistoryItemCount()) {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"HistoryClick", menu_id), controller->profile());
  } else {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"ChapterClick", menu_id - GetHistoryItemCount() - 1),
        controller->profile());
  }

  int index = MenuIdToNavEntryIndex(menu_id);
  if (index >= 0 && index < controller->GetEntryCount())
    controller->GoToIndex(index);
}

void BackForwardMenuModel::MenuWillShow() {
  UserMetrics::RecordComputedAction(BuildActionName(L"Popup", -1),
                                    browser_->profile());
}

std::wstring BackForwardMenuModel::GetShowFullHistoryLabel() const {
  return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);
}

int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
                                                    bool forward) const {
  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int max_count = controller->GetEntryCount();
  if (start_from < 0 || start_from >= max_count)
    return -1;  // Out of bounds.

  if (forward) {
    if (start_from < max_count - 1) {
      // We want to advance over the current chapter stop, so we add one.
      // We don't need to do this when direction is backwards.
      start_from++;
    } else {
      return -1;
    }
  }

  NavigationEntry* start_entry = controller->GetEntryAtIndex(start_from);
  const GURL& url = start_entry->url();

  if (!forward) {
    // When going backwards we return the first entry we find that has a
    // different domain.
    for (int i = start_from - 1; i >= 0; --i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller->GetEntryAtIndex(i)->url()))
        return i;
    }
    // We have reached the beginning without finding a chapter stop.
    return -1;
  } else {
    // When going forwards we return the entry before the entry that has a
    // different domain.
    for (int i = start_from + 1; i < max_count; ++i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller->GetEntryAtIndex(i)->url()))
        return i - 1;
    }
    // Last entry is always considered a chapter stop.
    return max_count - 1;
  }
}

int BackForwardMenuModel::FindChapterStop(int offset,
                                          bool forward,
                                          int skip) const {
  if (offset < 0 || skip < 0)
    return -1;

  if (!forward)
    offset *= -1;

  TabContents* contents = GetTabContents();
  NavigationController* controller = contents->controller();

  int entry = controller->GetCurrentEntryIndex() + offset;

  for (int i = 0; i < skip + 1; i++)
    entry = GetIndexOfNextChapterStop(entry, forward);

  return entry;
}