/*
 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "awt.h"
#include "awt_Toolkit.h"
#include "awt_Component.h"
#include "awt_Robot.h"
#include "sun_awt_windows_WRobotPeer.h"
#include "java_awt_event_InputEvent.h"
#include <winuser.h>

AwtRobot::AwtRobot( jobject peer )
{
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    m_peerObject = env->NewWeakGlobalRef(peer);
    JNU_CHECK_EXCEPTION(env);
    JNI_SET_PDATA(peer, this);
}

AwtRobot::~AwtRobot()
{
}

#ifndef SPI_GETMOUSESPEED
#define SPI_GETMOUSESPEED 112
#endif

#ifndef SPI_SETMOUSESPEED
#define SPI_SETMOUSESPEED 113
#endif

void AwtRobot::MouseMove( jint x, jint y)
{
    // Fix for Bug 4288230. See Q193003 from MSDN.
      int oldAccel[3], newAccel[3];
      INT_PTR oldSpeed, newSpeed;
      BOOL bResult;

   // The following values set mouse ballistics to 1 mickey/pixel.
      newAccel[0] = 0;
      newAccel[1] = 0;
      newAccel[2] = 0;
      newSpeed = 10;

      // Save the Current Mouse Acceleration Constants
      bResult = SystemParametersInfo(SPI_GETMOUSE,0,oldAccel,0);
      bResult = SystemParametersInfo(SPI_GETMOUSESPEED, 0, &oldSpeed,0);
      // Set the new Mouse Acceleration Constants (Disabled).
      bResult = SystemParametersInfo(SPI_SETMOUSE,0,newAccel,SPIF_SENDCHANGE);
      bResult = SystemParametersInfo(SPI_SETMOUSESPEED, 0,
                // 4504963: Though the third argument to SystemParameterInfo is
                // declared as a PVOID, as of Windows 2000 it is apparently
                // interpreted as an int.  (The MSDN docs for SPI_SETMOUSESPEED
                // say that it's an integer between 1 and 20, the default being
                // 10).  Instead of passing the @ of the desired value, the
                // value itself is now passed, cast as a PVOID so as to
                // compile.  -bchristi 10/02/2001
                                     (PVOID)newSpeed,
                                     SPIF_SENDCHANGE);

      int primaryIndex = AwtWin32GraphicsDevice::GetDefaultDeviceIndex();
      Devices::InstanceAccess devices;
      AwtWin32GraphicsDevice *device = devices->GetDevice(primaryIndex);

      x = (device == NULL) ? x : device->ScaleUpX(x);
      y = (device == NULL) ? y : device->ScaleUpY(y);

      POINT curPos;
      ::GetCursorPos(&curPos);
      x -= curPos.x;
      y -= curPos.y;

      mouse_event(MOUSEEVENTF_MOVE,x,y,0,0);
      // Move the cursor to the desired coordinates.

      // Restore the old Mouse Acceleration Constants.
      bResult = SystemParametersInfo(SPI_SETMOUSE,0, oldAccel, SPIF_SENDCHANGE);
      bResult = SystemParametersInfo(SPI_SETMOUSESPEED, 0, (PVOID)oldSpeed,
                                     SPIF_SENDCHANGE);
}

void AwtRobot::MousePress( jint buttonMask )
{
    DWORD dwFlags = 0L;
    // According to MSDN: Software Driving Software
    // application should consider SM_SWAPBUTTON to correctly emulate user with
    // left handed mouse setup
    BOOL bSwap = ::GetSystemMetrics(SM_SWAPBUTTON);

    if ( buttonMask & java_awt_event_InputEvent_BUTTON1_MASK ||
        buttonMask & java_awt_event_InputEvent_BUTTON1_DOWN_MASK)
    {
        dwFlags |= !bSwap ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_RIGHTDOWN;
    }

    if ( buttonMask & java_awt_event_InputEvent_BUTTON3_MASK ||
         buttonMask & java_awt_event_InputEvent_BUTTON3_DOWN_MASK)
    {
        dwFlags |= !bSwap ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_LEFTDOWN;
    }

    if ( buttonMask & java_awt_event_InputEvent_BUTTON2_MASK ||
         buttonMask & java_awt_event_InputEvent_BUTTON2_DOWN_MASK)
    {
        dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
    }

    INPUT mouseInput = {0};
    mouseInput.type = INPUT_MOUSE;
    mouseInput.mi.time = 0;
    mouseInput.mi.dwFlags = dwFlags;
    if ( buttonMask & AwtComponent::masks[3] ) {
        mouseInput.mi.dwFlags = mouseInput.mi.dwFlags | MOUSEEVENTF_XDOWN;
        mouseInput.mi.mouseData = XBUTTON1;
    }

    if ( buttonMask & AwtComponent::masks[4] ) {
        mouseInput.mi.dwFlags = mouseInput.mi.dwFlags | MOUSEEVENTF_XDOWN;
        mouseInput.mi.mouseData = XBUTTON2;
    }
    ::SendInput(1, &mouseInput, sizeof(mouseInput));
}

void AwtRobot::MouseRelease( jint buttonMask )
{
    DWORD dwFlags = 0L;
    // According to MSDN: Software Driving Software
    // application should consider SM_SWAPBUTTON to correctly emulate user with
    // left handed mouse setup
    BOOL bSwap = ::GetSystemMetrics(SM_SWAPBUTTON);

    if ( buttonMask & java_awt_event_InputEvent_BUTTON1_MASK ||
        buttonMask & java_awt_event_InputEvent_BUTTON1_DOWN_MASK)
    {
        dwFlags |= !bSwap ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_RIGHTUP;
    }

    if ( buttonMask & java_awt_event_InputEvent_BUTTON3_MASK ||
         buttonMask & java_awt_event_InputEvent_BUTTON3_DOWN_MASK)
    {
        dwFlags |= !bSwap ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_LEFTUP;
    }

    if ( buttonMask & java_awt_event_InputEvent_BUTTON2_MASK ||
        buttonMask & java_awt_event_InputEvent_BUTTON2_DOWN_MASK)
    {
        dwFlags |= MOUSEEVENTF_MIDDLEUP;
    }

    INPUT mouseInput = {0};
    mouseInput.type = INPUT_MOUSE;
    mouseInput.mi.time = 0;
    mouseInput.mi.dwFlags = dwFlags;

    if ( buttonMask & AwtComponent::masks[3] ) {
        mouseInput.mi.dwFlags = mouseInput.mi.dwFlags | MOUSEEVENTF_XUP;
        mouseInput.mi.mouseData = XBUTTON1;
    }

    if ( buttonMask & AwtComponent::masks[4] ) {
        mouseInput.mi.dwFlags = mouseInput.mi.dwFlags | MOUSEEVENTF_XUP;
        mouseInput.mi.mouseData = XBUTTON2;
    }
    ::SendInput(1, &mouseInput, sizeof(mouseInput));
}

void AwtRobot::MouseWheel (jint wheelAmt) {
    mouse_event(MOUSEEVENTF_WHEEL, 0, 0, wheelAmt * -1 * WHEEL_DELTA, 0);
}

inline jint AwtRobot::WinToJavaPixel(USHORT r, USHORT g, USHORT b)
{
    jint value =
            0xFF << 24 | // alpha channel is always turned all the way up
            r << 16 |
            g << 8  |
            b << 0;
    return value;
}

void AwtRobot::GetRGBPixels(jint x, jint y, jint width, jint height, jintArray pixelArray)
{
    DASSERT(width > 0 && height > 0);

    HDC hdcScreen = ::CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
    HDC hdcMem = ::CreateCompatibleDC(hdcScreen);
    HBITMAP hbitmap;
    HBITMAP hOldBitmap;
    HPALETTE hOldPalette = NULL;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    // create an offscreen bitmap
    hbitmap = ::CreateCompatibleBitmap(hdcScreen, width, height);
    if (hbitmap == NULL) {
        throw std::bad_alloc();
    }
    hOldBitmap = (HBITMAP)::SelectObject(hdcMem, hbitmap);

    // REMIND: not multimon-friendly...
    int primaryIndex = AwtWin32GraphicsDevice::GetDefaultDeviceIndex();
    hOldPalette =
        AwtWin32GraphicsDevice::SelectPalette(hdcMem, primaryIndex);
    AwtWin32GraphicsDevice::RealizePalette(hdcMem, primaryIndex);

    Devices::InstanceAccess devices;
    AwtWin32GraphicsDevice *device = devices->GetDevice(primaryIndex);
    int sWidth = (device == NULL) ? width : device->ScaleUpX(width);
    int sHeight = (device == NULL) ? height : device->ScaleUpY(height);

    // copy screen image to offscreen bitmap
    // CAPTUREBLT flag is required to capture WS_EX_LAYERED windows' contents
    // correctly on Win2K/XP
    if (width == sWidth && height == sHeight) {
        VERIFY(::BitBlt(hdcMem, 0, 0, width, height, hdcScreen, x, y,
               SRCCOPY | CAPTUREBLT) != 0);
    } else {
        int sX = (device == NULL) ? x : device->ScaleUpX(x);
        int sY = (device == NULL) ? y : device->ScaleUpY(y);
        VERIFY(::StretchBlt(hdcMem, 0, 0, width, height,
               hdcScreen, sX, sY, sWidth, sHeight,
               SRCCOPY | CAPTUREBLT) != 0);
    }

    static const int BITS_PER_PIXEL = 32;
    static const int BYTES_PER_PIXEL = BITS_PER_PIXEL/8;

    if (!IS_SAFE_SIZE_MUL(width, height)) throw std::bad_alloc();
    int numPixels = width*height;
    if (!IS_SAFE_SIZE_MUL(BYTES_PER_PIXEL, numPixels)) throw std::bad_alloc();
    int pixelDataSize = BYTES_PER_PIXEL*numPixels;
    DASSERT(pixelDataSize > 0 && pixelDataSize % 4 == 0);
    // allocate memory for BITMAPINFO + pixel data
    // 4620932: When using BI_BITFIELDS, GetDIBits expects an array of 3
    // RGBQUADS to follow the BITMAPINFOHEADER, but we were only allocating the
    // 1 that is included in BITMAPINFO.  Thus, GetDIBits was writing off the
    // end of our block of memory.  Now we allocate sufficient memory.
    // See MSDN docs for BITMAPINFOHEADER -bchristi

    if (!IS_SAFE_SIZE_ADD(sizeof(BITMAPINFOHEADER) + 3 * sizeof(RGBQUAD), pixelDataSize)) {
        throw std::bad_alloc();
    }
    BITMAPINFO * pinfo = (BITMAPINFO *)(new BYTE[sizeof(BITMAPINFOHEADER) + 3 * sizeof(RGBQUAD) + pixelDataSize]);

    // pixel data starts after 3 RGBQUADS for color masks
    RGBQUAD *pixelData = &pinfo->bmiColors[3];

    // prepare BITMAPINFO for a 32-bit RGB bitmap
    ::memset(pinfo, 0, sizeof(*pinfo));
    pinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pinfo->bmiHeader.biWidth = width;
    pinfo->bmiHeader.biHeight = -height; // negative height means a top-down DIB
    pinfo->bmiHeader.biPlanes = 1;
    pinfo->bmiHeader.biBitCount = BITS_PER_PIXEL;
    pinfo->bmiHeader.biCompression = BI_BITFIELDS;

    // Setup up color masks
    static const RGBQUAD redMask =   {0, 0, 0xFF, 0};
    static const RGBQUAD greenMask = {0, 0xFF, 0, 0};
    static const RGBQUAD blueMask =  {0xFF, 0, 0, 0};

    pinfo->bmiColors[0] = redMask;
    pinfo->bmiColors[1] = greenMask;
    pinfo->bmiColors[2] = blueMask;

    // Get the bitmap data in device-independent, 32-bit packed pixel format
    ::GetDIBits(hdcMem, hbitmap, 0, height, pixelData, pinfo, DIB_RGB_COLORS);

    // convert Win32 pixel format (BGRX) to Java format (ARGB)
    DASSERT(sizeof(jint) == sizeof(RGBQUAD));
    for(int nPixel = 0; nPixel < numPixels; nPixel++) {
        RGBQUAD * prgbq = &pixelData[nPixel];
        jint jpixel = WinToJavaPixel(prgbq->rgbRed, prgbq->rgbGreen, prgbq->rgbBlue);
        // stuff the 32-bit pixel back into the 32-bit RGBQUAD
        *prgbq = *( (RGBQUAD *)(&jpixel) );
    }

    // copy pixels into Java array
    env->SetIntArrayRegion(pixelArray, 0, numPixels, (jint *)pixelData);
    delete[] pinfo;

    // free all the GDI objects we made
    ::SelectObject(hdcMem, hOldBitmap);
    if (hOldPalette != NULL) {
        ::SelectPalette(hdcMem, hOldPalette, FALSE);
    }
    ::DeleteObject(hbitmap);
    ::DeleteDC(hdcMem);
    ::DeleteDC(hdcScreen);
}

void AwtRobot::KeyPress( jint jkey )
{
    DoKeyEvent(jkey, 0); // no flags means key down
}

void AwtRobot::KeyRelease( jint jkey )
{
    DoKeyEvent(jkey, KEYEVENTF_KEYUP);
}

void AwtRobot::DoKeyEvent( jint jkey, DWORD dwFlags )
{
    UINT        vkey;
    UINT        modifiers;
    UINT        scancode;
    JNIEnv *    env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    // convert Java key into Windows key (and modifiers too)
    AwtComponent::JavaKeyToWindowsKey(jkey, &vkey, &modifiers);
    if (vkey == 0) {
        // no equivalent Windows key found for given Java keycode
        JNU_ThrowIllegalArgumentException(env, "Invalid key code");
    } else {
        // get the scancode from the virtual key
        scancode = ::MapVirtualKey(vkey, 0);
        keybd_event(vkey, scancode, dwFlags, 0);
    }
}

//
// utility function to get the C++ object from the Java one
//
// (static)
AwtRobot * AwtRobot::GetRobot( jobject self )
{
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    AwtRobot * robot = (AwtRobot *)JNI_GET_PDATA(self);
    DASSERT( !::IsBadWritePtr( robot, sizeof(AwtRobot)));
    return robot;
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Native method declarations
//

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_create(
    JNIEnv * env, jobject self)
{
    TRY;

    new AwtRobot(self);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer__1dispose(
    JNIEnv *env, jobject self)
{
    TRY_NO_VERIFY;

    AwtObject::_Dispose(self);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_mouseMoveImpl(
    JNIEnv * env, jobject self, jint x, jint y)
{
    TRY;

    AwtRobot::GetRobot(self)->MouseMove(x, y);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_mousePress(
    JNIEnv * env, jobject self, jint buttons)
{
    TRY;

    AwtRobot::GetRobot(self)->MousePress(buttons);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_mouseRelease(
    JNIEnv * env, jobject self, jint buttons)
{
    TRY;

    AwtRobot::GetRobot(self)->MouseRelease(buttons);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_mouseWheel(
    JNIEnv * env, jobject self, jint wheelAmt)
{
    TRY;

    AwtRobot::GetRobot(self)->MouseWheel(wheelAmt);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_getRGBPixels(
    JNIEnv *env, jobject self, jint x, jint y, jint width, jint height, jintArray pixelArray)
{
    TRY;

    AwtRobot::GetRobot(self)->GetRGBPixels(x, y, width, height, pixelArray);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_keyPress(
  JNIEnv *, jobject self, jint javakey )
{
    TRY;

    AwtRobot::GetRobot(self)->KeyPress(javakey);

    CATCH_BAD_ALLOC;
}

JNIEXPORT void JNICALL Java_sun_awt_windows_WRobotPeer_keyRelease(
  JNIEnv *, jobject self, jint javakey )
{
    TRY;

    AwtRobot::GetRobot(self)->KeyRelease(javakey);

    CATCH_BAD_ALLOC;
}
