/*
 * Copyright (c) 1999, 2008, 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.
 */



/**
 * This class holds the information for a particular graphics device.
 * Since a display change can cause the creation of new devices
 * at any time, there is no referencing of the devices array allowed.
 * Instead, anyone wishing to reference a device in the array (e.g.,
 * the current default device or a device for a given hWnd) must
 * call one of the static methods of this class with the index of
 * the device in question.  Those methods will then lock the devices
 * array and forward the request to the current device at that
 * array index.
 */

#include <awt.h>
#include <sun_awt_Win32GraphicsDevice.h>
#include "awt_Canvas.h"
#include "awt_Win32GraphicsDevice.h"
#include "awt_Window.h"
#include "java_awt_Transparency.h"
#include "java_awt_color_ColorSpace.h"
#include "sun_awt_Win32GraphicsDevice.h"
#include "java_awt_image_DataBuffer.h"
#include "dither.h"
#include "img_util_md.h"
#include "Devices.h"

uns_ordered_dither_array img_oda_alpha;

jclass      AwtWin32GraphicsDevice::indexCMClass;
jclass      AwtWin32GraphicsDevice::wToolkitClass;
jfieldID    AwtWin32GraphicsDevice::dynamicColorModelID;
jfieldID    AwtWin32GraphicsDevice::indexCMrgbID;
jfieldID    AwtWin32GraphicsDevice::indexCMcacheID;
jmethodID   AwtWin32GraphicsDevice::paletteChangedMID;
BOOL        AwtWin32GraphicsDevice::primaryPalettized;
int         AwtWin32GraphicsDevice::primaryIndex = 0;


/**
 * Construct this device.  Store the screen (index into the devices
 * array of this object), the array (used in static references via
 * particular device indices), the monitor/pMonitorInfo (which other
 * classes will inquire of this device), the bits per pixel of this
 * device, and information on whether the primary device is palettized.
 */
AwtWin32GraphicsDevice::AwtWin32GraphicsDevice(int screen,
                                               HMONITOR mhnd, Devices *arr)
{
    this->screen  = screen;
    this->devicesArray = arr;
    javaDevice = NULL;
    colorData = new ImgColorData;
    colorData->grayscale = GS_NOTGRAY;
    palette = NULL;
    cData = NULL;
    gpBitmapInfo = NULL;
    monitor = mhnd;
    pMonitorInfo = new MONITORINFOEX;
    pMonitorInfo->cbSize = sizeof(MONITORINFOEX);
    ::GetMonitorInfo(monitor, pMonitorInfo);

    // Set primary device info: other devices will need to know
    // whether the primary is palettized during the initialization
    // process
    HDC hDC = this->GetDC();
    colorData->bitsperpixel = ::GetDeviceCaps(hDC, BITSPIXEL);
    this->ReleaseDC(hDC);
    if (MONITORINFOF_PRIMARY & pMonitorInfo->dwFlags) {
        primaryIndex = screen;
        if (colorData->bitsperpixel > 8) {
            primaryPalettized = FALSE;
        } else {
            primaryPalettized = TRUE;
        }
    }
}

AwtWin32GraphicsDevice::~AwtWin32GraphicsDevice()
{
    delete colorData;
    if (gpBitmapInfo) {
        free(gpBitmapInfo);
    }
    if (palette) {
        delete palette;
    }
    if (pMonitorInfo) {
        delete pMonitorInfo;
    }
    if (javaDevice) {
        JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
        env->DeleteWeakGlobalRef(javaDevice);
    }
    if (cData != NULL) {
        freeICMColorData(cData);
    }
}

HDC AwtWin32GraphicsDevice::MakeDCFromMonitor(HMONITOR hmMonitor) {
    HDC retCode = NULL;
    if (NULL != hmMonitor) {
        MONITORINFOEX mieInfo;

        memset((void*)(&mieInfo), 0, sizeof(MONITORINFOEX));
        mieInfo.cbSize = sizeof(MONITORINFOEX);

        if (TRUE == ::GetMonitorInfo(hmMonitor, (LPMONITORINFOEX)(&mieInfo))) {
            HDC hDC = CreateDC(mieInfo.szDevice, NULL, NULL, NULL);
            if (NULL != hDC) {
                retCode = hDC;
            }
        }
    }
    return retCode;
}

HDC AwtWin32GraphicsDevice::GetDC()
{
    return MakeDCFromMonitor(monitor);
}

void AwtWin32GraphicsDevice::ReleaseDC(HDC hDC)
{
    if (hDC != NULL) {
        ::DeleteDC(hDC);
    }
}

/**
 * Init this device.  This creates the bitmap structure
 * used to hold the device color data and initializes any
 * appropriate palette structures.
 */
void AwtWin32GraphicsDevice::Initialize()
{
    unsigned int ri, gi, bi;
    if (colorData->bitsperpixel < 8) {
        // REMIND: how to handle?
    }

    // Create a BitmapInfo object for color data
    if (!gpBitmapInfo) {
        try {
            gpBitmapInfo = (BITMAPINFO *)
                safe_Malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
        } catch (std::bad_alloc&) {
            throw;
        }
        gpBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    }
    gpBitmapInfo->bmiHeader.biBitCount = 0;
    HDC hBMDC = this->GetDC();
    HBITMAP hBM = ::CreateCompatibleBitmap(hBMDC, 1, 1);
    VERIFY(::GetDIBits(hBMDC, hBM, 0, 1, NULL, gpBitmapInfo, DIB_RGB_COLORS));

    if (colorData->bitsperpixel > 8) {
        if (MONITORINFOF_PRIMARY & pMonitorInfo->dwFlags) {
            primaryPalettized = FALSE;
        }
        if (colorData->bitsperpixel != 24) { // 15, 16, or 32 bpp
            int foo;
            gpBitmapInfo->bmiHeader.biCompression = BI_BITFIELDS;
            if (::GetDIBits(hBMDC, hBM, 0, 1, &foo, gpBitmapInfo,
                            DIB_RGB_COLORS) == 0)
            {
                // Bug 4684966: If GetDIBits returns an error, we could
                // get stuck in an infinite loop setting the colorData
                // fields.  Hardcode bitColors to reasonable values instead.
                // These values are picked according to standard masks
                // for these bit depths on win9x, according to MSDN docs.
                switch (colorData->bitsperpixel) {
                case 15:
                    ((int *)gpBitmapInfo->bmiColors)[0] = 0x7c00;
                    ((int *)gpBitmapInfo->bmiColors)[1] = 0x03e0;
                    ((int *)gpBitmapInfo->bmiColors)[2] = 0x001f;
                    break;
                case 16:
                    ((int *)gpBitmapInfo->bmiColors)[0] = 0xf800;
                    ((int *)gpBitmapInfo->bmiColors)[1] = 0x07e0;
                    ((int *)gpBitmapInfo->bmiColors)[2] = 0x001f;
                    break;
                case 32:
                default:
                    ((int *)gpBitmapInfo->bmiColors)[0] = 0xff0000;
                    ((int *)gpBitmapInfo->bmiColors)[1] = 0x00ff00;
                    ((int *)gpBitmapInfo->bmiColors)[2] = 0x0000ff;
                    break;
                }
            }
            ri = ((unsigned int *)gpBitmapInfo->bmiColors)[0];
            colorData->rOff = 0;
            while ((ri & 1) == 0) {
                colorData->rOff++;
                ri >>= 1;
            }
            colorData->rScale = 0;
            while (ri < 0x80) {
                colorData->rScale++;
                ri <<= 1;
            }
            gi = ((unsigned int *)gpBitmapInfo->bmiColors)[1];
            colorData->gOff = 0;
            while ((gi & 1) == 0) {
                colorData->gOff++;
                gi >>= 1;
            }
            colorData->gScale = 0;
            while (gi < 0x80) {
                colorData->gScale++;
                gi <<= 1;
            }
            bi = ((unsigned int *)gpBitmapInfo->bmiColors)[2];
            colorData->bOff = 0;
            while ((bi & 1) == 0) {
                colorData->bOff++;
                bi >>= 1;
            }
            colorData->bScale = 0;
            while (bi < 0x80) {
                colorData->bScale++;
                bi <<= 1;
            }
            if (   (0 == colorData->bOff)
                && (5 == colorData->gOff)
                && (10 == colorData->rOff)
                && (3 == colorData->bScale)
                && (3 == colorData->gScale)
                && (3 == colorData->rScale)) {
                colorData->bitsperpixel = 15;
                gpBitmapInfo->bmiHeader.biCompression = BI_RGB;
            }
        } else {    // 24 bpp
            gpBitmapInfo->bmiHeader.biBitCount = 24;
            gpBitmapInfo->bmiHeader.biCompression = BI_RGB;

            // Fill these values in as a convenience for the screen
            // ColorModel construction code below (see getColorModel())
            ((int *)gpBitmapInfo->bmiColors)[0] = 0x0000ff;
            ((int *)gpBitmapInfo->bmiColors)[1] = 0x00ff00;
            ((int *)gpBitmapInfo->bmiColors)[2] = 0xff0000;
        }
    } else {
        if (MONITORINFOF_PRIMARY & pMonitorInfo->dwFlags) {
            primaryPalettized = TRUE;
        }
        gpBitmapInfo->bmiHeader.biBitCount = 8;
        gpBitmapInfo->bmiHeader.biCompression = BI_RGB;
        gpBitmapInfo->bmiHeader.biClrUsed = 256;
        gpBitmapInfo->bmiHeader.biClrImportant = 256;

        // The initialization of cData is done prior to
        // calling palette->Update() since we need it
        // for calculating inverseGrayLut
        if (cData == NULL) {
            cData = (ColorData*)safe_Calloc(1, sizeof(ColorData));
            memset(cData, 0, sizeof(ColorData));
            initDitherTables(cData);
        }

        if (!palette) {
            palette = new AwtPalette(this);
        } else {
            palette->Update();
        }
        palette->UpdateLogical();
    }
    VERIFY(::DeleteObject(hBM));
    VERIFY(::DeleteDC(hBMDC));
}

/**
 * Creates a new colorModel given the current device configuration.
 * The dynamic flag determines whether we use the system palette
 * (dynamic == TRUE) or our custom palette in creating a new
 * IndexedColorModel.
 */
jobject AwtWin32GraphicsDevice::GetColorModel(JNIEnv *env, jboolean dynamic)
{
    jobject awt_colormodel;
    int i;
    if (colorData->bitsperpixel == 24) {
        awt_colormodel =
            JNU_NewObjectByName(env, "sun/awt/Win32ColorModel24", "()V");
    } else if (colorData->bitsperpixel > 8) {
        int *masks = (int *)gpBitmapInfo->bmiColors;
        int numbits = 0;
        unsigned int bits = (masks[0] | masks[1] | masks[2]);
        while (bits) {
            numbits++;
            bits >>= 1;
        }
        awt_colormodel = JNU_NewObjectByName(env,
                                             "java/awt/image/DirectColorModel",
                                             "(IIII)V", numbits,
                                             masks[0], masks[1], masks[2]);
    } else if (colorData->grayscale == GS_STATICGRAY) {
        jclass clazz;
        jclass clazz1;
        jmethodID mid;
        jobject cspace = NULL;
        jint bits[1];
        jintArray bitsArray;

        clazz1 = env->FindClass("java/awt/color/ColorSpace");
        mid = env->GetStaticMethodID(clazz1, "getInstance",
              "(I)Ljava/awt/color/ColorSpace;");
        if (mid == 0) {
            return NULL;
        }
        cspace = env->CallStaticObjectMethod(clazz1, mid,
            java_awt_color_ColorSpace_CS_GRAY);
        if (cspace == 0) {
            return NULL;
        }

        bits[0] = 8;
        bitsArray = env->NewIntArray(1);
        if (bitsArray == 0) {
            return NULL;
        } else {
            env->SetIntArrayRegion(bitsArray, 0, 1, bits);
        }

        clazz = env->FindClass("java/awt/image/ComponentColorModel");

        mid = env->GetMethodID(clazz,"<init>",
            "(Ljava/awt/color/ColorSpace;[IZZII)V");

        if (mid == 0) {
            return NULL;
        }

        awt_colormodel = env->NewObject(clazz, mid,
                                        cspace,
                                        bitsArray,
                                        JNI_FALSE,
                                        JNI_FALSE,
                                        java_awt_Transparency_OPAQUE,
                                        java_awt_image_DataBuffer_TYPE_BYTE);
    } else {
        jintArray hRGB = env->NewIntArray(256);
        unsigned int *rgb = NULL, *rgbP = NULL;
        jboolean allvalid = JNI_TRUE;
        jbyte vbits[256/8];
        jobject validBits = NULL;

        /* Create the LUT from the color map */
        try {
            rgb = (unsigned int *) env->GetPrimitiveArrayCritical(hRGB, 0);
            if (rgb == NULL) {
                return NULL;
            }
            rgbP = rgb;
            if (!palette) {
                palette = new AwtPalette(this);
                palette->UpdateLogical();
            }
            if (colorData->grayscale == GS_INDEXGRAY) {
                /* For IndexColorModel, pretend first 10 colors and last
                   10 colors are transparent black.  This makes
                   ICM.allgrayopaque true.
                */
                unsigned int *logicalEntries = palette->GetLogicalEntries();

                for (i=0; i < 10; i++) {
                    rgbP[i] = 0x00000000;
                    rgbP[i+246] = 0x00000000;
                }
                memcpy(&rgbP[10], &logicalEntries[10], 236 * sizeof(RGBQUAD));
                // We need to specify which entries in the colormap are
                // valid so that the transparent black entries we have
                // created do not affect the Transparency setting of the
                // IndexColorModel.  The vbits array is used to construct
                // a BigInteger such that the most significant bit of vbits[0]
                // indicates the validity of the last color (#256) and the
                // least significant bit of vbits[256/8] indicates the
                // validity of the first color (#0).  We need to fill vbits
                // with all 1's and then turn off the first and last 10 bits.
                memset(vbits, 0xff, sizeof(vbits));
                vbits[0] = 0;
                vbits[1] = (jbyte) (0xff >> 2);
                vbits[sizeof(vbits)-2] = (jbyte) (0xff << 2);
                vbits[sizeof(vbits)-1] = 0;
                allvalid = JNI_FALSE;
            } else {
                if (AwtPalette::UseCustomPalette() && !dynamic) {
                    // If we plan to use our custom palette (i.e., we are
                    // not running inside another app and we are not creating
                    // a dynamic colorModel object), then setup ICM with
                    // custom palette entries
                    unsigned int *logicalEntries = palette->GetLogicalEntries();
                    memcpy(rgbP, logicalEntries, 256 * sizeof(int));
                } else {
                    // Else, use current system palette entries.
                    // REMIND: This may not give the result we want if
                    // we are running inside another app and that
                    // parent app is running in the background when we
                    // reach here.  We could at least cache an "ideal" set of
                    // system palette entries from the first time we are
                    // running in the foreground and then future ICM's will
                    // use that set instead.
                    unsigned int *systemEntries = palette->GetSystemEntries();
                    memcpy(rgbP, systemEntries, 256 * sizeof(int));
                }
            }
        } catch (...) {
            env->ReleasePrimitiveArrayCritical(hRGB, rgb, 0);
            throw;
        }

        env->ReleasePrimitiveArrayCritical(hRGB, rgb, 0);

        // Construct a new color model
        if (!allvalid) {
            jbyteArray bArray = env->NewByteArray(sizeof(vbits));
            env->SetByteArrayRegion(bArray, 0, sizeof(vbits), vbits);
            validBits = JNU_NewObjectByName(env,
                                            "java/math/BigInteger",
                                            "([B)V", bArray);
        }
        awt_colormodel =
            JNU_NewObjectByName(env,
                                "java/awt/image/IndexColorModel",
                                "(II[IIILjava/math/BigInteger;)V",
                                8, 256,
                                hRGB, 0,
                                java_awt_image_DataBuffer_TYPE_BYTE,
                                validBits);
    }
    return awt_colormodel;
}

/**
 * Called from AwtPalette code when it is determined what grayscale
 * value (if any) the current logical palette has
 */
void AwtWin32GraphicsDevice::SetGrayness(int grayValue)
{
    colorData->grayscale = grayValue;
}


/**
 * Update our dynamic IndexedColorModel.  This happens after
 * a change to the system palette.  Any surfaces stored in vram
 * (Win32OffScreenSurfaceData and GDIWindowSurfaceData objects)
 * refer to this colorModel and use its lookup table and inverse
 * lookup to calculate correct index values for rgb colors.  So
 * the colorModel must always reflect the current state of the
 * system palette.
 */
void AwtWin32GraphicsDevice::UpdateDynamicColorModel()
{
    if (!javaDevice) {
        // javaDevice may not be set yet.  If not, return.  In
        // this situation, we probably don't need an update anyway
        // since the colorModel will be created with the correct
        // info when the java side is initialized.
        return;
    }
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jobject colorModel = env->GetObjectField(javaDevice,
        dynamicColorModelID);
    if (!colorModel) {
        return;
    }
    if (env->IsInstanceOf(colorModel, indexCMClass)) {
        // If colorModel not of type ICM then we're not in 8-bit mode and
        // don't need to update it
        jboolean isCopy;
        unsigned int *newEntries = palette->GetSystemEntries();
        jintArray rgbArray = (jintArray)env->GetObjectField(colorModel,
            AwtWin32GraphicsDevice::indexCMrgbID);
        jintArray cacheArray = (jintArray)env->GetObjectField(colorModel,
            AwtWin32GraphicsDevice::indexCMcacheID);
        if (!rgbArray || !cacheArray) {
            JNU_ThrowInternalError(env,
                "rgb or lookupcache array of IndexColorModel null");
            return;
        }
        int rgbLength = env->GetArrayLength(rgbArray);
        int cacheLength = env->GetArrayLength(cacheArray);
        jint *cmEntries = (jint *)env->GetPrimitiveArrayCritical(rgbArray,
            &isCopy);
        jint *cache = (jint *)env->GetPrimitiveArrayCritical(cacheArray,
            &isCopy);
        if (!cmEntries || !cache) {
            JNU_ThrowInternalError(env,
                "Problem retrieving rgb or cache critical array");
            return;
        }
        // Set the new rgb values
    int i;
    for (i = 0; i < rgbLength; ++i) {
            cmEntries[i] = newEntries[i];
        }
        // clear out the old cache
        for (i = 0; i < cacheLength; ++i) {
            cache[i] = 0;
        }
        env->ReleasePrimitiveArrayCritical(cacheArray, cache, 0);
        env->ReleasePrimitiveArrayCritical(rgbArray, cmEntries, 0);

        // Call WToolkit::paletteChanged() method; this will invalidate
        // the offscreen surfaces dependent on this dynamic colorModel
        // to ensure that they get redrawn with the correct color indices
        env->CallStaticVoidMethod(AwtWin32GraphicsDevice::wToolkitClass,
            paletteChangedMID);
    }
}

unsigned int *AwtWin32GraphicsDevice::GetSystemPaletteEntries()
{
    // REMIND: What to do if palette NULL?  Need to throw
    // some kind of exception?
    return palette->GetSystemEntries();
}

unsigned char *AwtWin32GraphicsDevice::GetSystemInverseLUT()
{
    // REMIND: What to do if palette NULL?  Need to throw
    // some kind of exception?
    return palette->GetSystemInverseLUT();
}


BOOL AwtWin32GraphicsDevice::UpdateSystemPalette()
{
    if (colorData->bitsperpixel > 8) {
        return FALSE;
    } else {
        return palette->Update();
    }
}

HPALETTE AwtWin32GraphicsDevice::SelectPalette(HDC hDC)
{
    if (palette) {
        return palette->Select(hDC);
    } else {
        return NULL;
    }
}

void AwtWin32GraphicsDevice::RealizePalette(HDC hDC)
{
    if (palette) {
        palette->Realize(hDC);
    }
}

/**
 * Deterine which device the HWND exists on and return the
 * appropriate index into the devices array.
 */
int AwtWin32GraphicsDevice::DeviceIndexForWindow(HWND hWnd)
{
    HMONITOR mon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    int screen = AwtWin32GraphicsDevice::GetScreenFromHMONITOR(mon);
    return screen;
}

/**
 * Get the HPALETTE associated with this device
 */
HPALETTE AwtWin32GraphicsDevice::GetPalette()
{
    if (palette) {
        return palette->GetPalette();
    } else {
        return NULL;
    }
}

/**
 * Object referring to this device is releasing that reference.
 * This allows the array holding all devices to be released (once
 * all references to the array have gone away).
 */
void AwtWin32GraphicsDevice::Release()
{
    devicesArray->Release();
}

/**
 * Links this native object with its java Win32GraphicsDevice.
 * Need this link because the colorModel of the java device
 * may be updated from native code.
 */
void AwtWin32GraphicsDevice::SetJavaDevice(JNIEnv *env, jobject objPtr)
{
    javaDevice = env->NewWeakGlobalRef(objPtr);
}

/**
 * Disables offscreen acceleration for this device.  This
 * sets a flag in the java object that is used to determine
 * whether offscreen surfaces can be created on the device.
 */
void AwtWin32GraphicsDevice::DisableOffscreenAcceleration()
{
    // REMIND: noop for now
}

/**
 * Invalidates the GraphicsDevice object associated with this
 * device by disabling offscreen acceleration and calling
 * invalidate(defIndex) on the java object.
 */
void AwtWin32GraphicsDevice::Invalidate(JNIEnv *env)
{
    int defIndex = AwtWin32GraphicsDevice::GetDefaultDeviceIndex();
    DisableOffscreenAcceleration();
    jobject javaDevice = GetJavaDevice();
    if (!JNU_IsNull(env, javaDevice)) {
        JNU_CallMethodByName(env, NULL, javaDevice, "invalidate",
                             "(I)V", defIndex);
    }
}

/**
 * Static deviceIndex-based methods
 *
 * The following methods take a deviceIndex for the list of devices
 * and perform the appropriate action on that device.  This way of
 * dereferencing the list of devices allows us to do appropriate
 * locks around the list to ensure multi-threaded safety.
 */


jobject AwtWin32GraphicsDevice::GetColorModel(JNIEnv *env, jboolean dynamic,
                                              int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetColorModel(env, dynamic);
}

LPMONITORINFO AwtWin32GraphicsDevice::GetMonitorInfo(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetMonitorInfo();
}

/**
 * This function updates the data in the MONITORINFOEX structure pointed to by
 * pMonitorInfo for all monitors on the system.  Added for 4654713.
 */
void AwtWin32GraphicsDevice::ResetAllMonitorInfo()
{
    //IE in some circumstances generates WM_SETTINGCHANGE message on appearance
    //and thus triggers this method
    //but we may not have the devices list initialized yet.
    if (!Devices::GetInstance()){
        return;
    }
    Devices::InstanceAccess devices;
    int devicesNum = devices->GetNumDevices();
    for (int deviceIndex = 0; deviceIndex < devicesNum; deviceIndex++) {
        HMONITOR monitor = devices->GetDevice(deviceIndex)->GetMonitor();
        ::GetMonitorInfo(monitor,
                         devices->GetDevice(deviceIndex)->pMonitorInfo);
    }
}

void AwtWin32GraphicsDevice::DisableOffscreenAccelerationForDevice(
    HMONITOR hMonitor)
{
    Devices::InstanceAccess devices;
    if (hMonitor == NULL) {
        devices->GetDevice(0)->DisableOffscreenAcceleration();
    } else {
        int devicesNum = devices->GetNumDevices();
        for (int i = 0; i < devicesNum; ++i) {
            if (devices->GetDevice(i)->GetMonitor() == hMonitor) {
                devices->GetDevice(i)->DisableOffscreenAcceleration();
            }
        }
    }
}

HMONITOR AwtWin32GraphicsDevice::GetMonitor(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetMonitor();
}

HPALETTE AwtWin32GraphicsDevice::GetPalette(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetPalette();
}

void AwtWin32GraphicsDevice::UpdateDynamicColorModel(int deviceIndex)
{
    Devices::InstanceAccess devices;
    devices->GetDevice(deviceIndex)->UpdateDynamicColorModel();
}

BOOL AwtWin32GraphicsDevice::UpdateSystemPalette(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->UpdateSystemPalette();
}

HPALETTE AwtWin32GraphicsDevice::SelectPalette(HDC hDC, int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->SelectPalette(hDC);
}

void AwtWin32GraphicsDevice::RealizePalette(HDC hDC, int deviceIndex)
{
    Devices::InstanceAccess devices;
    devices->GetDevice(deviceIndex)->RealizePalette(hDC);
}

ColorData *AwtWin32GraphicsDevice::GetColorData(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetColorData();
}

/**
 * Return the grayscale value for the indicated device.
 */
int AwtWin32GraphicsDevice::GetGrayness(int deviceIndex)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(deviceIndex)->GetGrayness();
}

HDC AwtWin32GraphicsDevice::GetDCFromScreen(int screen) {
    J2dTraceLn1(J2D_TRACE_INFO,
                "AwtWin32GraphicsDevice::GetDCFromScreen screen=%d", screen);
    Devices::InstanceAccess devices;
    AwtWin32GraphicsDevice *dev = devices->GetDevice(screen);
    return MakeDCFromMonitor(dev->GetMonitor());
}

/** Compare elements of MONITORINFOEX structures for the given HMONITORs.
 * If equal, return TRUE
 */
BOOL AwtWin32GraphicsDevice::AreSameMonitors(HMONITOR mon1, HMONITOR mon2) {
    J2dTraceLn2(J2D_TRACE_INFO,
                "AwtWin32GraphicsDevice::AreSameMonitors mhnd1=%x mhnd2=%x",
                mon1, mon2);
    DASSERT(mon1 != NULL);
    DASSERT(mon2 != NULL);

    MONITORINFOEX mi1;
    MONITORINFOEX mi2;

    memset((void*)(&mi1), 0, sizeof(MONITORINFOEX));
    mi1.cbSize = sizeof(MONITORINFOEX);
    memset((void*)(&mi2), 0, sizeof(MONITORINFOEX));
    mi2.cbSize = sizeof(MONITORINFOEX);

    if (::GetMonitorInfo(mon1, &mi1) != 0 &&
        ::GetMonitorInfo(mon2, &mi2) != 0 )
    {
        if (::EqualRect(&mi1.rcMonitor, &mi2.rcMonitor) &&
            ::EqualRect(&mi1.rcWork, &mi2.rcWork) &&
            (mi1.dwFlags  == mi1.dwFlags))
        {

            J2dTraceLn(J2D_TRACE_VERBOSE, "  the monitors are the same");
            return TRUE;
        }
    }
    J2dTraceLn(J2D_TRACE_VERBOSE, "  the monitors are not the same");
    return FALSE;
}

int AwtWin32GraphicsDevice::GetScreenFromHMONITOR(HMONITOR mon) {
    J2dTraceLn1(J2D_TRACE_INFO,
                "AwtWin32GraphicsDevice::GetScreenFromHMONITOR mhnd=%x", mon);

    DASSERT(mon != NULL);
    Devices::InstanceAccess devices;

    for (int i = 0; i < devices->GetNumDevices(); i++) {
        HMONITOR mhnd = devices->GetDevice(i)->GetMonitor();
        if (AreSameMonitors(mon, mhnd)) {
            J2dTraceLn1(J2D_TRACE_VERBOSE, "  Found device: %d", i);
            return i;
        }
    }

    J2dTraceLn1(J2D_TRACE_WARNING,
                "AwtWin32GraphicsDevice::GetScreenFromHMONITOR(): "\
                "couldn't find screen for HMONITOR %x, returning default", mon);
    return AwtWin32GraphicsDevice::GetDefaultDeviceIndex();
}


/**
 * End of static deviceIndex-based methods
 */


    const DWORD REQUIRED_FLAGS = (   //Flags which must be set in
     PFD_SUPPORT_GDI |               //in the PixelFormatDescriptor.
     PFD_DRAW_TO_WINDOW);            //Used to choose the default config
                                     //and to check formats in
                                     //isPixFmtSupported()
extern "C" {

JNIEXPORT void JNICALL
Java_sun_awt_Win32GraphicsDevice_initIDs(JNIEnv *env, jclass cls)
{
    TRY;

    /* class ids */
    AwtWin32GraphicsDevice::indexCMClass =
        (jclass)env->NewGlobalRef(env->FindClass("java/awt/image/IndexColorModel"));
    AwtWin32GraphicsDevice::wToolkitClass =
        (jclass)env->NewGlobalRef(env->FindClass("sun/awt/windows/WToolkit"));

    DASSERT(AwtWin32GraphicsDevice::indexCMClass);
    DASSERT(AwtWin32GraphicsDevice::wToolkitClass);

    /* field ids */
    AwtWin32GraphicsDevice::dynamicColorModelID = env->GetFieldID(cls,
        "dynamicColorModel", "Ljava/awt/image/ColorModel;");
    AwtWin32GraphicsDevice::indexCMrgbID =
        env->GetFieldID(AwtWin32GraphicsDevice::indexCMClass, "rgb", "[I");
    AwtWin32GraphicsDevice::indexCMcacheID =
        env->GetFieldID(AwtWin32GraphicsDevice::indexCMClass,
        "lookupcache", "[I");

    /* method ids */
    AwtWin32GraphicsDevice::paletteChangedMID = env->GetStaticMethodID(
        AwtWin32GraphicsDevice::wToolkitClass, "paletteChanged", "()V");


    DASSERT(AwtWin32GraphicsDevice::dynamicColorModelID);
    DASSERT(AwtWin32GraphicsDevice::indexCMrgbID);
    DASSERT(AwtWin32GraphicsDevice::indexCMcacheID);

    DASSERT(AwtWin32GraphicsDevice::paletteChangedMID);

    // Only want to call this once per session
    make_uns_ordered_dither_array(img_oda_alpha, 256);

    CATCH_BAD_ALLOC;
}

} /* extern "C" */


/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    getMaxConfigsImpl
 * Signature: ()I
 */

JNIEXPORT jint JNICALL Java_sun_awt_Win32GraphicsDevice_getMaxConfigsImpl
    (JNIEnv* jniEnv, jobject theThis, jint screen) {
        TRY;
    HDC hDC = AwtWin32GraphicsDevice::GetDCFromScreen(screen);

    PIXELFORMATDESCRIPTOR pfd;
    int max = ::DescribePixelFormat(hDC, 1, sizeof(PIXELFORMATDESCRIPTOR),
        &pfd);
    if (hDC != NULL) {
        VERIFY(::DeleteDC(hDC));
        hDC = NULL;
    }
    //If ::DescribePixelFormat() fails, max = 0
    //In this case, we return 1 config with visual number 0
    if (max == 0) {
        max = 1;
    }
    return (jint)max;
        CATCH_BAD_ALLOC_RET(0);
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    isPixFmtSupported
 * Signature: (I)Z
 */

JNIEXPORT jboolean JNICALL Java_sun_awt_Win32GraphicsDevice_isPixFmtSupported
    (JNIEnv* env, jobject theThis, jint pixFmtID, jint screen) {
        TRY;
    jboolean suppColor = JNI_TRUE;
    HDC hDC = AwtWin32GraphicsDevice::GetDCFromScreen(screen);

    if (pixFmtID == 0) {
        return true;
    }

    PIXELFORMATDESCRIPTOR pfd;
    int max = ::DescribePixelFormat(hDC, (int)pixFmtID,
        sizeof(PIXELFORMATDESCRIPTOR), &pfd);
    DASSERT(max);

    //Check for supported ColorModel
    if ((pfd.cColorBits < 8) ||
       ((pfd.cColorBits == 8) && (pfd.iPixelType != PFD_TYPE_COLORINDEX))) {
        //Note: this still allows for PixelFormats with > 8 color bits
        //which use COLORINDEX instead of RGB.  This seems to work fine,
        //although issues may crop up involving PFD_NEED_PALETTE, which
        //is not currently taken into account.
        //If changes are made, they should also be reflected in
        //getDefaultPixID.
        suppColor = JNI_FALSE;
    }

    if (hDC != NULL) {
        VERIFY(::DeleteDC(hDC));
        hDC = NULL;
    }
    return (((pfd.dwFlags & REQUIRED_FLAGS) == REQUIRED_FLAGS) && suppColor) ?
     JNI_TRUE : JNI_FALSE;
        CATCH_BAD_ALLOC_RET(FALSE);
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    getDefaultPixIDImpl
 * Signature: (I)I
 */

JNIEXPORT jint JNICALL Java_sun_awt_Win32GraphicsDevice_getDefaultPixIDImpl
    (JNIEnv* env, jobject theThis, jint screen) {
        TRY;
    int pixFmtID = 0;
    HDC hDC = AwtWin32GraphicsDevice::GetDCFromScreen(screen);

    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,               //version
        REQUIRED_FLAGS,  //flags
        0,               //iPixelType
        0,               //cColorBits
        0,0,0,0,0,0,0,0, //cRedBits, cRedShift, green, blue, alpha
        0,0,0,0,0,       //cAccumBits, cAccumRedBits, green, blue, alpha
        0,0,0,0,0,0,0,0  //etc.
    };

    //If 8-bit mode, must use Indexed mode
    if (8 == ::GetDeviceCaps(hDC, BITSPIXEL)) {
        pfd.iPixelType = PFD_TYPE_COLORINDEX;
    }

    pixFmtID = ::ChoosePixelFormat(hDC, &pfd);
    if (pixFmtID == 0) {
        //Return 0 if GDI call fails.
        if (hDC != NULL) {
            VERIFY(::DeleteDC(hDC));
            hDC = NULL;
        }
        return pixFmtID;
    }

    if (JNI_FALSE == Java_sun_awt_Win32GraphicsDevice_isPixFmtSupported(
     env, theThis, pixFmtID, screen)) {
        /* Can't find a suitable pixel format ID.  Fall back on 0. */
        pixFmtID = 0;
    }

    VERIFY(::DeleteDC(hDC));
    hDC = NULL;
    return (jint)pixFmtID;
        CATCH_BAD_ALLOC_RET(0);
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    enterFullScreenExclusive
 * Signature: (Ljava/awt/peer/WindowPeer;)V
 */

JNIEXPORT void JNICALL
Java_sun_awt_Win32GraphicsDevice_enterFullScreenExclusive(
        JNIEnv* env, jobject graphicsDevice,
        jint screen, jobject windowPeer) {

    TRY;

    PDATA pData;
    JNI_CHECK_PEER_RETURN(windowPeer);

    AwtWindow *window = (AwtWindow *)pData;  // safe cast since we are called
                                             // with the WWindowPeer object
    HWND hWnd = window->GetHWnd();

    if (!::SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
                        SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOSIZE))
    {
        J2dTraceLn1(J2D_TRACE_ERROR,
                    "Error %d setting topmost attribute to fs window",
                    ::GetLastError());
    }

    CATCH_BAD_ALLOC;
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    exitFullScreenExclusive
 * Signature: (Ljava/awt/peer/WindowPeer;)V
 */

JNIEXPORT void JNICALL
Java_sun_awt_Win32GraphicsDevice_exitFullScreenExclusive(
        JNIEnv* env, jobject graphicsDevice,
        jint screen, jobject windowPeer) {

    TRY;

    PDATA pData;
    JNI_CHECK_PEER_RETURN(windowPeer);

    AwtWindow *window = (AwtWindow *)pData;  // safe cast since we are called
                                             // with the WWindowPeer object
    HWND hWnd = window->GetHWnd();

    jobject target = env->GetObjectField(windowPeer, AwtObject::targetID);
    jboolean alwaysOnTop = JNU_GetFieldByName(env, NULL, target, "alwaysOnTop", "Z").z;
    env->DeleteLocalRef(target);

    if (!::SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,
                        SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOSIZE))
    {
        J2dTraceLn1(J2D_TRACE_ERROR,
                    "Error %d unsetting topmost attribute to fs window",
                    ::GetLastError());
    }

    // We should restore alwaysOnTop state as it's anyway dropped here
    Java_sun_awt_windows_WWindowPeer_setAlwaysOnTopNative(env, windowPeer, alwaysOnTop);

    CATCH_BAD_ALLOC;
}

jobject CreateDisplayMode(JNIEnv* env, jint width, jint height,
    jint bitDepth, jint refreshRate) {

    TRY;

    jclass displayModeClass = env->FindClass("java/awt/DisplayMode");
    if (JNU_IsNull(env, displayModeClass)) {
        JNU_ThrowInternalError(env,
            "Could not get display mode class");
        return NULL;
    }

    jmethodID cid = env->GetMethodID(displayModeClass, "<init>",
        "(IIII)V");
    if (cid == NULL) {
        JNU_ThrowInternalError(env,
            "Could not get display mode constructor");
        return NULL;
    }

    jobject displayMode = env->NewObject(displayModeClass, cid, width,
        height, bitDepth, refreshRate);
    return displayMode;

    CATCH_BAD_ALLOC_RET(NULL);
}

/**
 * A utility function which retrieves a DISPLAY_DEVICE information
 * given a screen number.
 *
 * If the function was able to find an attached device for the given screen
 * number, the lpDisplayDevice will be initialized with the data and
 * the function will return TRUE, otherwise it returns FALSE and contents
 * of the structure pointed to by lpDisplayDevice is undefined.
 */
static BOOL
GetAttachedDisplayDevice(int screen, DISPLAY_DEVICE *lpDisplayDevice)
{
    DWORD dwDeviceNum = 0;
    lpDisplayDevice->cb = sizeof(DISPLAY_DEVICE);
    while (EnumDisplayDevices(NULL, dwDeviceNum, lpDisplayDevice, 0) &&
           dwDeviceNum < 20) // avoid infinite loop with buggy drivers
    {
        if (lpDisplayDevice->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) {
            Devices::InstanceAccess devices;
            MONITORINFOEX *pMonInfo =
                (LPMONITORINFOEX)devices->GetDevice(screen)->GetMonitorInfo();
            // make sure the device names match
            if (wcscmp(pMonInfo->szDevice, lpDisplayDevice->DeviceName) == 0) {
                return TRUE;
            }
        }
        dwDeviceNum++;
    }
    return FALSE;
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    getCurrentDisplayMode
 * Signature: (IZ)Ljava/awt/DisplayMode;
 */
JNIEXPORT jobject JNICALL
Java_sun_awt_Win32GraphicsDevice_getCurrentDisplayMode
    (JNIEnv* env, jobject graphicsDevice, jint screen)
{
    TRY;

    DEVMODE dm;
    LPTSTR pName = NULL;

    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;

    DISPLAY_DEVICE displayDevice;
    if (GetAttachedDisplayDevice(screen, &displayDevice)) {
        pName = displayDevice.DeviceName;
    }
    if (!EnumDisplaySettings(pName, ENUM_CURRENT_SETTINGS, &dm))
    {
        return NULL;
    }

    return CreateDisplayMode(env, dm.dmPelsWidth,
        dm.dmPelsHeight, dm.dmBitsPerPel, dm.dmDisplayFrequency);

    CATCH_BAD_ALLOC_RET(NULL);
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    configDisplayMode
 * Signature: (IIIIZ)V
 */
JNIEXPORT void JNICALL
Java_sun_awt_Win32GraphicsDevice_configDisplayMode
    (JNIEnv* env, jobject graphicsDevice, jint screen, jobject windowPeer,
     jint width, jint height, jint bitDepth, jint refreshRate)
{
    TRY;

        DEVMODE dm;

    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    dm.dmPelsWidth = width;
    dm.dmPelsHeight = height;
    dm.dmBitsPerPel = bitDepth;
    dm.dmDisplayFrequency = refreshRate;
    dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT |
        DM_BITSPERPEL | DM_DISPLAYFREQUENCY;

    // ChangeDisplaySettings works only on the primary screen.
    // ChangeDisplaySettingsEx is not available on NT,
    // so it'd be nice not to break it if we can help it.
    if (screen == AwtWin32GraphicsDevice::GetDefaultDeviceIndex()) {
        if (::ChangeDisplaySettings(&dm, CDS_FULLSCREEN) !=
            DISP_CHANGE_SUCCESSFUL)
        {
            JNU_ThrowInternalError(env,
                                   "Could not set display mode");
        }
        return;
    }

    DISPLAY_DEVICE displayDevice;
    if (!GetAttachedDisplayDevice(screen, &displayDevice) ||
        (::ChangeDisplaySettingsEx(displayDevice.DeviceName, &dm, NULL, CDS_FULLSCREEN, NULL) !=
          DISP_CHANGE_SUCCESSFUL))
    {
        JNU_ThrowInternalError(env,
                               "Could not set display mode");
    }

    CATCH_BAD_ALLOC;
}

class EnumDisplayModeParam {
public:
    EnumDisplayModeParam(JNIEnv* e, jobject a) : env(e), arrayList(a) {}
    JNIEnv* env;
    jobject arrayList;
};

void addDisplayMode(JNIEnv* env, jobject arrayList, jint width,
    jint height, jint bitDepth, jint refreshRate) {

    TRY;

    jobject displayMode = CreateDisplayMode(env, width, height,
        bitDepth, refreshRate);
    if (!JNU_IsNull(env, displayMode)) {
        jclass arrayListClass = env->GetObjectClass(arrayList);
        if (JNU_IsNull(env, arrayListClass)) {
            JNU_ThrowInternalError(env,
                "Could not get class java.util.ArrayList");
            return;
        }
        jmethodID mid = env->GetMethodID(arrayListClass, "add",
        "(Ljava/lang/Object;)Z");
        if (mid == NULL) {
            JNU_ThrowInternalError(env,
                "Could not get method java.util.ArrayList.add()");
            return;
        }
        env->CallObjectMethod(arrayList, mid, displayMode);
        env->DeleteLocalRef(displayMode);
    }

    CATCH_BAD_ALLOC;
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    enumDisplayModes
 * Signature: (Ljava/util/ArrayList;Z)V
 */
JNIEXPORT void JNICALL Java_sun_awt_Win32GraphicsDevice_enumDisplayModes
    (JNIEnv* env, jobject graphicsDevice, jint screen, jobject arrayList)
{

    TRY;

    DEVMODE dm;
    LPTSTR pName = NULL;
    DISPLAY_DEVICE displayDevice;


    if (GetAttachedDisplayDevice(screen, &displayDevice)) {
        pName = displayDevice.DeviceName;
    }

    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;

    BOOL bContinue = TRUE;
    for (int i = 0; bContinue; i++) {
        bContinue = EnumDisplaySettings(pName, i, &dm);
        if (dm.dmBitsPerPel >= 8) {
            addDisplayMode(env, arrayList, dm.dmPelsWidth, dm.dmPelsHeight,
                           dm.dmBitsPerPel, dm.dmDisplayFrequency);
        }
    }

    CATCH_BAD_ALLOC;
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    makeColorModel
 * Signature: ()Ljava/awt/image/ColorModel
 */

JNIEXPORT jobject JNICALL
    Java_sun_awt_Win32GraphicsDevice_makeColorModel
    (JNIEnv *env, jobject thisPtr, jint screen, jboolean dynamic)
{
    Devices::InstanceAccess devices;
    return devices->GetDevice(screen)->GetColorModel(env, dynamic);
}

/*
 * Class:     sun_awt_Win32GraphicsDevice
 * Method:    initDevice
 * Signature: (I)V
 */
JNIEXPORT void JNICALL
    Java_sun_awt_Win32GraphicsDevice_initDevice
    (JNIEnv *env, jobject thisPtr, jint screen)
{
    Devices::InstanceAccess devices;
    devices->GetDevice(screen)->SetJavaDevice(env, thisPtr);
}
