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

#include <gtk/gtk.h>

#include "base/logging.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/frame/bubble_window.h"
#include "chrome/browser/ui/views/window.h"
#include "ui/base/gtk/gtk_signal.h"
#include "views/controls/native/native_view_host.h"
#include "views/window/dialog_delegate.h"
#include "views/window/non_client_view.h"
#include "views/window/window.h"

namespace {

const int kDialogPadding = 3;

const char kNativeDialogHost[] = "_chromeos_native_dialog_host_";

// TODO(xiyuan): Use gtk_window_get_default_widget with GTK 2.14+.
// Gets the default widget of given dialog.
GtkWidget* GetDialogDefaultWidget(GtkDialog* dialog) {
  GtkWidget* default_widget = NULL;

  GList* children = gtk_container_get_children(
      GTK_CONTAINER(dialog->action_area));

  GList* current = children;
  while (current) {
    GtkWidget* widget = reinterpret_cast<GtkWidget*>(current->data);
    if (GTK_WIDGET_HAS_DEFAULT(widget)) {
      default_widget = widget;
      break;
    }

    current = g_list_next(current);
  }

  g_list_free(children);

  return default_widget;
}

}  // namespace

namespace chromeos {

class NativeDialogHost : public views::View,
                         public views::DialogDelegate {
 public:
  NativeDialogHost(gfx::NativeView native_dialog,
                   int flags,
                   const gfx::Size& size,
                   const gfx::Size& min_size);
  ~NativeDialogHost();

  // views::DialogDelegate implementation:
  virtual bool CanResize() const { return flags_ & DIALOG_FLAG_RESIZEABLE; }
  virtual int GetDialogButtons() const { return 0; }
  virtual std::wstring GetWindowTitle() const { return title_; }
  virtual views::View* GetContentsView() { return this; }
  virtual bool IsModal() const { return flags_ & DIALOG_FLAG_MODAL; }
  virtual void WindowClosing();

 protected:
  CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnCheckResize);
  CHROMEGTK_CALLBACK_0(NativeDialogHost, void, OnDialogDestroy);

  // views::View implementation:
  virtual gfx::Size GetPreferredSize();
  virtual void Layout();
  virtual void ViewHierarchyChanged(bool is_add,
                                    views::View* parent,
                                    views::View* child);
 private:
  // Init and attach to native dialog.
  void Init();

  // Check and apply minimum size restriction.
  void CheckSize();

  // The GtkDialog whose vbox will be displayed in this view.
  gfx::NativeView dialog_;

  // NativeViewHost for the dialog's contents.
  views::NativeViewHost* contents_view_;

  std::wstring title_;
  int flags_;
  gfx::Size size_;
  gfx::Size preferred_size_;
  gfx::Size min_size_;

  int destroy_signal_id_;

  DISALLOW_IMPLICIT_CONSTRUCTORS(NativeDialogHost);
};

///////////////////////////////////////////////////////////////////////////////
// NativeDialogHost, public:

NativeDialogHost::NativeDialogHost(gfx::NativeView native_dialog,
                                   int flags,
                                   const gfx::Size& size,
                                   const gfx::Size& min_size)
    : dialog_(native_dialog),
      contents_view_(NULL),
      flags_(flags),
      size_(size),
      preferred_size_(size),
      min_size_(min_size),
      destroy_signal_id_(0) {
  const char* title = gtk_window_get_title(GTK_WINDOW(dialog_));
  if (title)
    UTF8ToWide(title, strlen(title), &title_);

  destroy_signal_id_ = g_signal_connect(dialog_, "destroy",
      G_CALLBACK(&OnDialogDestroyThunk), this);
}

NativeDialogHost::~NativeDialogHost() {
}

void NativeDialogHost::OnCheckResize(GtkWidget* widget) {
  // Do auto height resize only when we are asked to do so.
  if (size_.height() == 0) {
    gfx::NativeView contents = contents_view_->native_view();

    // Check whether preferred height has changed. We keep the current width
    // unchanged and pass "-1" as height to let gtk calculate a proper height.
    gtk_widget_set_size_request(contents, width(), -1);
    GtkRequisition requsition = { 0 };
    gtk_widget_size_request(contents, &requsition);

    if (preferred_size_.height() != requsition.height) {
      preferred_size_.set_width(requsition.width);
      preferred_size_.set_height(requsition.height);
      CheckSize();
      SizeToPreferredSize();

      gfx::Size window_size = window()->non_client_view()->GetPreferredSize();
      gfx::Rect window_bounds = window()->GetBounds();
      window_bounds.set_width(window_size.width());
      window_bounds.set_height(window_size.height());
      window()->SetWindowBounds(window_bounds, NULL);
    }
  }
}

void NativeDialogHost::OnDialogDestroy(GtkWidget* widget) {
  dialog_ = NULL;
  destroy_signal_id_ = 0;
  window()->CloseWindow();
}

///////////////////////////////////////////////////////////////////////////////
// NativeDialogHost, views::DialogDelegate implementation:
void NativeDialogHost::WindowClosing() {
  if (dialog_) {
    // Disconnect the "destroy" signal because we are about to destroy
    // the dialog ourselves and no longer interested in it.
    g_signal_handler_disconnect(G_OBJECT(dialog_), destroy_signal_id_);
    gtk_dialog_response(GTK_DIALOG(dialog_), GTK_RESPONSE_DELETE_EVENT);
  }
}

///////////////////////////////////////////////////////////////////////////////
// NativeDialogHost, views::View implementation:

gfx::Size NativeDialogHost::GetPreferredSize() {
  return preferred_size_;
}

void NativeDialogHost::Layout() {
  contents_view_->SetBounds(0, 0, width(), height());
}

void NativeDialogHost::ViewHierarchyChanged(bool is_add,
                                            views::View* parent,
                                            views::View* child) {
  if (is_add && child == this)
    Init();
}

///////////////////////////////////////////////////////////////////////////////
// NativeDialogHost, private:
void NativeDialogHost::Init() {
  if (contents_view_)
    return;

  // Get default widget of the dialog.
  GtkWidget* default_widget = GetDialogDefaultWidget(GTK_DIALOG(dialog_));

  // Get focus widget of the dialog.
  GtkWidget* focus_widget = gtk_window_get_focus(GTK_WINDOW(dialog_));

  // Create a GtkAlignment as dialog contents container.
  GtkWidget* contents = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(contents),
      kDialogPadding, kDialogPadding,
      kDialogPadding, kDialogPadding);

  // Move dialog contents into our container.
  GtkWidget* dialog_contents = GTK_DIALOG(dialog_)->vbox;
  g_object_ref(dialog_contents);
  gtk_container_remove(GTK_CONTAINER(dialog_), dialog_contents);
  gtk_container_add(GTK_CONTAINER(contents), dialog_contents);
  g_object_unref(dialog_contents);
  gtk_widget_hide(dialog_);

  g_object_set_data(G_OBJECT(dialog_), kNativeDialogHost,
      reinterpret_cast<gpointer>(this));

  gtk_widget_show_all(contents);

  contents_view_ = new views::NativeViewHost();
  // TODO(xiyuan): Find a better way to get proper background.
  contents_view_->set_background(views::Background::CreateSolidBackground(
      BubbleWindow::kBackgroundColor));
  AddChildView(contents_view_);
  contents_view_->Attach(contents);

  g_signal_connect(window()->GetNativeWindow(), "check-resize",
      G_CALLBACK(&OnCheckResizeThunk), this);

  const int padding = 2 * kDialogPadding;
  // Use gtk's default size if size is not specified.
  if (size_.IsEmpty()) {
    // Use given width or height if given.
    if (size_.width() || size_.height()) {
      int width = size_.width() == 0 ? -1 : size_.width() + padding;
      int height = size_.height() == 0 ? -1 : size_.height() + padding;
      gtk_widget_set_size_request(contents, width, height);
    }

    GtkRequisition requsition = { 0 };
    gtk_widget_size_request(contents, &requsition);
    preferred_size_.set_width(requsition.width);
    preferred_size_.set_height(requsition.height);
  } else {
    preferred_size_.set_width(size_.width() + padding);
    preferred_size_.set_height(size_.height() + padding);
  }

  CheckSize();

  if (default_widget)
    gtk_widget_grab_default(default_widget);

  if (focus_widget)
    gtk_widget_grab_focus(focus_widget);
}

void NativeDialogHost::CheckSize() {
  // Apply the minimum size.
  if (preferred_size_.width() < min_size_.width())
    preferred_size_.set_width(min_size_.width());
  if (preferred_size_.height() < min_size_.height())
    preferred_size_.set_height(min_size_.height());
}

void ShowNativeDialog(gfx::NativeWindow parent,
                      gfx::NativeView native_dialog,
                      int flags,
                      const gfx::Size& size,
                      const gfx::Size& min_size) {
  NativeDialogHost* native_dialog_host =
      new NativeDialogHost(native_dialog, flags, size, min_size);
  browser::CreateViewsWindow(parent, gfx::Rect(), native_dialog_host);
  native_dialog_host->window()->Show();
}

gfx::NativeWindow GetNativeDialogWindow(gfx::NativeView native_dialog) {
  NativeDialogHost* host = reinterpret_cast<NativeDialogHost*>(
      g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost));
  return host ? host->window()->GetNativeWindow() : NULL;
}

gfx::Rect GetNativeDialogContentsBounds(gfx::NativeView native_dialog) {
  NativeDialogHost* host = reinterpret_cast<NativeDialogHost*>(
      g_object_get_data(G_OBJECT(native_dialog), kNativeDialogHost));
  return host ? host->bounds() : gfx::Rect();
}

}  // namespace chromeos