// 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/extensions/extension_toolbar_model.h"

#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_service.h"

ExtensionToolbarModel::ExtensionToolbarModel(ExtensionService* service)
    : service_(service),
      prefs_(service->profile()->GetPrefs()),
      extensions_initialized_(false) {
  DCHECK(service_);

  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
                 Source<Profile>(service_->profile()));
  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
                 Source<Profile>(service_->profile()));
  registrar_.Add(this, NotificationType::EXTENSIONS_READY,
                 Source<Profile>(service_->profile()));
  registrar_.Add(this,
                 NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
                 NotificationService::AllSources());

  visible_icon_count_ = prefs_->GetInteger(prefs::kExtensionToolbarSize);
}

ExtensionToolbarModel::~ExtensionToolbarModel() {
}

void ExtensionToolbarModel::DestroyingProfile() {
  registrar_.RemoveAll();
}

void ExtensionToolbarModel::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void ExtensionToolbarModel::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void ExtensionToolbarModel::MoveBrowserAction(const Extension* extension,
                                              int index) {
  ExtensionList::iterator pos = std::find(begin(), end(), extension);
  if (pos == end()) {
    NOTREACHED();
    return;
  }
  toolitems_.erase(pos);

  int i = 0;
  bool inserted = false;
  for (ExtensionList::iterator iter = begin(); iter != end(); ++iter, ++i) {
    if (i == index) {
      toolitems_.insert(iter, make_scoped_refptr(extension));
      inserted = true;
      break;
    }
  }

  if (!inserted) {
    DCHECK_EQ(index, static_cast<int>(toolitems_.size()));
    index = toolitems_.size();

    toolitems_.push_back(make_scoped_refptr(extension));
  }

  FOR_EACH_OBSERVER(Observer, observers_, BrowserActionMoved(extension, index));

  UpdatePrefs();
}

void ExtensionToolbarModel::SetVisibleIconCount(int count) {
  visible_icon_count_ = count == static_cast<int>(size()) ? -1 : count;
  prefs_->SetInteger(prefs::kExtensionToolbarSize, visible_icon_count_);
  prefs_->ScheduleSavePersistentPrefs();
}

void ExtensionToolbarModel::Observe(NotificationType type,
                                    const NotificationSource& source,
                                    const NotificationDetails& details) {
  if (type == NotificationType::EXTENSIONS_READY) {
    InitializeExtensionList();
    return;
  }

  if (!service_->is_ready())
    return;

  const Extension* extension = NULL;
  if (type == NotificationType::EXTENSION_UNLOADED) {
    extension = Details<UnloadedExtensionInfo>(details)->extension;
  } else {
    extension = Details<const Extension>(details).ptr();
  }
  if (type == NotificationType::EXTENSION_LOADED) {
    // We don't want to add the same extension twice. It may have already been
    // added by EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED below, if the user
    // hides the browser action and then disables and enables the extension.
    for (size_t i = 0; i < toolitems_.size(); i++) {
      if (toolitems_[i].get() == extension)
        return;  // Already exists.
    }
    if (service_->GetBrowserActionVisibility(extension))
      AddExtension(extension);
  } else if (type == NotificationType::EXTENSION_UNLOADED) {
    RemoveExtension(extension);
  } else if (type ==
             NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED) {
    if (service_->GetBrowserActionVisibility(extension))
      AddExtension(extension);
    else
      RemoveExtension(extension);
  } else {
    NOTREACHED() << "Received unexpected notification";
  }
}

void ExtensionToolbarModel::AddExtension(const Extension* extension) {
  // We only care about extensions with browser actions.
  if (!extension->browser_action())
    return;

  if (extension->id() == last_extension_removed_ &&
      last_extension_removed_index_ < toolitems_.size()) {
    toolitems_.insert(begin() + last_extension_removed_index_,
                      make_scoped_refptr(extension));
    FOR_EACH_OBSERVER(Observer, observers_,
        BrowserActionAdded(extension, last_extension_removed_index_));
  } else {
    toolitems_.push_back(make_scoped_refptr(extension));
    FOR_EACH_OBSERVER(Observer, observers_,
                      BrowserActionAdded(extension, toolitems_.size() - 1));
  }

  last_extension_removed_ = "";
  last_extension_removed_index_ = -1;

  UpdatePrefs();
}

void ExtensionToolbarModel::RemoveExtension(const Extension* extension) {
  ExtensionList::iterator pos = std::find(begin(), end(), extension);
  if (pos == end()) {
    return;
  }

  last_extension_removed_ = extension->id();
  last_extension_removed_index_ = pos - begin();

  toolitems_.erase(pos);
  FOR_EACH_OBSERVER(Observer, observers_,
                    BrowserActionRemoved(extension));

  UpdatePrefs();
}

// Combine the currently enabled extensions that have browser actions (which
// we get from the ExtensionService) with the ordering we get from the
// pref service. For robustness we use a somewhat inefficient process:
// 1. Create a vector of extensions sorted by their pref values. This vector may
// have holes.
// 2. Create a vector of extensions that did not have a pref value.
// 3. Remove holes from the sorted vector and append the unsorted vector.
void ExtensionToolbarModel::InitializeExtensionList() {
  DCHECK(service_->is_ready());

  std::vector<std::string> pref_order = service_->extension_prefs()->
      GetToolbarOrder();
  // Items that have a pref for their position.
  ExtensionList sorted;
  sorted.resize(pref_order.size(), NULL);
  // The items that don't have a pref for their position.
  ExtensionList unsorted;

  // Create the lists.
  for (size_t i = 0; i < service_->extensions()->size(); ++i) {
    const Extension* extension = service_->extensions()->at(i);
    if (!extension->browser_action())
      continue;
    if (!service_->GetBrowserActionVisibility(extension))
      continue;

    std::vector<std::string>::iterator pos =
        std::find(pref_order.begin(), pref_order.end(), extension->id());
    if (pos != pref_order.end()) {
      int index = std::distance(pref_order.begin(), pos);
      sorted[index] = extension;
    } else {
      unsorted.push_back(make_scoped_refptr(extension));
    }
  }

  // Merge the lists.
  toolitems_.reserve(sorted.size() + unsorted.size());
  for (ExtensionList::iterator iter = sorted.begin();
       iter != sorted.end(); ++iter) {
    if (*iter != NULL)
      toolitems_.push_back(*iter);
  }
  toolitems_.insert(toolitems_.end(), unsorted.begin(), unsorted.end());

  // Inform observers.
  for (size_t i = 0; i < toolitems_.size(); i++) {
    FOR_EACH_OBSERVER(Observer, observers_,
                      BrowserActionAdded(toolitems_[i], i));
  }

  UpdatePrefs();

  extensions_initialized_ = true;
  FOR_EACH_OBSERVER(Observer, observers_, ModelLoaded());
}

void ExtensionToolbarModel::UpdatePrefs() {
  if (!service_->extension_prefs())
    return;

  std::vector<std::string> ids;
  ids.reserve(toolitems_.size());
  for (ExtensionList::iterator iter = begin(); iter != end(); ++iter)
    ids.push_back((*iter)->id());
  service_->extension_prefs()->SetToolbarOrder(ids);
}

const Extension* ExtensionToolbarModel::GetExtensionByIndex(int index) const {
  return toolitems_[index];
}

int ExtensionToolbarModel::IncognitoIndexToOriginal(int incognito_index) {
  int original_index = 0, i = 0;
  for (ExtensionList::iterator iter = begin(); iter != end();
       ++iter, ++original_index) {
    if (service_->IsIncognitoEnabled((*iter)->id())) {
      if (incognito_index == i)
        break;
      ++i;
    }
  }
  return original_index;
}

int ExtensionToolbarModel::OriginalIndexToIncognito(int original_index) {
  int incognito_index = 0, i = 0;
  for (ExtensionList::iterator iter = begin(); iter != end();
       ++iter, ++i) {
    if (original_index == i)
      break;
    if (service_->IsIncognitoEnabled((*iter)->id()))
      ++incognito_index;
  }
  return incognito_index;
}