// Copyright (c) 2009 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 "gfx/gtk_native_view_id_manager.h"

#include <gtk/gtk.h>
#include <gdk/gdkx.h>

#include "base/logging.h"
#include "base/rand_util.h"
#include "gfx/gtk_preserve_window.h"

// -----------------------------------------------------------------------------
// Bounce functions for GTK to callback into a C++ object...

void OnRealize(gfx::NativeView widget, void* arg) {
  GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg);
  manager->OnRealize(widget);
}

void OnUnrealize(gfx::NativeView widget, void *arg) {
  GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg);
  manager->OnUnrealize(widget);
}

static void OnDestroy(GtkObject* obj, void* arg) {
  GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg);
  manager->OnDestroy(reinterpret_cast<GtkWidget*>(obj));
}

// -----------------------------------------------------------------------------


// -----------------------------------------------------------------------------
// Public functions...

GtkNativeViewManager::GtkNativeViewManager() {
}

GtkNativeViewManager::~GtkNativeViewManager() {
}

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

gfx::NativeViewId GtkNativeViewManager::GetIdForWidget(gfx::NativeView widget) {
  // This is just for unit tests:
  if (!widget)
    return 0;

  AutoLock locked(lock_);

  std::map<gfx::NativeView, gfx::NativeViewId>::const_iterator i =
    native_view_to_id_.find(widget);

  if (i != native_view_to_id_.end())
    return i->second;

  gfx::NativeViewId new_id =
      static_cast<gfx::NativeViewId>(base::RandUint64());
  while (id_to_info_.find(new_id) != id_to_info_.end())
    new_id = static_cast<gfx::NativeViewId>(base::RandUint64());

  NativeViewInfo info;
  info.widget = widget;
  if (GTK_WIDGET_REALIZED(widget)) {
    GdkWindow *gdk_window = widget->window;
    DCHECK(gdk_window);
    info.x_window_id = GDK_WINDOW_XID(gdk_window);
  }

  native_view_to_id_[widget] = new_id;
  id_to_info_[new_id] = info;

  g_signal_connect(widget, "realize", G_CALLBACK(::OnRealize), this);
  g_signal_connect(widget, "unrealize", G_CALLBACK(::OnUnrealize), this);
  g_signal_connect(widget, "destroy", G_CALLBACK(::OnDestroy), this);

  return new_id;
}

bool GtkNativeViewManager::GetXIDForId(XID* output, gfx::NativeViewId id) {
  AutoLock locked(lock_);

  std::map<gfx::NativeViewId, NativeViewInfo>::const_iterator i =
    id_to_info_.find(id);

  if (i == id_to_info_.end())
    return false;

  *output = i->second.x_window_id;
  return true;
}

bool GtkNativeViewManager::GetPermanentXIDForId(XID* output,
                                                gfx::NativeViewId id) {
  AutoLock locked(lock_);

  std::map<gfx::NativeViewId, NativeViewInfo>::iterator i =
      id_to_info_.find(id);

  if (i == id_to_info_.end())
    return false;

  // We only return permanent XIDs for widgets that allow us to guarantee that
  // the XID will not change.
  DCHECK(GTK_IS_PRESERVE_WINDOW(i->second.widget));
  GtkPreserveWindow* widget =
      reinterpret_cast<GtkPreserveWindow*>(i->second.widget);
  gtk_preserve_window_set_preserve(widget, TRUE);

  *output = GDK_WINDOW_XID(i->second.widget->window);

  // Update the reference count on the permanent XID.
  PermanentXIDInfo info;
  info.widget = widget;
  info.ref_count = 1;
  std::pair<std::map<XID, PermanentXIDInfo>::iterator, bool> ret =
    perm_xid_to_info_.insert(std::make_pair(*output, info));

  if (!ret.second) {
    DCHECK(ret.first->second.widget == widget);
    ret.first->second.ref_count++;
  }

  return true;
}

void GtkNativeViewManager::ReleasePermanentXID(XID xid) {
  AutoLock locked(lock_);

  std::map<XID, PermanentXIDInfo>::iterator i =
    perm_xid_to_info_.find(xid);

  if (i == perm_xid_to_info_.end())
    return;

  if (i->second.ref_count > 1) {
    i->second.ref_count--;
  } else {
    if (i->second.widget) {
      gtk_preserve_window_set_preserve(i->second.widget, FALSE);
    } else {
      GdkWindow* window = reinterpret_cast<GdkWindow*>(
          gdk_xid_table_lookup(xid));
      DCHECK(window);
      gdk_window_destroy(window);
    }
    perm_xid_to_info_.erase(i);
  }
}

// -----------------------------------------------------------------------------


// -----------------------------------------------------------------------------
// Private functions...

gfx::NativeViewId GtkNativeViewManager::GetWidgetId(gfx::NativeView widget) {
  lock_.AssertAcquired();

  std::map<gfx::NativeView, gfx::NativeViewId>::const_iterator i =
    native_view_to_id_.find(widget);

  CHECK(i != native_view_to_id_.end());
  return i->second;
}

void GtkNativeViewManager::OnRealize(gfx::NativeView widget) {
  AutoLock locked(lock_);

  const gfx::NativeViewId id = GetWidgetId(widget);
  std::map<gfx::NativeViewId, NativeViewInfo>::iterator i =
    id_to_info_.find(id);

  CHECK(i != id_to_info_.end());
  CHECK(widget->window);

  i->second.x_window_id = GDK_WINDOW_XID(widget->window);
}

void GtkNativeViewManager::OnUnrealize(gfx::NativeView widget) {
  AutoLock unrealize_locked(unrealize_lock_);
  AutoLock locked(lock_);

  const gfx::NativeViewId id = GetWidgetId(widget);
  std::map<gfx::NativeViewId, NativeViewInfo>::iterator i =
    id_to_info_.find(id);

  CHECK(i != id_to_info_.end());
}

void GtkNativeViewManager::OnDestroy(gfx::NativeView widget) {
  AutoLock locked(lock_);

  std::map<gfx::NativeView, gfx::NativeViewId>::iterator i =
    native_view_to_id_.find(widget);
  CHECK(i != native_view_to_id_.end());

  std::map<gfx::NativeViewId, NativeViewInfo>::iterator j =
    id_to_info_.find(i->second);
  CHECK(j != id_to_info_.end());

  // If the XID is supposed to outlive the widget, mark it
  // in the lookup table.
  if (GTK_IS_PRESERVE_WINDOW(widget) &&
      gtk_preserve_window_get_preserve(
          reinterpret_cast<GtkPreserveWindow*>(widget))) {
    std::map<XID, PermanentXIDInfo>::iterator k =
      perm_xid_to_info_.find(GDK_WINDOW_XID(widget->window));

    if (k != perm_xid_to_info_.end())
      k->second.widget = NULL;
  }

  native_view_to_id_.erase(i);
  id_to_info_.erase(j);
}

// -----------------------------------------------------------------------------