/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of a Qt Solutions component.
**
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * 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.
**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
**     the names of its contributors may be used to endorse or promote
**     products derived from this software without specific prior written
**     permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
** OWNER OR 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 <QtGui/QApplication>
#include <QtCore/QStringList>
#include <QtCore/QPointer>
#include <QtGui/QWidget>
#include <QtCore/QEvent>
#include <QtCore/QDebug>
#include <QtCore/QRect>
#include <QtGui/QMouseEvent>
#include <QtGui/QHoverEvent>
#include <QtGui/QKeyEvent>

#include <Carbon/Carbon.h>

#include "qtnpapi.h"

#include "qtbrowserplugin.h"
#include "qtbrowserplugin_p.h"

static bool ownsqapp = false;
extern void qt_mac_set_native_menubar(bool b);
const UInt32 kWidgetCreatorQt = 'cute';
enum {
    kWidgetPropertyQWidget = 'QWId' //QWidget *
};

class
QMacBrowserRoot : public QWidget
{
    Q_OBJECT
public:
    QMacBrowserRoot(HIViewRef root) : QWidget()
    {
        // make sure we're not registered with Qt before create
        WindowRef window = HIViewGetWindow(root);
        QWidget *oldwindow=0;
        OSErr err;
        err = GetWindowProperty(window,
                                kWidgetCreatorQt,
                                kWidgetPropertyQWidget,
                                sizeof(oldwindow),
                                0,
                                &oldwindow);
        if (err == noErr)
            RemoveWindowProperty(window, kWidgetCreatorQt, kWidgetPropertyQWidget);

        create((WId)root);

        // re-register the root window with Qt
        err = SetWindowProperty(window,
                                kWidgetCreatorQt,
                                kWidgetPropertyQWidget,
                                sizeof(oldwindow),
                                &oldwindow);
        if (err != noErr) {
            qWarning("Error, couldn't register Window with Qt: (%s:%d:%d)", __FILE__, __LINE__, err);
        }

        QPalette pal = palette();
        pal.setColor(QPalette::Window,Qt::transparent);
        setPalette(pal);

        setAttribute(Qt::WA_WState_Polished);
    }

    ~QMacBrowserRoot() { }
};
#include "qtbrowserplugin_mac.moc"

struct key_sym
{
    int mac_code;
    int qt_code;
    const char *desc;
};

static key_sym modifier_syms[] = {
{ shiftKey, Qt::ShiftModifier, "Qt::ShiftModifier" },
{ controlKey, Qt::MetaModifier, "Qt::MetaModifier" },
{ rightControlKey, Qt::MetaModifier, "Qt::MetaModifier" },
{ optionKey, Qt::AltModifier, "Qt::AltModifier" },
{ rightOptionKey, Qt::AltModifier, "Qt::AltModifier" },
{ cmdKey, Qt::ControlModifier, "Qt::ControlModifier" },
{   0, 0, NULL }
};
static Qt::KeyboardModifiers get_modifiers(int key)
{
    Qt::KeyboardModifiers ret = 0;
    for(int i = 0; modifier_syms[i].desc; i++) {
        if(key & modifier_syms[i].mac_code) {
            ret |= Qt::KeyboardModifier(modifier_syms[i].qt_code);
        }
    }
    return ret;
}

static key_sym key_syms[] = {
{ kHomeCharCode, Qt::Key_Home, "Qt::Home" },
{ kEnterCharCode, Qt::Key_Enter, "Qt::Key_Enter" },
{ kEndCharCode, Qt::Key_End, "Qt::Key_End" },
{ kBackspaceCharCode, Qt::Key_Backspace, "Qt::Backspace" },
{ kTabCharCode, Qt::Key_Tab, "Qt::Tab" },
{ kPageUpCharCode, Qt::Key_PageUp, "Qt::PageUp" },
{ kPageDownCharCode, Qt::Key_PageDown, "Qt::PageDown" },
{ kReturnCharCode, Qt::Key_Return, "Qt::Key_Return" },
//function keys?
{ kEscapeCharCode, Qt::Key_Escape, "Qt::Key_Escape" },
{ kLeftArrowCharCode, Qt::Key_Left, "Qt::Key_Left" },
{ kRightArrowCharCode, Qt::Key_Right, "Qt::Key_Right" },
{ kUpArrowCharCode, Qt::Key_Up, "Qt::Key_Up" },
{ kDownArrowCharCode, Qt::Key_Down, "Qt::Key_Down" },
{ kDeleteCharCode, Qt::Key_Delete, "Qt::Key_Delete" }
};
static int get_key(int key)
{
    for(int i = 0; key_syms[i].desc; i++) {
        if(key_syms[i].mac_code == key) {
            return key_syms[i].qt_code;
        }
    }
    return key;
}

struct qt_last_mouse_down_struct {
    unsigned int when;
    int x, y;
} qt_last_mouse_down = { 0, 0, 0 };

//nasty, copied code -
static void qt_dispatchEnterLeave(QWidget* enter, QWidget* leave) {
#if 0
    if(leave) {
        QEvent e(QEvent::Leave);
        QApplication::sendEvent(leave, & e);
    }
    if(enter) {
        QEvent e(QEvent::Enter);
        QApplication::sendEvent(enter, & e);
    }
    return;
#endif

    QWidget* w ;
    if(!enter && !leave)
        return;
    QWidgetList leaveList;
    QWidgetList enterList;

    bool sameWindow = leave && enter && leave->window() == enter->window();
    if(leave && !sameWindow) {
        w = leave;
        do {
            leaveList.append(w);
        } while(!w->isWindow() && (w = w->parentWidget()));
    }
    if(enter && !sameWindow) {
        w = enter;
        do {
            enterList.prepend(w);
        } while(!w->isWindow() && (w = w->parentWidget()));
    }
    if(sameWindow) {
        int enterDepth = 0;
        int leaveDepth = 0;
        w = enter;
        while(!w->isWindow() && (w = w->parentWidget()))
            enterDepth++;
        w = leave;
        while(!w->isWindow() && (w = w->parentWidget()))
            leaveDepth++;
        QWidget* wenter = enter;
        QWidget* wleave = leave;
        while(enterDepth > leaveDepth) {
            wenter = wenter->parentWidget();
            enterDepth--;
        }
        while(leaveDepth > enterDepth) {
            wleave = wleave->parentWidget();
            leaveDepth--;
        }
        while(!wenter->isWindow() && wenter != wleave) {
            wenter = wenter->parentWidget();
            wleave = wleave->parentWidget();
        }

        w = leave;
        while(w != wleave) {
            leaveList.append(w);
            w = w->parentWidget();
        }
        w = enter;
        while(w != wenter) {
            enterList.prepend(w);
            w = w->parentWidget();
        }
    }

    QEvent leaveEvent(QEvent::Leave);
    for (int i = 0; i < leaveList.size(); ++i) {
        w = leaveList.at(i);
        QApplication::sendEvent(w, &leaveEvent);
#if 0
        if(w->testAttribute(Qt::WA_Hover)) {
            Q_ASSERT(instance());
            QHoverEvent he(QEvent::HoverLeave, QPoint(-1, -1),
                           w->mapFromGlobal(QApplicationPrivate::instance()->hoverGlobalPos));
            QApplication::sendEvent(w, &he);
        }
#endif
    }
    QPoint posEnter = QCursor::pos();
    QEvent enterEvent(QEvent::Enter);
    for (int i = 0; i < enterList.size(); ++i) {
        w = enterList.at(i);
        QApplication::sendEvent(w, &enterEvent);
        if(w->testAttribute(Qt::WA_Hover)) {
            QHoverEvent he(QEvent::HoverEnter, w->mapFromGlobal(posEnter), QPoint(-1, -1));
            QApplication::sendEvent(w, &he);
        }
    }
}


extern "C" bool qtns_event(QtNPInstance *This, NPEvent *event)
{
    static QPointer<QWidget> lastWidget;
    static QPointer<QWidget> qt_button_down;
    static Point lastPosition = { 0, 0 };
    if(event->what == nullEvent || event->what == adjustCursorEvent) {
        if(event->what == nullEvent) {
            qApp->processEvents();
            QApplication::sendPostedEvents();
        }

        //watch for mouse moves
       Point currentPosition;
        GetMouse(&currentPosition);
        LocalToGlobal(&currentPosition);
        if(currentPosition.h != lastPosition.h || currentPosition.v != lastPosition.v) {
            lastPosition = currentPosition;

            WindowPtr wp;
            FindWindow(currentPosition, &wp);
            QWidget *widget = 0;
            if(wp == GetWindowFromPort((CGrafPtr)This->window->port))
                widget = This->rootWidget->childAt(This->rootWidget->mapFromGlobal(QPoint(event->where.h, event->where.v)));
            else
                widget = QApplication::widgetAt(event->where.h, event->where.v);
            if(widget != lastWidget) {
                qt_dispatchEnterLeave(widget, lastWidget);
                lastWidget = widget;
            }
            if(widget) {
                QPoint p(currentPosition.h, currentPosition.v);
                QPoint plocal(widget->mapFromGlobal(p));
                QMouseEvent qme(QEvent::MouseMove, plocal, p, Button() ? Qt::LeftButton : Qt::NoButton,
                                0, get_modifiers(GetCurrentKeyModifiers()));
                QApplication::sendEvent(widget, &qme);
            }
        }
        return true;
    } else if(QWidget *widget = qobject_cast<QWidget*>(This->qt.object)) {
        if(event->what == updateEvt) {
            widget->repaint();
            return true;
        } else if(event->what == keyUp || event->what == keyDown) {
            QWidget *widget = 0;
            if(QWidget::keyboardGrabber())
                widget = QWidget::keyboardGrabber();
            else if(QApplication::focusWidget())
                widget = QApplication::focusWidget();
            else //last ditch effort
                widget = QApplication::widgetAt(event->where.h, event->where.v);

            if(widget) {
#if 0
                if(app_do_modal && !qt_try_modal(widget, er))
                    return 1;
#endif

                int mychar=get_key(event->message & charCodeMask);
                QEvent::Type etype = event->what == keyUp ? QEvent::KeyRelease : QEvent::KeyPress;
                QKeyEvent ke(etype, mychar, get_modifiers(event->modifiers), QString(QChar(mychar)));
                QApplication::sendEvent(widget,&ke);
                return true;
            }
        } else if(event->what == mouseDown  || event->what == mouseUp) {
            QEvent::Type etype = QEvent::None;
            Qt::KeyboardModifiers keys = get_modifiers(event->modifiers);
            Qt::MouseButton button = Qt::LeftButton;

            if(event->what == mouseDown) {
                if (lastWidget)
                    qt_button_down = lastWidget;
                //check if this is the second click, there must be a way to make the
                //mac do this for us, FIXME!!
                if(qt_last_mouse_down.when &&
                   (event->when - qt_last_mouse_down.when <= (uint)QApplication::doubleClickInterval())) {
                    int x = event->where.h, y = event->where.v;
                    if(x >= (qt_last_mouse_down.x-2) && x <= (qt_last_mouse_down.x+4) &&
                       y >= (qt_last_mouse_down.y-2) && y <= (qt_last_mouse_down.y+4)) {
                        etype = QEvent::MouseButtonDblClick;
                        qt_last_mouse_down.when = 0;
                    }
                }

                if(etype == QEvent::None) { //guess it's just a press
                    etype = QEvent::MouseButtonPress;
                    qt_last_mouse_down.when = event->when;
                    qt_last_mouse_down.x = event->where.h;
                    qt_last_mouse_down.y = event->where.v;
                }
            } else {
                etype = QEvent::MouseButtonRelease;
            }

            WindowPtr wp;
            FindWindow(event->where, &wp);

            //handle popup's first
            QWidget *popupwidget = NULL;
            if(QApplication::activePopupWidget()) {
                if(wp) {
                    QWidget *clt=QWidget::find((WId)wp);
                    if(clt && clt->windowType() == Qt::Popup)
                        popupwidget = clt;
                }
                if(!popupwidget)
                    popupwidget = QApplication::activePopupWidget();
                if(QWidget *child = popupwidget->childAt(popupwidget->mapFromGlobal(QPoint(event->where.h, event->where.v))))
                    popupwidget = child;

                QPoint p(event->where.h, event->where.v);
                QPoint plocal(popupwidget->mapFromGlobal(p));
                QMouseEvent qme(etype, plocal, p, button, 0, keys);
                QApplication::sendEvent(popupwidget, &qme);
            }

            {
                QWidget *widget = 0; //figure out which widget to send it to
                if(event->what == mouseUp && qt_button_down)
                    widget = qt_button_down;
                else if(QWidget::mouseGrabber())
                    widget = QWidget::mouseGrabber();
                else if(wp == GetWindowFromPort((CGrafPtr)This->window->port))
                    widget = This->rootWidget->childAt(This->rootWidget->mapFromGlobal(QPoint(event->where.h, event->where.v)));
                else
                    widget = QApplication::widgetAt(event->where.h, event->where.v);

                //setup the saved widget
                qt_button_down = event->what == mouseDown ? widget : 0;

                //finally send the event to the widget if its not the popup
                if(widget && widget != popupwidget) {
#if 0
                    if(app_do_modal && !qt_try_modal(widget, er))
                        return 1;
#endif
                    if(event->what == mouseDown) {
                        QWidget* w = widget;
                        while(w->focusProxy())
                            w = w->focusProxy();
                        if(w->focusPolicy() & Qt::ClickFocus)
                            w->setFocus(Qt::MouseFocusReason);
                        if(QWidget *tlw = widget->topLevelWidget()) {
                            tlw->raise();
                            if(tlw->isTopLevel() && tlw->windowType() != Qt::Popup &&
                               (tlw->isModal() || tlw->windowType() != Qt::Dialog))
                                QApplication::setActiveWindow(tlw);
                        }
                    }

                    QPoint p(event->where.h, event->where.v);
                    QPoint plocal(widget->mapFromGlobal( p ));
                    QMouseEvent qme(etype, plocal, p, button, 0, keys);
                    QApplication::sendEvent(widget, &qme);
                    return true;
                }
            }
        } else {
            //qDebug("%d", event->what);
        }
    }
    return false;
}

#ifdef QTBROWSER_USE_CFM
static bool qtbrowser_use_cfm = false;
static UInt32 gGlueTemplate[6] = { 0x3D800000, 0x618C0000, 0x800C0000,
                            0x804C0004, 0x7C0903A6, 0x4E800420  };
struct TVector_rec
{
    ProcPtr fProcPtr;
    void *fTOC;
};

void *CFMFunctionPointerForMachOFunctionPointer(void *inMachProcPtr)
{
    if(!qtbrowser_use_cfm)
        return inMachProcPtr;
     TVector_rec *vTVector = (TVector_rec*)malloc(sizeof(TVector_rec));
     if(MemError() == noErr && vTVector != 0) {
         vTVector->fProcPtr = (ProcPtr)inMachProcPtr;
         vTVector->fTOC = 0;  // ignored
     }
     return((void *)vTVector);
}

void DisposeCFMFunctionPointer(void *inCfmProcPtr)
{
    if(!qtbrowser_use_cfm)
        return;
    if(inCfmProcPtr)
        free(inCfmProcPtr);
}

void* MachOFunctionPointerForCFMFunctionPointer(void* inCfmProcPtr)
{
    if(!qtbrowser_use_cfm)
        return inCfmProcPtr;
    UInt32 *vMachProcPtr = (UInt32*)NewPtr(sizeof(gGlueTemplate));
    vMachProcPtr[0] = gGlueTemplate[0] | ((UInt32)inCfmProcPtr >> 16);
    vMachProcPtr[1] = gGlueTemplate[1] | ((UInt32)inCfmProcPtr & 0xFFFF);
    vMachProcPtr[2] = gGlueTemplate[2];
    vMachProcPtr[3] = gGlueTemplate[3];
    vMachProcPtr[4] = gGlueTemplate[4];
    vMachProcPtr[5] = gGlueTemplate[5];
    MakeDataExecutable(vMachProcPtr, sizeof(gGlueTemplate));
    return(vMachProcPtr);
}
#endif


extern "C" void qtns_initialize(QtNPInstance *)
{
    qt_mac_set_native_menubar(false);
    if(!qApp) {
        ownsqapp = true;
        static int argc=0;
        static char **argv={ 0 };
        (void)new QApplication(argc, argv);
    }
}

extern "C" void qtns_destroy(QtNPInstance *This)
{
    delete This->rootWidget;
    This->rootWidget = 0;
}

extern "C" void qtns_shutdown()
{
    if(!ownsqapp)
        return;

    // TODO: find out if other plugin DLL's still need qApp
    delete qApp;
    ownsqapp = false;
}

extern "C" void qtns_embed(QtNPInstance *This)
{
    Q_ASSERT(qobject_cast<QWidget*>(This->qt.object));

    WindowPtr windowptr = GetWindowFromPort((CGrafPtr)This->window->port);
    HIViewRef root = 0;
    OSErr err;
    err = GetRootControl(windowptr,&root);
    if(!root)
        root = HIViewGetRoot(windowptr);
    if(!root) {
        qDebug("No window composition!");
    } else {
        This->rootWidget = new QMacBrowserRoot(root);
        This->qt.widget->setParent(This->rootWidget);
    }
}

extern "C" void qtns_setGeometry(QtNPInstance *This, const QRect &rect, const QRect &clipRect)
{
    Q_ASSERT(qobject_cast<QWidget*>(This->qt.object));

    WindowPtr windowptr = GetWindowFromPort((CGrafPtr)This->window->port);
    Rect content_r;
    GetWindowBounds(windowptr, kWindowContentRgn, &content_r);
    Rect structure_r;
    GetWindowBounds(windowptr, kWindowStructureRgn, &structure_r);

    QRect geom(rect.translated(content_r.left-structure_r.left, content_r.top-structure_r.top));
    if(rect != clipRect) {
        QRegion clipRegion(QRect(clipRect.x()-geom.x(), clipRect.y()-geom.y(), clipRect.width(), clipRect.height())
                           .translated(content_r.left-structure_r.left, content_r.top-structure_r.top));
        if(clipRegion.isEmpty())
            clipRegion = QRegion(-1, -1, 1, 1); //eww ### FIXME
        This->qt.widget->setMask(clipRegion);
    } else {
        This->qt.widget->clearMask();
    }
    This->qt.widget->setGeometry(geom);
}

typedef void (*NPP_ShutdownUPP)(void);

extern "C" void NPP_MacShutdown()
{
    //extern NPError NP_Shutdown();
    //NP_Shutdown();
}

extern "C" int main(NPNetscapeFuncs *npn_funcs, NPPluginFuncs *np_funcs, NPP_ShutdownUPP *shutdown)
{
    qtbrowser_use_cfm = true; //quite the heuristic..

    NPError ret;
    extern NPError NP_Initialize(NPNetscapeFuncs*);
    if((ret=NP_Initialize(npn_funcs)) != NPERR_NO_ERROR)
        return ret;
    extern NPError NP_GetEntryPoints(NPPluginFuncs*);
    if((ret=NP_GetEntryPoints(np_funcs)) != NPERR_NO_ERROR)
        return ret;
    *shutdown = (NPP_ShutdownUPP)MAKE_FUNCTION_POINTER(NPP_MacShutdown);
    return NPERR_NO_ERROR;
}
