// 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/sidebar/sidebar_manager.h"

#include <vector>

#include "base/command_line.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_sidebar_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sidebar/sidebar_container.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_service.h"
#include "googleurl/src/gurl.h"

struct SidebarManager::SidebarStateForTab {
  // Sidebars linked to this tab.
  ContentIdToSidebarHostMap content_id_to_sidebar_host;
  // Content id of the currently active (expanded and visible) sidebar.
  std::string active_content_id;
};

// static
SidebarManager* SidebarManager::GetInstance() {
  return g_browser_process->sidebar_manager();
}

// static
bool SidebarManager::IsSidebarAllowed() {
  return CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kEnableExperimentalExtensionApis);
}

SidebarManager::SidebarManager() {
}

SidebarContainer* SidebarManager::GetActiveSidebarContainerFor(
    TabContents* tab) {
  TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
  if (it == tab_to_sidebar_host_.end())
    return NULL;
  if (it->second.active_content_id.empty())
    return NULL;
  ContentIdToSidebarHostMap::iterator host_it =
      it->second.content_id_to_sidebar_host.find(it->second.active_content_id);
  DCHECK(host_it != it->second.content_id_to_sidebar_host.end());
  return host_it->second;
}

SidebarContainer* SidebarManager::GetSidebarContainerFor(
    TabContents* tab, const std::string& content_id) {
  DCHECK(!content_id.empty());
  TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
  if (it == tab_to_sidebar_host_.end())
    return NULL;
  ContentIdToSidebarHostMap::iterator host_it =
      it->second.content_id_to_sidebar_host.find(content_id);
  if (host_it == it->second.content_id_to_sidebar_host.end())
    return NULL;
  return host_it->second;
}

TabContents* SidebarManager::GetSidebarTabContents(
    TabContents* tab, const std::string& content_id) {
  DCHECK(!content_id.empty());
  SidebarContainer* sidebar_host = GetSidebarContainerFor(tab, content_id);
  if (!sidebar_host)
    return NULL;
  return sidebar_host->sidebar_contents();
}

void SidebarManager::NotifyStateChanges(
    TabContents* was_active_sidebar_contents,
    TabContents* active_sidebar_contents) {
  if (was_active_sidebar_contents == active_sidebar_contents)
    return;

  SidebarContainer* was_active_host =
      was_active_sidebar_contents == NULL ? NULL :
          FindSidebarContainerFor(was_active_sidebar_contents);
  SidebarContainer* active_host =
      active_sidebar_contents == NULL ? NULL :
          FindSidebarContainerFor(active_sidebar_contents);

  if (was_active_host != NULL) {
    ExtensionSidebarEventRouter::OnStateChanged(
        was_active_sidebar_contents->profile(),
        was_active_host->tab_contents(), was_active_host->content_id(),
        extension_sidebar_constants::kShownState);
  }

  if (active_host != NULL) {
    ExtensionSidebarEventRouter::OnStateChanged(
        active_sidebar_contents->profile(),
        active_host->tab_contents(), active_host->content_id(),
        extension_sidebar_constants::kActiveState);
  }
}

void SidebarManager::ShowSidebar(TabContents* tab,
                                 const std::string& content_id) {
  DCHECK(!content_id.empty());
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  if (!host) {
    host = new SidebarContainer(tab, content_id, this);
    RegisterSidebarContainerFor(tab, host);
    // It might trigger UpdateSidebar notification, so load them after
    // the registration.
    host->LoadDefaults();
  }

  host->Show();

  ExtensionSidebarEventRouter::OnStateChanged(
      tab->profile(), tab, content_id,
      extension_sidebar_constants::kShownState);
}

void SidebarManager::ExpandSidebar(TabContents* tab,
                                   const std::string& content_id) {
  DCHECK(!content_id.empty());
  TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
  if (it == tab_to_sidebar_host_.end())
    return;
  // If it's already active, bail out.
  if (it->second.active_content_id == content_id)
    return;

  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  DCHECK(host);
  if (!host)
    return;
  it->second.active_content_id = content_id;

  host->Expand();
}

void SidebarManager::CollapseSidebar(TabContents* tab,
                                     const std::string& content_id) {
  DCHECK(!content_id.empty());
  TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
  if (it == tab_to_sidebar_host_.end())
    return;
  // If it's not the one active now, bail out.
  if (it->second.active_content_id != content_id)
    return;

  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  DCHECK(host);
  if (!host)
    return;
  it->second.active_content_id.clear();

  host->Collapse();
}

void SidebarManager::HideSidebar(TabContents* tab,
                                 const std::string& content_id) {
  DCHECK(!content_id.empty());
  TabToSidebarHostMap::iterator it = tab_to_sidebar_host_.find(tab);
  if (it == tab_to_sidebar_host_.end())
    return;
  if (it->second.active_content_id == content_id)
    it->second.active_content_id.clear();

  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  DCHECK(host);

  UnregisterSidebarContainerFor(tab, content_id);

  ExtensionSidebarEventRouter::OnStateChanged(
      tab->profile(), tab, content_id,
      extension_sidebar_constants::kHiddenState);
}

void SidebarManager::NavigateSidebar(TabContents* tab,
                                     const std::string& content_id,
                                     const GURL& url) {
  DCHECK(!content_id.empty());
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  if (!host)
    return;

  host->Navigate(url);
}

void SidebarManager::SetSidebarBadgeText(
    TabContents* tab, const std::string& content_id,
    const string16& badge_text) {
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  if (!host)
    return;
  host->SetBadgeText(badge_text);
}

void SidebarManager::SetSidebarIcon(
    TabContents* tab, const std::string& content_id,
    const SkBitmap& bitmap) {
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  if (!host)
    return;
  host->SetIcon(bitmap);
}

void SidebarManager::SetSidebarTitle(
    TabContents* tab, const std::string& content_id,
    const string16& title) {
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  if (!host)
    return;
  host->SetTitle(title);
}

SidebarManager::~SidebarManager() {
  DCHECK(tab_to_sidebar_host_.empty());
  DCHECK(sidebar_host_to_tab_.empty());
}

void SidebarManager::Observe(NotificationType type,
                             const NotificationSource& source,
                             const NotificationDetails& details) {
  if (type == NotificationType::TAB_CONTENTS_DESTROYED) {
    HideAllSidebars(Source<TabContents>(source).ptr());
  } else {
    NOTREACHED() << "Got a notification we didn't register for!";
  }
}

void SidebarManager::UpdateSidebar(SidebarContainer* host) {
  NotificationService::current()->Notify(
      NotificationType::SIDEBAR_CHANGED,
      Source<SidebarManager>(this),
      Details<SidebarContainer>(host));
}

void SidebarManager::HideAllSidebars(TabContents* tab) {
  TabToSidebarHostMap::iterator tab_it = tab_to_sidebar_host_.find(tab);
  if (tab_it == tab_to_sidebar_host_.end())
    return;
  const ContentIdToSidebarHostMap& hosts =
      tab_it->second.content_id_to_sidebar_host;

  std::vector<std::string> content_ids;
  for (ContentIdToSidebarHostMap::const_iterator it = hosts.begin();
       it != hosts.end(); ++it) {
    content_ids.push_back(it->first);
  }

  for (std::vector<std::string>::iterator it = content_ids.begin();
       it != content_ids.end(); ++it) {
    HideSidebar(tab, *it);
  }
}

SidebarContainer* SidebarManager::FindSidebarContainerFor(
    TabContents* sidebar_contents) {
  for (SidebarHostToTabMap::iterator it = sidebar_host_to_tab_.begin();
       it != sidebar_host_to_tab_.end();
       ++it) {
    if (sidebar_contents == it->first->sidebar_contents())
      return it->first;
  }
  return NULL;
}

void SidebarManager::RegisterSidebarContainerFor(
    TabContents* tab, SidebarContainer* sidebar_host) {
  DCHECK(!GetSidebarContainerFor(tab, sidebar_host->content_id()));

  // If it's a first sidebar for this tab, register destroy notification.
  if (tab_to_sidebar_host_.find(tab) == tab_to_sidebar_host_.end()) {
    registrar_.Add(this,
                   NotificationType::TAB_CONTENTS_DESTROYED,
                   Source<TabContents>(tab));
  }

  BindSidebarHost(tab, sidebar_host);
}

void SidebarManager::UnregisterSidebarContainerFor(
      TabContents* tab, const std::string& content_id) {
  SidebarContainer* host = GetSidebarContainerFor(tab, content_id);
  DCHECK(host);
  if (!host)
    return;

  UnbindSidebarHost(tab, host);

  // If there's no more sidebars linked to this tab, unsubscribe.
  if (tab_to_sidebar_host_.find(tab) == tab_to_sidebar_host_.end()) {
    registrar_.Remove(this,
                      NotificationType::TAB_CONTENTS_DESTROYED,
                      Source<TabContents>(tab));
  }

  // Issue tab closing event post unbound.
  host->SidebarClosing();
  // Destroy sidebar container.
  delete host;
}

void SidebarManager::BindSidebarHost(TabContents* tab,
                                     SidebarContainer* sidebar_host) {
  const std::string& content_id = sidebar_host->content_id();

  DCHECK(GetSidebarContainerFor(tab, content_id) == NULL);
  DCHECK(sidebar_host_to_tab_.find(sidebar_host) ==
         sidebar_host_to_tab_.end());

  tab_to_sidebar_host_[tab].content_id_to_sidebar_host[content_id] =
      sidebar_host;
  sidebar_host_to_tab_[sidebar_host] = tab;
}

void SidebarManager::UnbindSidebarHost(TabContents* tab,
                                       SidebarContainer* sidebar_host) {
  const std::string& content_id = sidebar_host->content_id();

  DCHECK(GetSidebarContainerFor(tab, content_id) == sidebar_host);
  DCHECK(sidebar_host_to_tab_.find(sidebar_host)->second == tab);
  DCHECK(tab_to_sidebar_host_[tab].active_content_id != content_id);

  tab_to_sidebar_host_[tab].content_id_to_sidebar_host.erase(content_id);
  if (tab_to_sidebar_host_[tab].content_id_to_sidebar_host.empty())
    tab_to_sidebar_host_.erase(tab);
  sidebar_host_to_tab_.erase(sidebar_host);
}