/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qnativeevents.h"
#include <Carbon/Carbon.h>
#include <QtCore>

//  ************************************************************
//  Quartz
//  ************************************************************

static Qt::KeyboardModifiers getModifiersFromQuartzEvent(CGEventRef inEvent)
{
    Qt::KeyboardModifiers m;
    CGEventFlags flags = CGEventGetFlags(inEvent);
    if (flags & kCGEventFlagMaskShift || flags & kCGEventFlagMaskAlphaShift)
        m |= Qt::ShiftModifier;
    if (flags & kCGEventFlagMaskControl)
        m |= Qt::MetaModifier;
    if (flags & kCGEventFlagMaskAlternate)
        m |= Qt::AltModifier;
    if (flags & kCGEventFlagMaskCommand)
        m |= Qt::ControlModifier;
    return m;
}

static void setModifiersFromQNativeEvent(CGEventRef inEvent, const QNativeEvent &event)
{
    CGEventFlags flags = 0;
    if (event.modifiers.testFlag(Qt::ShiftModifier))
        flags |= kCGEventFlagMaskShift;
    if (event.modifiers.testFlag(Qt::MetaModifier))
        flags |= kCGEventFlagMaskControl;
    if (event.modifiers.testFlag(Qt::AltModifier))
        flags |= kCGEventFlagMaskAlternate;
    if (event.modifiers.testFlag(Qt::ControlModifier))
        flags |= kCGEventFlagMaskCommand;
    CGEventSetFlags(inEvent, flags);
}

static QPoint getMouseLocationFromQuartzEvent(CGEventRef inEvent)
{
    CGPoint pos = CGEventGetLocation(inEvent);
    QPoint tmp;
    tmp.setX(pos.x);
    tmp.setY(pos.y);
    return tmp;
}

static QChar getCharFromQuartzEvent(CGEventRef inEvent)
{
    UniCharCount count = 0;
    UniChar c;
    CGEventKeyboardGetUnicodeString(inEvent, 1, &count, &c);
    return QChar(c);
}

static CGEventRef EventHandler_Quartz(CGEventTapProxy proxy, CGEventType type, CGEventRef inEvent, void *refCon)
{
    Q_UNUSED(proxy);
    QNativeInput *nativeInput = static_cast<QNativeInput *>(refCon);
    switch (type){
        case kCGEventKeyDown:{
            QNativeKeyEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.nativeKeyCode = CGEventGetIntegerValueField(inEvent, kCGKeyboardEventKeycode);
            e.character = getCharFromQuartzEvent(inEvent);
            e.press = true;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventKeyUp:{
            QNativeKeyEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.nativeKeyCode = CGEventGetIntegerValueField(inEvent, kCGKeyboardEventKeycode);
            e.character = getCharFromQuartzEvent(inEvent);
            e.press = false;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventLeftMouseDown:{
            QNativeMouseButtonEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            e.clickCount = CGEventGetIntegerValueField(inEvent, kCGMouseEventClickState);
            e.button = Qt::LeftButton;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventLeftMouseUp:{
            QNativeMouseButtonEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            e.clickCount = 0;
            e.button = Qt::LeftButton;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventRightMouseDown:{
            QNativeMouseButtonEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            e.clickCount = CGEventGetIntegerValueField(inEvent, kCGMouseEventClickState);
            e.button = Qt::RightButton;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventRightMouseUp:{
            QNativeMouseButtonEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            e.clickCount = 0;
            e.button = Qt::RightButton;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventMouseMoved:{
            QNativeMouseMoveEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            nativeInput->notify(&e);
            break;
        }
        case kCGEventLeftMouseDragged:{
            QNativeMouseDragEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            e.clickCount = CGEventGetIntegerValueField(inEvent, kCGMouseEventClickState);
            e.button = Qt::LeftButton;
            nativeInput->notify(&e);
            break;
        }
        case kCGEventScrollWheel:{
            QNativeMouseWheelEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.delta = CGEventGetIntegerValueField(inEvent, kCGScrollWheelEventDeltaAxis1);
            e.globalPos = getMouseLocationFromQuartzEvent(inEvent);
            nativeInput->notify(&e);
            break;
        }
        case kCGEventFlagsChanged:{
            QNativeModifierEvent e;
            e.modifiers = getModifiersFromQuartzEvent(inEvent);
            e.nativeKeyCode = CGEventGetIntegerValueField(inEvent, kCGKeyboardEventKeycode);
            nativeInput->notify(&e);
            break;
        }

    }

    return inEvent;
}

Qt::Native::Status insertEventHandler_Quartz(QNativeInput *nativeInput, int pid = 0)
{
    uid_t uid = geteuid();
    if (uid != 0)
        qWarning("MacNativeEvents: You must be root to listen for key events!");

    CFMachPortRef port;
    if (!pid){
        port = CGEventTapCreate(kCGHIDEventTap,
            kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
            kCGEventMaskForAllEvents, EventHandler_Quartz, nativeInput);
    } else {
        ProcessSerialNumber psn;
        GetProcessForPID(pid, &psn);
        port = CGEventTapCreateForPSN(&psn,
            kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
            kCGEventMaskForAllEvents, EventHandler_Quartz, nativeInput);
    }

    CFRunLoopSourceRef eventSrc = CFMachPortCreateRunLoopSource(NULL, port, 0);
    CFRunLoopAddSource((CFRunLoopRef) GetCFRunLoopFromEventLoop(GetMainEventLoop()),
        eventSrc, kCFRunLoopCommonModes);

    return Qt::Native::Success;
}

Qt::Native::Status removeEventHandler_Quartz()
{
    return Qt::Native::Success; // ToDo:
}

Qt::Native::Status sendNativeKeyEventToProcess_Quartz(const QNativeKeyEvent &event, int pid)
{
    ProcessSerialNumber psn;
    GetProcessForPID(pid, &psn);

    CGEventRef e = CGEventCreateKeyboardEvent(0, (uint)event.nativeKeyCode, event.press);
    setModifiersFromQNativeEvent(e, event);
    SetFrontProcess(&psn);
    CGEventPostToPSN(&psn, e);
    CFRelease(e);
    return Qt::Native::Success;
}

Qt::Native::Status sendNativeKeyEvent_Quartz(const QNativeKeyEvent &event)
{
    CGEventRef e = CGEventCreateKeyboardEvent(0, (uint)event.nativeKeyCode, event.press);
    setModifiersFromQNativeEvent(e, event);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);
    return Qt::Native::Success;
}

Qt::Native::Status sendNativeMouseMoveEvent_Quartz(const QNativeMouseMoveEvent &event)
{
    CGPoint pos;
    pos.x = event.globalPos.x();
    pos.y = event.globalPos.y();

    CGEventRef e = CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, 0);
    setModifiersFromQNativeEvent(e, event);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);
    return Qt::Native::Success;
}

Qt::Native::Status sendNativeMouseButtonEvent_Quartz(const QNativeMouseButtonEvent &event)
{
    CGPoint pos;
    pos.x = event.globalPos.x();
    pos.y = event.globalPos.y();

    CGEventType type = 0;
    if (event.button == Qt::LeftButton)
        type = (event.clickCount > 0) ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
    else if (event.button == Qt::RightButton)
        type = (event.clickCount > 0) ? kCGEventRightMouseDown : kCGEventRightMouseUp;
    else
        type = (event.clickCount > 0) ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;

    CGEventRef e = CGEventCreateMouseEvent(0, type, pos, event.button);
    setModifiersFromQNativeEvent(e, event);
    CGEventSetIntegerValueField(e, kCGMouseEventClickState, event.clickCount);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);
    return Qt::Native::Success;
}

Qt::Native::Status sendNativeMouseDragEvent_Quartz(const QNativeMouseDragEvent &event)
{
    CGPoint pos;
    pos.x = event.globalPos.x();
    pos.y = event.globalPos.y();

    CGEventType type = 0;
    if (event.button == Qt::LeftButton)
        type = kCGEventLeftMouseDragged;
    else if (event.button == Qt::RightButton)
        type = kCGEventRightMouseDragged;
    else
        type = kCGEventOtherMouseDragged;

    CGEventRef e = CGEventCreateMouseEvent(0, type, pos, event.button);
    setModifiersFromQNativeEvent(e, event);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);
    return Qt::Native::Success;
}

Qt::Native::Status sendNativeMouseWheelEvent_Quartz(const QNativeMouseWheelEvent &event)
{
    CGPoint pos;
    pos.x = event.globalPos.x();
    pos.y = event.globalPos.y();

    CGEventRef e = CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitPixel, 1, 0);
    CGEventSetIntegerValueField(e, kCGScrollWheelEventDeltaAxis1, event.delta);
    CGEventSetLocation(e, pos);
    setModifiersFromQNativeEvent(e, event);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);

    return Qt::Native::Success;
}

Qt::Native::Status sendNativeModifierEvent_Quartz(const QNativeModifierEvent &event)
{
    CGEventRef e = CGEventCreateKeyboardEvent(0, (uint)event.nativeKeyCode, 0);
    CGEventSetType(e, kCGEventFlagsChanged);
    setModifiersFromQNativeEvent(e, event);
    CGEventPost(kCGHIDEventTap, e);
    CFRelease(e);
    return Qt::Native::Success;
}

//  ************************************************************
//  QNativeInput methods:
//  ************************************************************

Qt::Native::Status QNativeInput::sendNativeMouseButtonEvent(const QNativeMouseButtonEvent &event)
{
    return sendNativeMouseButtonEvent_Quartz(event);
}

Qt::Native::Status QNativeInput::sendNativeMouseMoveEvent(const QNativeMouseMoveEvent &event)
{
    return sendNativeMouseMoveEvent_Quartz(event);
}

Qt::Native::Status QNativeInput::sendNativeMouseDragEvent(const QNativeMouseDragEvent &event)
{
    return sendNativeMouseDragEvent_Quartz(event);
}

Qt::Native::Status QNativeInput::sendNativeMouseWheelEvent(const QNativeMouseWheelEvent &event)
{
    return sendNativeMouseWheelEvent_Quartz(event);
}

Qt::Native::Status QNativeInput::sendNativeKeyEvent(const QNativeKeyEvent &event, int pid)
{
    if (!pid)
        return sendNativeKeyEvent_Quartz(event);
    else
        return sendNativeKeyEventToProcess_Quartz(event, pid);
}

Qt::Native::Status QNativeInput::sendNativeModifierEvent(const QNativeModifierEvent &event)
{
    return sendNativeModifierEvent_Quartz(event);
}

Qt::Native::Status QNativeInput::subscribeForNativeEvents()
{
    return insertEventHandler_Quartz(this);
}

Qt::Native::Status QNativeInput::unsubscribeForNativeEvents()
{
    return removeEventHandler_Quartz();
}

// Some Qt to Mac mappings:
int QNativeKeyEvent::Key_A = 0;
int QNativeKeyEvent::Key_B = 11;
int QNativeKeyEvent::Key_C = 8;
int QNativeKeyEvent::Key_1 = 18;
int QNativeKeyEvent::Key_Backspace = 51;
int QNativeKeyEvent::Key_Enter = 36;
int QNativeKeyEvent::Key_Del = 117;