/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2010 University of Szeged
 *
 * 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 "NetscapePlugin.h"

#include "WebEvent.h"
#include <WebCore/GraphicsContext.h>
#include <WebCore/NotImplemented.h>

#if PLATFORM(QT)
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmap>
#include <QX11Info>
#elif PLATFORM(GTK)
#include <gdk/gdkx.h>
#endif

using namespace WebCore;

namespace WebKit {

static Display *getPluginDisplay()
{
#if PLATFORM(QT)
    // At the moment, we only support gdk based plugins (like Flash) that use a different X connection.
    // The code below has the same effect as this one:
    // Display *gdkDisplay = gdk_x11_display_get_xdisplay(gdk_display_get_default());

    QLibrary library("libgdk-x11-2.0", 0);
    if (!library.load())
        return 0;

    typedef void *(*gdk_display_get_default_ptr)();
    gdk_display_get_default_ptr gdk_display_get_default = (gdk_display_get_default_ptr)library.resolve("gdk_display_get_default");
    if (!gdk_display_get_default)
        return 0;

    typedef void *(*gdk_x11_display_get_xdisplay_ptr)(void *);
    gdk_x11_display_get_xdisplay_ptr gdk_x11_display_get_xdisplay = (gdk_x11_display_get_xdisplay_ptr)library.resolve("gdk_x11_display_get_xdisplay");
    if (!gdk_x11_display_get_xdisplay)
        return 0;

    return (Display*)gdk_x11_display_get_xdisplay(gdk_display_get_default());
#elif PLATFORM(GTK)
    // Since we're a gdk/gtk app, we'll (probably?) have the same X connection as any gdk-based
    // plugins, so we can return that. We might want to add other implementations here later.
    return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
#else
    return 0;
#endif
}

static inline Display* x11Display()
{
#if PLATFORM(QT)
    return QX11Info::display();
#elif PLATFORM(GTK)
    return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
#else
    return 0;
#endif
}

static inline int displayDepth()
{
#if PLATFORM(QT)
    return QApplication::desktop()->x11Info().depth();
#elif PLATFORM(GTK)
    return gdk_visual_get_depth(gdk_screen_get_system_visual(gdk_screen_get_default()));
#else
    return 0;
#endif
}

static inline unsigned long rootWindowID()
{
#if PLATFORM(QT)
    return QX11Info::appRootWindow();
#elif PLATFORM(GTK)
    return GDK_ROOT_WINDOW();
#else
    return 0;
#endif
}

static inline int x11Screen()
{
#if PLATFORM(QT)
    return QX11Info::appScreen();
#elif PLATFORM(GTK)
    return gdk_screen_get_number(gdk_screen_get_default());
#else
    return 0;
#endif
}

bool NetscapePlugin::platformPostInitialize()
{
    if (m_isWindowed)
        return false;

    if (!(m_pluginDisplay = getPluginDisplay()))
        return false;

    NPSetWindowCallbackStruct* callbackStruct = new NPSetWindowCallbackStruct;
    callbackStruct->type = 0;
    Display* display = x11Display();
    int depth = displayDepth();
    callbackStruct->display = display;
    callbackStruct->depth = depth;

    XVisualInfo visualTemplate;
    visualTemplate.screen = x11Screen();
    visualTemplate.depth = depth;
    visualTemplate.c_class = TrueColor;
    int numMatching;
    XVisualInfo* visualInfo = XGetVisualInfo(display, VisualScreenMask | VisualDepthMask | VisualClassMask,
                                             &visualTemplate, &numMatching);
    ASSERT(visualInfo);
    Visual* visual = visualInfo[0].visual;
    ASSERT(visual);
    XFree(visualInfo);

    callbackStruct->visual = visual;
    callbackStruct->colormap = XCreateColormap(display, rootWindowID(), visual, AllocNone);

    m_npWindow.type = NPWindowTypeDrawable;
    m_npWindow.window = 0;
    m_npWindow.ws_info = callbackStruct;

    callSetWindow();

    return true;
}

void NetscapePlugin::platformDestroy()
{
    delete static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);

    if (m_drawable) {
        XFreePixmap(x11Display(), m_drawable);
        m_drawable = 0;
    }
}

bool NetscapePlugin::platformInvalidate(const IntRect&)
{
    notImplemented();
    return false;
}

void NetscapePlugin::platformGeometryDidChange()
{
    if (m_isWindowed) {
        notImplemented();
        return;
    }

    Display* display = x11Display();
    if (m_drawable)
        XFreePixmap(display, m_drawable);

    m_drawable = XCreatePixmap(display, rootWindowID(), m_frameRect.width(), m_frameRect.height(), displayDepth());

    XSync(display, false); // Make sure that the server knows about the Drawable.
}

void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool /*isSnapshot*/)
{
    if (m_isWindowed) {
        notImplemented();
        return;
    }

    if (!m_isStarted) {
        // FIXME: we should paint a missing plugin icon.
        return;
    }

    if (context->paintingDisabled())
        return;

    ASSERT(m_drawable);

#if PLATFORM(QT)
    QPainter* painter = context->platformContext();
    painter->translate(m_frameRect.x(), m_frameRect.y());
#else
    notImplemented();
    return;
#endif

    XEvent xevent;
    memset(&xevent, 0, sizeof(XEvent));
    XGraphicsExposeEvent& exposeEvent = xevent.xgraphicsexpose;
    exposeEvent.type = GraphicsExpose;
    exposeEvent.display = x11Display();
    exposeEvent.drawable = m_drawable;

    IntRect exposedRect(dirtyRect);
    exposedRect.intersect(m_frameRect);
    exposedRect.move(-m_frameRect.x(), -m_frameRect.y());
    exposeEvent.x = exposedRect.x();
    exposeEvent.y = exposedRect.y();

    // Note: in transparent mode Flash thinks width is the right and height is the bottom.
    // We should take it into account if we want to support transparency.
    exposeEvent.width = exposedRect.width();
    exposeEvent.height = exposedRect.height();

    NPP_HandleEvent(&xevent);

    if (m_pluginDisplay != x11Display())
        XSync(m_pluginDisplay, false);

#if PLATFORM(QT)
    QPixmap qtDrawable = QPixmap::fromX11Pixmap(m_drawable, QPixmap::ExplicitlyShared);
    ASSERT(qtDrawable.depth() == static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info)->depth);
    painter->drawPixmap(QPoint(exposedRect.x(), exposedRect.y()), qtDrawable, exposedRect);

    painter->translate(-m_frameRect.x(), -m_frameRect.y());
#endif
}

NPEvent toNP(const WebMouseEvent& event)
{
#if OS(SYMBIAN)
    NPEvent npEvent = QEvent(QEvent::None);
#else
    NPEvent npEvent = NPEvent();
#endif

    notImplemented();

    return npEvent;
}

bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& event)
{
    if (m_isWindowed)
        return false;

    NPEvent npEvent = toNP(event);
    NPP_HandleEvent(&npEvent);
    return true;
}

bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent&)
{
    notImplemented();
    return false;
}

void NetscapePlugin::platformSetFocus(bool)
{
    notImplemented();
}

bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& event)
{
    if (m_isWindowed)
        return false;

    NPEvent npEvent = toNP(event);
    NPP_HandleEvent(&npEvent);
    return true;
}

bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& event)
{
    if (m_isWindowed)
        return false;

    NPEvent npEvent = toNP(event);
    NPP_HandleEvent(&npEvent);
    return true;
}

bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent&)
{
    notImplemented();
    return false;
}

} // namespace WebKit
