1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/webui/ntp/suggestions_combiner.h"
6
7#include <algorithm>
8
9#include "base/values.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/browser_iterator.h"
13#include "chrome/browser/ui/tabs/tab_strip_model.h"
14#include "chrome/browser/ui/webui/ntp/suggestions_page_handler.h"
15#include "chrome/browser/ui/webui/ntp/suggestions_source.h"
16#include "content/public/browser/web_contents.h"
17
18namespace {
19
20static const size_t kSuggestionsCount = 8;
21
22}  // namespace
23
24SuggestionsCombiner::SuggestionsCombiner(
25    SuggestionsCombiner::Delegate* delegate,
26    Profile* profile)
27    : sources_fetching_count_(0),
28      delegate_(delegate),
29      suggestions_count_(kSuggestionsCount),
30      page_values_(new base::ListValue()),
31      debug_enabled_(false),
32      profile_(profile) {
33}
34
35SuggestionsCombiner::~SuggestionsCombiner() {
36}
37
38void SuggestionsCombiner::AddSource(SuggestionsSource* source) {
39  source->SetCombiner(this);
40  source->SetDebug(debug_enabled_);
41  sources_.push_back(source);
42}
43
44void SuggestionsCombiner::EnableDebug(bool enable) {
45  debug_enabled_ = enable;
46  for (size_t i = 0; i < sources_.size(); ++i) {
47    sources_[i]->SetDebug(enable);
48  }
49}
50
51void SuggestionsCombiner::FetchItems(Profile* profile) {
52  sources_fetching_count_ = sources_.size();
53  for (size_t i = 0; i < sources_.size(); ++i) {
54    sources_[i]->FetchItems(profile);
55  }
56}
57
58base::ListValue* SuggestionsCombiner::GetPageValues() {
59  return page_values_.get();
60}
61
62void SuggestionsCombiner::OnItemsReady() {
63  DCHECK_GT(sources_fetching_count_, 0);
64  sources_fetching_count_--;
65  if (sources_fetching_count_ == 0) {
66    FillPageValues();
67    delegate_->OnSuggestionsReady();
68  }
69}
70
71void SuggestionsCombiner::SetSuggestionsCount(size_t suggestions_count) {
72  suggestions_count_ = suggestions_count;
73}
74
75void SuggestionsCombiner::FillPageValues() {
76  int total_weight = 0;
77  for (size_t i = 0; i < sources_.size(); ++i)
78    total_weight += sources_[i]->GetWeight();
79  DCHECK_GT(total_weight, 0);
80
81  page_values_.reset(new base::ListValue());
82
83  // Evaluate how many items to obtain from each source. We use error diffusion
84  // to ensure that we get the total desired number of items.
85  int error = 0;
86
87  // Holds the index at which the next item should be added for each source.
88  std::vector<size_t> next_item_index_for_source;
89  next_item_index_for_source.reserve(sources_.size());
90  for (size_t i = 0; i < sources_.size(); ++i) {
91    int numerator = sources_[i]->GetWeight() * suggestions_count_ + error;
92    error = numerator % total_weight;
93    int item_count = std::min(numerator / total_weight,
94        sources_[i]->GetItemCount());
95
96    for (int j = 0; j < item_count; ++j)
97      page_values_->Append(sources_[i]->PopItem());
98    next_item_index_for_source.push_back(page_values_->GetSize());
99  }
100
101  // Fill in extra items, prioritizing the first source.
102  // Rather than updating |next_item_index_for_source| we keep track of the
103  // number of extra items that were added and offset indices by that much.
104  size_t extra_items_added = 0;
105  for (size_t i = 0; i < sources_.size() &&
106      page_values_->GetSize() < suggestions_count_; ++i) {
107
108    size_t index = next_item_index_for_source[i] + extra_items_added;
109    while (page_values_->GetSize() < suggestions_count_) {
110      base::DictionaryValue* item = sources_[i]->PopItem();
111      if (!item)
112        break;
113      page_values_->Insert(index++, item);
114      extra_items_added++;
115    }
116  }
117
118  // Add page value information common to all sources.
119  for (size_t i = 0; i < page_values_->GetSize(); i++) {
120    base::DictionaryValue* page_value;
121    if (page_values_->GetDictionary(i, &page_value))
122      AddExtendedInformation(page_value);
123  }
124}
125
126void SuggestionsCombiner::AddExtendedInformation(
127    base::DictionaryValue* page_value) {
128  if (debug_enabled_) {
129    std::string url_string;
130    if (page_value->GetString("url", &url_string)) {
131      GURL url(url_string);
132      page_value->SetBoolean("already_open", IsUrlAlreadyOpen(url));
133    }
134  }
135}
136
137bool SuggestionsCombiner::IsUrlAlreadyOpen(const GURL &url) {
138  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
139    const Browser* browser = *it;
140    if (browser->profile()->IsOffTheRecord() ||
141        !browser->profile()->IsSameProfile(profile_))
142      continue;
143
144    for (int i = 0; i < browser->tab_strip_model()->count(); i++) {
145      const content::WebContents* tab =
146          browser->tab_strip_model()->GetWebContentsAt(i);
147      if (tab->GetURL() == url)
148        return true;
149    }
150  }
151  return false;
152}
153