/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 * Portions Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
 * Copyright (C) 2011 Igalia S.L.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebViewWidget.h"

#include "GOwnPtrGtk.h"
#include "GtkVersioning.h"
#include "NotImplemented.h"
#include "RefPtrCairo.h"

using namespace WebKit;
using namespace WebCore;

static gpointer webViewWidgetParentClass = 0;

struct _WebViewWidgetPrivate {
    WebView* webViewInstance;
    GtkIMContext* imContext;
    gint currentClickCount;
    IntPoint previousClickPoint;
    guint previousClickButton;
    guint32 previousClickTime;
};

static void webViewWidgetRealize(GtkWidget* widget)
{
    gtk_widget_set_realized(widget, TRUE);

    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);

    GdkWindowAttr attributes;
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = allocation.x;
    attributes.y = allocation.y;
    attributes.width = allocation.width;
    attributes.height = allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual(widget);
#ifdef GTK_API_VERSION_2
    attributes.colormap = gtk_widget_get_colormap(widget);
#endif
    attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK
        | GDK_EXPOSURE_MASK
        | GDK_BUTTON_PRESS_MASK
        | GDK_BUTTON_RELEASE_MASK
        | GDK_POINTER_MOTION_MASK
        | GDK_KEY_PRESS_MASK
        | GDK_KEY_RELEASE_MASK
        | GDK_BUTTON_MOTION_MASK
        | GDK_BUTTON1_MOTION_MASK
        | GDK_BUTTON2_MOTION_MASK
        | GDK_BUTTON3_MOTION_MASK;

    gint attributesMask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
#ifdef GTK_API_VERSION_2
    attributesMask |= GDK_WA_COLORMAP;
#endif
    GdkWindow* window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributesMask);
    gtk_widget_set_window(widget, window);
    gdk_window_set_user_data(window, widget);

#ifdef GTK_API_VERSION_2
#if GTK_CHECK_VERSION(2, 20, 0)
    gtk_widget_style_attach(widget);
#else
    widget->style = gtk_style_attach(gtk_widget_get_style(widget), window);
#endif
    gtk_style_set_background(gtk_widget_get_style(widget), window, GTK_STATE_NORMAL);
#else
    gtk_style_context_set_background(gtk_widget_get_style_context(widget), window);
#endif

    WebViewWidget* webView = WEB_VIEW_WIDGET(widget);
    WebViewWidgetPrivate* priv = webView->priv;
    gtk_im_context_set_client_window(priv->imContext, window);
}

static void webViewWidgetContainerAdd(GtkContainer* container, GtkWidget* widget)
{
    gtk_widget_set_parent(widget, GTK_WIDGET(container));
}

static void webViewWidgetDispose(GObject* gobject)
{
    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(gobject);
    WebViewWidgetPrivate* priv = webViewWidget->priv;

    if (priv->imContext) {
        g_object_unref(priv->imContext);
        priv->imContext = 0;
    }

    G_OBJECT_CLASS(webViewWidgetParentClass)->dispose(gobject);
}

static void webViewWidgetInit(WebViewWidget* webViewWidget)
{
    WebViewWidgetPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(webViewWidget, WEB_VIEW_TYPE_WIDGET, WebViewWidgetPrivate);
    webViewWidget->priv = priv;

    gtk_widget_set_can_focus(GTK_WIDGET(webViewWidget), TRUE);
    priv->imContext = gtk_im_multicontext_new();

    priv->currentClickCount = 0;
    priv->previousClickButton = 0;
    priv->previousClickTime = 0;
}

#ifdef GTK_API_VERSION_2
static gboolean webViewExpose(GtkWidget* widget, GdkEventExpose* event)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    GdkRectangle clipRect;
    gdk_region_get_clipbox(event->region, &clipRect);

    GdkWindow* window = gtk_widget_get_window(widget);
    RefPtr<cairo_t> cr = adoptRef(gdk_cairo_create(window));

    webView->paint(widget, clipRect, cr.get());

    return FALSE;
}
#else
static gboolean webViewDraw(GtkWidget* widget, cairo_t* cr)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    GdkRectangle clipRect;

    if (!gdk_cairo_get_clip_rectangle(cr, &clipRect))
        return FALSE;

    webView->paint(widget, clipRect, cr);

    return FALSE;
}
#endif

static void webViewSizeAllocate(GtkWidget* widget, GtkAllocation* allocation)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    GTK_WIDGET_CLASS(webViewWidgetParentClass)->size_allocate(widget, allocation);
    webView->setSize(widget, IntSize(allocation->width, allocation->height));
}

static gboolean webViewFocusInEvent(GtkWidget* widget, GdkEventFocus* event)
{
    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);

    GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
    if (gtk_widget_is_toplevel(toplevel) && gtk_window_has_toplevel_focus(GTK_WINDOW(toplevel))) {
        gtk_im_context_focus_in(webViewWidgetGetIMContext(webViewWidget));
        webView->handleFocusInEvent(widget);
    }

    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_in_event(widget, event);
}

static gboolean webViewFocusOutEvent(GtkWidget* widget, GdkEventFocus* event)
{
    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);

    webView->handleFocusOutEvent(widget);
    GtkIMContext* imContext = webViewWidgetGetIMContext(webViewWidget);
    if (imContext)
        gtk_im_context_focus_out(imContext);

    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->focus_out_event(widget, event);
}

static gboolean webViewKeyPressEvent(GtkWidget* widget, GdkEventKey* event)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    webView->handleKeyboardEvent(event);

    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_press_event(widget, event);
}

static gboolean webViewKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event)
{
    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);

    if (gtk_im_context_filter_keypress(webViewWidgetGetIMContext(webViewWidget), event))
        return TRUE;

    webView->handleKeyboardEvent(event);

    return GTK_WIDGET_CLASS(webViewWidgetParentClass)->key_release_event(widget, event);
}

// Copied from webkitwebview.cpp
static guint32 getEventTime(GdkEvent* event)
{
    guint32 time = gdk_event_get_time(event);
    if (time)
        return time;

    // Real events always have a non-zero time, but events synthesized
    // by the DRT do not and we must calculate a time manually. This time
    // is not calculated in the DRT, because GTK+ does not work well with
    // anything other than GDK_CURRENT_TIME on synthesized events.
    GTimeVal timeValue;
    g_get_current_time(&timeValue);
    return (timeValue.tv_sec * 1000) + (timeValue.tv_usec / 1000);
}

static gboolean webViewButtonPressEvent(GtkWidget* widget, GdkEventButton* buttonEvent)
{
    if (buttonEvent->button == 3) {
        // FIXME: [GTK] Add context menu support for Webkit2.
        // https://bugs.webkit.org/show_bug.cgi?id=54827
        notImplemented();
        return FALSE;
    }

    gtk_widget_grab_focus(widget);

    // For double and triple clicks GDK sends both a normal button press event
    // and a specific type (like GDK_2BUTTON_PRESS). If we detect a special press
    // coming up, ignore this event as it certainly generated the double or triple
    // click. The consequence of not eating this event is two DOM button press events
    // are generated.
    GOwnPtr<GdkEvent> nextEvent(gdk_event_peek());
    if (nextEvent && (nextEvent->any.type == GDK_2BUTTON_PRESS || nextEvent->any.type == GDK_3BUTTON_PRESS))
        return TRUE;

    gint doubleClickDistance = 250;
    gint doubleClickTime = 5;
    GtkSettings* settings = gtk_settings_get_for_screen(gtk_widget_get_screen(widget));
    g_object_get(settings,
                 "gtk-double-click-distance", &doubleClickDistance,
                 "gtk-double-click-time", &doubleClickTime, NULL);

    // GTK+ only counts up to triple clicks, but WebCore wants to know about
    // quadruple clicks, quintuple clicks, ad infinitum. Here, we replicate the
    // GDK logic for counting clicks.
    GdkEvent* event(reinterpret_cast<GdkEvent*>(buttonEvent));
    guint32 eventTime = getEventTime(event);
    WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(widget);
    WebViewWidgetPrivate* priv = webViewWidget->priv;
    if ((event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
        || ((abs(buttonEvent->x - priv->previousClickPoint.x()) < doubleClickDistance)
            && (abs(buttonEvent->y - priv->previousClickPoint.y()) < doubleClickDistance)
            && (eventTime - priv->previousClickTime < static_cast<guint>(doubleClickTime))
            && (buttonEvent->button == priv->previousClickButton)))
        priv->currentClickCount++;
    else
        priv->currentClickCount = 1;

    WebView* webView = webViewWidgetGetWebViewInstance(webViewWidget);
    webView->handleMouseEvent(event, priv->currentClickCount);

    gdouble x, y;
    gdk_event_get_coords(event, &x, &y);
    priv->previousClickPoint = IntPoint(x, y);
    priv->previousClickButton = buttonEvent->button;
    priv->previousClickTime = eventTime;

    return FALSE;
}

static gboolean webViewButtonReleaseEvent(GtkWidget* widget, GdkEventButton* event)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    gtk_widget_grab_focus(widget);
    webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);

    return FALSE;
}

static gboolean webViewScrollEvent(GtkWidget* widget, GdkEventScroll* event)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    webView->handleWheelEvent(event);

    return FALSE;
}

static gboolean webViewMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event)
{
    WebView* webView = webViewWidgetGetWebViewInstance(WEB_VIEW_WIDGET(widget));
    webView->handleMouseEvent(reinterpret_cast<GdkEvent*>(event), 0 /* currentClickCount */);

    return FALSE;
}

static void webViewWidgetClassInit(WebViewWidgetClass* webViewWidgetClass)
{
    webViewWidgetParentClass = g_type_class_peek_parent(webViewWidgetClass);

    GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(webViewWidgetClass);
    widgetClass->realize = webViewWidgetRealize;
#ifdef GTK_API_VERSION_2
    widgetClass->expose_event = webViewExpose;
#else
    widgetClass->draw = webViewDraw;
#endif
    widgetClass->size_allocate = webViewSizeAllocate;
    widgetClass->focus_in_event = webViewFocusInEvent;
    widgetClass->focus_out_event = webViewFocusOutEvent;
    widgetClass->key_press_event = webViewKeyPressEvent;
    widgetClass->key_release_event = webViewKeyReleaseEvent;
    widgetClass->button_press_event = webViewButtonPressEvent;
    widgetClass->button_release_event = webViewButtonReleaseEvent;
    widgetClass->scroll_event = webViewScrollEvent;
    widgetClass->motion_notify_event = webViewMotionNotifyEvent;

    GObjectClass* gobjectClass = G_OBJECT_CLASS(webViewWidgetClass);
    gobjectClass->dispose = webViewWidgetDispose;

    GtkContainerClass* containerClass = GTK_CONTAINER_CLASS(webViewWidgetClass);
    containerClass->add = webViewWidgetContainerAdd;

    g_type_class_add_private(webViewWidgetClass, sizeof(WebViewWidgetPrivate));
}

GType webViewWidgetGetType()
{
    static volatile gsize gDefineTypeIdVolatile = 0;

    if (!g_once_init_enter(&gDefineTypeIdVolatile))
        return gDefineTypeIdVolatile;

    GType gDefineTypeId = g_type_register_static_simple(GTK_TYPE_CONTAINER,
                                                        g_intern_static_string("WebViewWidget"),
                                                        sizeof(WebViewWidgetClass),
                                                        reinterpret_cast<GClassInitFunc>(webViewWidgetClassInit),
                                                        sizeof(WebViewWidget),
                                                        reinterpret_cast<GInstanceInitFunc>(webViewWidgetInit),
                                                        static_cast<GTypeFlags>(0));
    g_once_init_leave(&gDefineTypeIdVolatile, gDefineTypeId);

    return gDefineTypeIdVolatile;
}

WebView* webViewWidgetGetWebViewInstance(WebViewWidget* webViewWidget)
{
    return webViewWidget->priv->webViewInstance;
}

void webViewWidgetSetWebViewInstance(WebViewWidget* webViewWidget, WebView* webViewInstance)
{
    webViewWidget->priv->webViewInstance = webViewInstance;
}

GtkIMContext* webViewWidgetGetIMContext(WebViewWidget* webViewWidget)
{
    return webViewWidget->priv->imContext;
}