/*
 * Copyright (c) 2001, 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 encapsulates the array of Win32GraphicsDevices,
 * allowing it to be accessed and recreated from multiple
 * threads in a thread-safe manner.
 *
 * The MT-safeness of the array is assured in the following ways:
 *      - hide the actual array being used so that access to
 *        it can only be made from this class
 *      - Do not delete the array until all references to the
 *        array have released it.  That way, anyone that happens
 *        to have a pointer to an element of the array can still
 *        safely refer to that item, even if the situation has
 *        changed and the array is out of date.
 *      - ensure that the user of the array always gets a non-disposed
 *        instance (before the user is handed over a reference to the
 *        instance, a ref counter of the instance is increased atomically)
 *      - The act of replacing an old encapsulated array
 *        of devices with the new one is protected via common lock
 *
 * Expected usage patterns:
 * 1. The array element will not be used outside of this code block.
 *   {
 *     // first, get the reference to the Devices instance through InstanceAccess
 *     // subclass (this automatically increases ref count of this instance)
 *     Devices::InstanceAccess devices; // increases the ref count of current instance
 *     // Then the object can be used, for example, to retrieve the awt device.
 *     // (note: ref count is not increased with GetDevice())
 *     AwtWin32GraphicsDevice *dev = devices->GetDevice(idx);
 *     dev->DoStuff();
 *     Data data = dev->GetData();
 *     return data;
 *     // don't need to release the reference, it's done automatically in
 *     // InstanceAccess destructor
 *   }
 *
 * 2. The array element will be used outside of this code block (i.e.
 *    saved for later use).
 *   {
 *     Devices::InstanceAccess devices; // increases the ref count
 *     // next call increases the ref count of the instance again
 *     AwtWin32GraphicsDevice *dev = devices->GetDeviceReference(idx);
 *     wsdo->device = dev;
 *     // we saved the ref to the device element, the first reference
 *     // will be released automatically in the InstanceAccess destructor
 *   }
 *
 *   {
 *     wsdo->device->DoStuff(); // safe because we hold a reference
 *     // then, sometime later (different thread, method, whatever)
 *     // release the reference to the array element, which in
 *     // turn will decrease the ref count of the instance of Devices class
 *     // this element belongs to
 *     wsdo->device->Release();
 *     wsdo->device = NULL; // this reference can no longer be used
 *   }
 */

#include "Devices.h"
#include "Trace.h"
#include "D3DPipelineManager.h"


/* Some helper functions (from awt_MMStub.h/cpp) */

int g_nMonitorCounter;
int g_nMonitorLimit;
HMONITOR* g_hmpMonitors;

// Callback for CountMonitors below
BOOL WINAPI clb_fCountMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lP)
{
    g_nMonitorCounter ++;
    return TRUE;
}

int WINAPI CountMonitors(void)
{
    g_nMonitorCounter = 0;
    ::EnumDisplayMonitors(NULL, NULL, clb_fCountMonitors, 0L);
    return g_nMonitorCounter;

}

// Callback for CollectMonitors below
BOOL WINAPI clb_fCollectMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lP)
{

    if ((g_nMonitorCounter < g_nMonitorLimit) && (NULL != g_hmpMonitors)) {
        g_hmpMonitors[g_nMonitorCounter] = hMon;
        g_nMonitorCounter ++;
    }

    return TRUE;
}

int WINAPI CollectMonitors(HMONITOR* hmpMonitors, int nNum)
{
    int retCode = 0;

    if (NULL != hmpMonitors) {

        g_nMonitorCounter   = 0;
        g_nMonitorLimit     = nNum;
        g_hmpMonitors       = hmpMonitors;

        ::EnumDisplayMonitors(NULL, NULL, clb_fCollectMonitors, 0L);

        retCode             = g_nMonitorCounter;

        g_nMonitorCounter   = 0;
        g_nMonitorLimit     = 0;
        g_hmpMonitors       = NULL;

    }
    return retCode;
}

BOOL WINAPI MonitorBounds(HMONITOR hmMonitor, RECT* rpBounds)
{
    BOOL retCode = FALSE;

    if ((NULL != hmMonitor) && (NULL != rpBounds)) {
        MONITORINFOEX miInfo;

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

        if (TRUE == (retCode = ::GetMonitorInfo(hmMonitor, &miInfo))) {
            (*rpBounds) = miInfo.rcMonitor;
        }
    }
    return retCode;
}

/* End of helper functions */

Devices* Devices::theInstance = NULL;
CriticalSection Devices::arrayLock;

/**
 * Create a new Devices object with numDevices elements.
 */
Devices::Devices(int numDevices)
{
    J2dTraceLn1(J2D_TRACE_INFO, "Devices::Devices numDevices=%d", numDevices);
    this->numDevices = numDevices;
    this->refCount = 0;
    devices = (AwtWin32GraphicsDevice**)safe_Malloc
        (numDevices * sizeof(AwtWin32GraphicsDevice *));
}

/**
 * Static method which updates the array of the devices
 * while holding global lock.
 *
 * If the update was successful, method returns TRUE,
 * otherwise it returns FALSE.
 */
// static
BOOL Devices::UpdateInstance(JNIEnv *env)
{
    J2dTraceLn(J2D_TRACE_INFO, "Devices::UpdateInstance");

    int numScreens = CountMonitors();
    HMONITOR *monHds = (HMONITOR *)safe_Malloc(numScreens * sizeof(HMONITOR));
    if (numScreens != CollectMonitors(monHds, numScreens)) {
        J2dRlsTraceLn(J2D_TRACE_ERROR,
                      "Devices::UpdateInstance: Failed to get all "\
                      "monitor handles.");
        free(monHds);
        return FALSE;
    }

    Devices *newDevices = new Devices(numScreens);
    // This way we know that the array will not be disposed of
    // at least until we replaced it with a new one.
    newDevices->AddReference();

    // Create all devices first, then initialize them.  This allows
    // correct configuration of devices after contruction of the
    // primary device (which may not be device 0).
    AwtWin32GraphicsDevice** rawDevices = newDevices->GetRawArray();
    int i;
    for (i = 0; i < numScreens; ++i) {
        J2dTraceLn2(J2D_TRACE_VERBOSE, "  hmon[%d]=0x%x", i, monHds[i]);
        rawDevices[i] = new AwtWin32GraphicsDevice(i, monHds[i], newDevices);
    }
    for (i = 0; i < numScreens; ++i) {
        rawDevices[i]->Initialize();
    }
    {
        CriticalSection::Lock l(arrayLock);

        // install the new devices array
        Devices *oldDevices = theInstance;
        theInstance = newDevices;

        if (oldDevices) {
            // Invalidate the devices with indexes out of the new set of
            // devices. This doesn't cover all cases when the device
            // might should be invalidated (like if it's not the last device
            // that was removed), but it will have to do for now.
            int oldNumScreens = oldDevices->GetNumDevices();
            int newNumScreens = theInstance->GetNumDevices();
            J2dTraceLn(J2D_TRACE_VERBOSE, "  Invalidating removed devices");
            for (int i = newNumScreens; i < oldNumScreens; i++) {
                // removed device, needs to be invalidated
                J2dTraceLn1(J2D_TRACE_WARNING,
                            "Devices::UpdateInstance: device removed: %d", i);
                oldDevices->GetDevice(i)->Invalidate(env);
            }
            // Now that we have a new array in place, remove this (possibly the
            // last) reference to the old instance.
            oldDevices->Release();
        }
        D3DPipelineManager::HandleAdaptersChange((HMONITOR*)monHds,
                                                 theInstance->GetNumDevices());
    }
    free(monHds);

    return TRUE;
}

/**
 * Add a reference to the array.  This could be someone that wants
 * to register interest in the array, versus someone that actually
 * holds a reference to an array item (in which case they would
 * call GetDeviceReference() instead).  This mechanism can keep
 * the array from being deleted when it has no elements being
 * referenced but is still a valid array to use for new elements
 * or references.
 */
void Devices::AddReference()
{
    J2dTraceLn(J2D_TRACE_INFO, "Devices::AddReference");
    CriticalSection::Lock l(arrayLock);
    refCount++;
    J2dTraceLn1(J2D_TRACE_VERBOSE, "  refCount=%d", refCount);
}

/**
 * Static method for getting a reference
 * to the instance of the current devices array.
 * The instance will automatically have reference count increased.
 *
 * The caller thus must call Release() when done dealing with
 * the array.
 */
// static
Devices* Devices::GetInstance()
{
    J2dTraceLn(J2D_TRACE_INFO, "Devices::GetInstance");
    CriticalSection::Lock l(arrayLock);
    if (theInstance != NULL) {
        theInstance->AddReference();
    } else {
        J2dTraceLn(J2D_TRACE_ERROR,
                   "Devices::GetInstance NULL instance");
    }
    return theInstance;
}

/**
 * Retrieve a pointer to an item in the array and register a
 * reference to the array.  This increases the refCount of the
 * instance, used to track when the array can be deleted.
 *
 * This method must be called while holding a reference to the instance.
 *
 * If adjust parameter is true (default), adjust the index into the
 * devices array so that it falls within the current devices array.
 * This is needed because the devices array can be changed at any
 * time, and the index may be from the old array. But in some
 * cases we prefer to know that the index is incorrect.
 *
 */
AwtWin32GraphicsDevice *Devices::GetDeviceReference(int index,
                                                    BOOL adjust)
{
    J2dTraceLn2(J2D_TRACE_INFO,
                "Devices::GetDeviceReference index=%d adjust?=%d",
                index, adjust);

    AwtWin32GraphicsDevice * ret = GetDevice(index, adjust);
    if (ret != NULL) {
        AddReference();
    }
    return ret;
}

/**
 * Returns a reference to a device with the passed index.
 *
 * This method does not increase the ref count of the Devices instance.
 *
 * This method must be called while holding a reference to the instance.
 */
AwtWin32GraphicsDevice *Devices::GetDevice(int index, BOOL adjust)
{
    J2dTraceLn2(J2D_TRACE_INFO,
                "Devices::GetDevice index=%d adjust?=%d",
                index, adjust);
    if (index < 0 || index >= numDevices) {
        if (!adjust) {
            J2dTraceLn1(J2D_TRACE_WARNING,
                        "Devices::GetDevice: "\
                        "incorrect index %d, returning NULL.", index);
            return NULL;
        }
        J2dTraceLn1(J2D_TRACE_WARNING,
                    "Devices::GetDevice: "\
                    "adjusted index %d to 0.", index);
        index = 0;
    }
    return devices[index];
}

/**
 * Returns a raw reference to the incapsulated array.
 *
 * This method does not increase the ref count of the Devices instance.
 *
 * This method must be called while holding a reference to the instance.
 */
AwtWin32GraphicsDevice **Devices::GetRawArray()
{
    J2dTraceLn(J2D_TRACE_INFO, "Devices::GetRawArray");
    return devices;
}


/**
 * Decreases the reference count of the array. If the refCount goes to 0,
 * then there are no more references to the array and all of the
 * array elements, the array itself, and this object can be destroyed.
 *
 * Returns the number of references left after it was decremented.
 */
int Devices::Release()
{
    J2dTraceLn(J2D_TRACE_INFO, "Devices::Release");
    CriticalSection::Lock l(arrayLock);

    int refs = --refCount;

    J2dTraceLn1(J2D_TRACE_VERBOSE, "  refCount=%d", refs);

    if (refs == 0) {
        J2dTraceLn(J2D_TRACE_VERBOSE, "  disposing the array");
        if (devices != NULL) {
            for (int i = 0; i < numDevices; ++i) {
                if (devices[i] != NULL) {
                    delete devices[i];
                    devices[i] = NULL;
                }
            }
            free(devices);
            // null out data, can help with debugging
            devices = NULL;
        }
        // it's safe to delete the instance and only
        // then release the static lock
        delete this;
        // for safety return immediately after committing suicide
        // (note: can not reference refCount here!)
        return refs;
    } else if (refs < 0) {
        J2dTraceLn1(J2D_TRACE_ERROR,
                    "Devices::Release: Negative ref count! refCount=%d",
                    refs);
    }

    return refs;
}
