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

#include "content/browser/browser_thread.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "content/common/notification_registrar.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_service.h"

// ExtensionTabIdMap is a Singleton, so it doesn't need refcounting.
DISABLE_RUNNABLE_METHOD_REFCOUNT(ExtensionTabIdMap);

//
// ExtensionTabIdMap::TabObserver
//

// This class listens for notifications about new and closed tabs on the UI
// thread, and notifies the ExtensionTabIdMap on the IO thread. It should only
// ever be accessed on the UI thread.
class ExtensionTabIdMap::TabObserver : public NotificationObserver {
 public:
  TabObserver();
  ~TabObserver();

 private:
  // NotificationObserver interface.
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);

  NotificationRegistrar registrar_;
};

ExtensionTabIdMap::TabObserver::TabObserver() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
                 NotificationService::AllSources());
  registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_DELETED,
                 NotificationService::AllSources());
  registrar_.Add(this, NotificationType::TAB_PARENTED,
                 NotificationService::AllSources());
}

ExtensionTabIdMap::TabObserver::~TabObserver() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}

void ExtensionTabIdMap::TabObserver::Observe(
    NotificationType type, const NotificationSource& source,
    const NotificationDetails& details) {
  switch (type.value) {
    case NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB: {
      TabContents* contents = Source<TabContents>(source).ptr();
      RenderViewHost* host = Details<RenderViewHost>(details).ptr();
      // TODO(mpcmoplete): How can we tell if window_id is bogus? It may not
      // have been set yet.
      BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          NewRunnableMethod(
              ExtensionTabIdMap::GetInstance(),
              &ExtensionTabIdMap::SetTabAndWindowId,
              host->process()->id(), host->routing_id(),
              contents->controller().session_id().id(),
              contents->controller().window_id().id()));
      break;
    }
    case NotificationType::TAB_PARENTED: {
      NavigationController* controller =
          Source<NavigationController>(source).ptr();
      RenderViewHost* host = controller->tab_contents()->render_view_host();
      BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          NewRunnableMethod(
              ExtensionTabIdMap::GetInstance(),
              &ExtensionTabIdMap::SetTabAndWindowId,
              host->process()->id(), host->routing_id(),
              controller->session_id().id(),
              controller->window_id().id()));
      break;
    }
    case NotificationType::RENDER_VIEW_HOST_DELETED: {
      RenderViewHost* host = Source<RenderViewHost>(source).ptr();
      BrowserThread::PostTask(
          BrowserThread::IO, FROM_HERE,
          NewRunnableMethod(
              ExtensionTabIdMap::GetInstance(),
              &ExtensionTabIdMap::ClearTabAndWindowId,
              host->process()->id(), host->routing_id()));
      break;
    }
    default:
      NOTREACHED();
      return;
  }
}

//
// ExtensionTabIdMap
//

ExtensionTabIdMap::ExtensionTabIdMap() : observer_(NULL) {
}

ExtensionTabIdMap::~ExtensionTabIdMap() {
}

// static
ExtensionTabIdMap* ExtensionTabIdMap::GetInstance() {
  return Singleton<ExtensionTabIdMap>::get();
}

void ExtensionTabIdMap::Init() {
  observer_ = new TabObserver;
}

void ExtensionTabIdMap::Shutdown() {
  delete observer_;
}

void ExtensionTabIdMap::SetTabAndWindowId(
    int render_process_host_id, int routing_id, int tab_id, int window_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  RenderId render_id(render_process_host_id, routing_id);
  map_[render_id] = TabAndWindowId(tab_id, window_id);
}

void ExtensionTabIdMap::ClearTabAndWindowId(
    int render_process_host_id, int routing_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  RenderId render_id(render_process_host_id, routing_id);
  map_.erase(render_id);
}

bool ExtensionTabIdMap::GetTabAndWindowId(
    int render_process_host_id, int routing_id, int* tab_id, int* window_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  RenderId render_id(render_process_host_id, routing_id);
  TabAndWindowIdMap::iterator iter = map_.find(render_id);
  if (iter != map_.end()) {
    *tab_id = iter->second.first;
    *window_id = iter->second.second;
    return true;
  }
  return false;
}