/*
 * Copyright (C) 2011 Collabora Ltd.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "DragClientClutter.h"

#include "ClipboardClutter.h"
#include "DragController.h"
#include "DragData.h"
#include "DragSession.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameView.h"
#include "NotImplemented.h"
#include "PlatformMouseEvent.h"
#include "UtilClutter.h"
#include "webkitgwebframeprivate.h"
#include "webkitiwebview.h"
#include "webkitwebpageprivate.h"

using namespace WebCore;

namespace WebKit {

DragClientGObject* DragClientGObject::create(WebKitWebPage* webPage)
{
    return new DragClientClutter(webPage);
}

DragClientClutter::DragClientClutter(WebKitWebPage* webPage)
    : m_webPage(webPage)
    , m_dragOperation(DragOperationMove)
{
}

void DragClientClutter::onDragUpdate(const ClutterEvent *event)
{
    gfloat eventX, eventY;
    clutter_event_get_coords (event, &eventX, &eventY);

    IntPoint actorPosition, globalPosition;
    WebKitIWebView* view = webkit_web_page_get_view(m_webPage);
    UtilClutter::convertEventCoords(CLUTTER_ACTOR(view), eventX, eventY, actorPosition, globalPosition);
    DragData dragData(m_dataObject.get(), actorPosition, globalPosition, m_dragOperation);

    Frame* frame = core(m_webPage)->focusController()->focusedOrMainFrame();
    if (!frame)
        return;

    m_dragOperation = core(m_webPage)->dragController()->dragUpdated(&dragData).operation;

    eventX -= m_dragImageOffset.width();
    eventY -= m_dragImageOffset.height();
    clutter_actor_set_position(m_handleActor.get(), eventX, eventY);
}

void DragClientClutter::onDragEnd(const ClutterEvent *event, bool success)
{
    gfloat eventX, eventY;
    clutter_event_get_coords (event, &eventX, &eventY);

    ClutterStage* stage = clutter_event_get_stage(event);
    if (m_onCapturedEventId) {
        g_signal_handler_disconnect(stage, m_onCapturedEventId);
        m_onCapturedEventId = 0;
    }

    WebKitIWebView* view = webkit_web_page_get_view(m_webPage);
    Frame* frame = core(m_webPage)->focusController()->focusedOrMainFrame();
    if (frame) {

        // Source-side end-drag handling
        switch (clutter_event_type(event))
        {
            case CLUTTER_MOTION: {
                PlatformMouseEvent platformMouseEvent(CLUTTER_ACTOR(view), reinterpret_cast<const ClutterMotionEvent*>(event));
                frame->eventHandler()->dragSourceEndedAt(platformMouseEvent, m_dragOperation);
                break;
            }
            case CLUTTER_BUTTON_RELEASE: {
                PlatformMouseEvent platformMouseEvent(CLUTTER_ACTOR(view), reinterpret_cast<const ClutterButtonEvent*>(event));
                frame->eventHandler()->dragSourceEndedAt(platformMouseEvent, m_dragOperation);
                break;
            }
#if ENABLE(TOUCH_EVENTS)
            case CLUTTER_TOUCH_BEGIN:
            case CLUTTER_TOUCH_CANCEL:
            case CLUTTER_TOUCH_END: {
                PlatformTouchEvent platformTouchEvent(CLUTTER_ACTOR(view), reinterpret_cast<const ClutterTouchEvent*>(event));
                frame->eventHandler()->dragSourceEndedAt(platformTouchEvent, m_dragOperation);
                break;
            }
#endif
            default:
                ASSERT_NOT_REACHED();
        }
    }

    // Target-side drop handling
    if (success) {
        IntPoint actorPosition, globalPosition;
        UtilClutter::convertEventCoords(CLUTTER_ACTOR(view), eventX, eventY, actorPosition, globalPosition);
        DragData dragData(m_dataObject.get(), actorPosition, globalPosition, m_dragOperation);
        core(m_webPage)->dragController()->performDrag(&dragData);
    }

    core(m_webPage)->dragController()->dragEnded();
    clutter_actor_remove_child(CLUTTER_ACTOR(stage), m_handleActor.get());
    m_handleActor = 0;
    m_handleImage = 0;
}

gboolean DragClientClutter::onCapturedEvent(ClutterActor *stage, const ClutterEvent *event, gpointer data)
{
    DragClientClutter *self = static_cast<DragClientClutter*>(data);

    // Cancel the drag if we get a new touch point on the same device
    if (clutter_event_get_source_device(event) == self->m_startDragEventDevice &&
        clutter_event_type(event) == CLUTTER_TOUCH_BEGIN) {
        self->onDragEnd(event, false);
        return CLUTTER_EVENT_PROPAGATE;
    }

    if (clutter_event_get_source_device(event) != self->m_startDragEventDevice ||
        clutter_event_get_event_sequence(event) != self->m_startDragEventSequence)
        return CLUTTER_EVENT_PROPAGATE;

    ClutterModifierType mods = clutter_event_get_state (event);
    switch (clutter_event_type(event))
    {
        case CLUTTER_MOTION:
            if (mods & CLUTTER_BUTTON1_MASK)
                self->onDragUpdate(event);
            else
                self->onDragEnd(event, false);
            return CLUTTER_EVENT_STOP;
        case CLUTTER_TOUCH_UPDATE:
            self->onDragUpdate(event);
            return CLUTTER_EVENT_STOP;
        case CLUTTER_BUTTON_RELEASE:
            if (clutter_event_get_button(event) != CLUTTER_BUTTON_PRIMARY)
                return CLUTTER_EVENT_PROPAGATE;
            self->onDragEnd(event, true);
            return CLUTTER_EVENT_STOP;
        case CLUTTER_TOUCH_END:
            self->onDragEnd(event, true);
            return CLUTTER_EVENT_STOP;
        case CLUTTER_TOUCH_CANCEL:
            self->onDragEnd(event, false);
            return CLUTTER_EVENT_STOP;
        default:
            break;
    }
    return CLUTTER_EVENT_PROPAGATE;
}

gboolean DragClientClutter::onCanvasDraw(ClutterCanvas *canvas, cairo_t *cr, int width, int height, gpointer data)
{
    DragClientClutter *self = static_cast<DragClientClutter*>(data);
    cairo_surface_t* image = self->m_handleImage.get();
    g_return_val_if_fail(image != NULL, FALSE);
    cairo_set_source_surface(cr, image, 0, 0);
    cairo_paint(cr);
    return TRUE;
}

void DragClientClutter::startDrag(DragImageRef dragImage, const IntPoint& dragImageOrigin, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool linkDrag)
{
    const float defaultDragImageSize = 40.0;
    cairo_surface_t* image = dragImage;

    ClipboardClutter* clipboardClutter = reinterpret_cast<ClipboardClutter*>(clipboard);

    const ClutterEvent *event = clutter_get_current_event();
    m_startDragEventDevice = clutter_event_get_source_device (event);
    m_startDragEventSequence = clutter_event_get_event_sequence (event);
    float stageX, stageY;
    clutter_event_get_coords(event, &stageX, &stageY);

    m_dataObject = clipboardClutter->dataObject();

    WebKitIWebView *webView = getViewFromFrame(kit(frame));
#if ENABLE(GESTURE_EVENTS)
    webkit_iweb_view_cancel_gestures(webView);
#endif
    ClutterActor *stage = clutter_actor_get_stage(CLUTTER_ACTOR(webView));
    m_onCapturedEventId = g_signal_connect(stage, "captured-event", G_CALLBACK(onCapturedEvent), this);

    int width = defaultDragImageSize, height = defaultDragImageSize;
    if (image) {
        width = cairo_image_surface_get_width(image);
        height = cairo_image_surface_get_height(image);
        m_handleImage = image;
    }

    m_dragImageOffset = eventPos - dragImageOrigin;

    stageX -= m_dragImageOffset.width();
    stageY -= m_dragImageOffset.height();

    ClutterActor* handle = clutter_actor_new();
    clutter_actor_set_size (handle, width, height);
    clutter_actor_add_child(CLUTTER_ACTOR(stage), handle);
    clutter_actor_set_position(handle, stageX, stageY);
    clutter_actor_show(handle);
    m_handleActor = handle;

    if (image) {
        ClutterContent* canvas = clutter_canvas_new();
        g_signal_connect(canvas, "draw", G_CALLBACK(onCanvasDraw), this);
        clutter_canvas_set_size(CLUTTER_CANVAS(canvas), width, height);
        clutter_actor_set_content(handle, canvas);
    }
}

}
