/*
 *        PsychToolbox3/Source/Common/WindowBank.c
 *
 *        AUTHORS:
 *
 *              Allen.Ingling@nyu.edu        awi
 *              mario.kleiner.de@gmail.com      mk
 *
 *        PLATFORMS:
 *
 *                All
 *
 *        HISTORY:
 *
 *                07/18/02  awi         Wrote it. This is a second attempt.  The first
 *                                      attempted to maintain state with static
 *                                      variables internal to functions. Much too
 *                                      awkward for prototyping purposes.
 *                07/22/05  mk          Windowbank array is allocated and resized dynamically now, so no limit to maximum
 *                                      number of windows anymore.
 *
 *        DESCRIPTION:
 *
 *                WindowBank contains functions for storing records of open
 *                onscreen and offscreen windows.
 *
 *                See WindowHelpers for convenience functions which access the fields
 *                of records defined in WindowBank.h
*/

#include "Screen.h"

/* ------------------------------------------------------------------
 *        Declare static persistant variables local to WindwBank.c variables.
 */

// MK: windowRecordArrayWINBANK is allocated in InitWindowBank(), released in CloseWindowBank(),
// and dynamically resized in PsychCreateWindowArrayRecord(), if necessary to accomodate more windows.
// We resize in chunks of PSYCH_ALLOC_WINDOW_RECORDS_INC window slots to avoid/reduce possible memory-
// fragmentation and make allocation efficient. This approach is better than using linked-lists, because
// it is easier to implement and still allows access to each windowRecord in constant time via array-lookup,
// avoiding increasing access-delays that would happen with long linked lists.
static PsychWindowRecordType **windowRecordArrayWINBANK=NULL;
static PsychScreenRecordType *screenRecordArrayWINBANK[PSYCH_ALLOC_SCREEN_RECORDS];
static int numWindowRecordsWINBANK = 0;

// MK: See InitWindowBank() for initial setup of variables below...
static int PSYCH_MAX_WINDOWS = 0; // Maximum number of onscreen-/offscreen-windows/textures...
static int PSYCH_LAST_WINDOW = 0; // the highest possible window index
static int PSYCH_ALLOC_WINDOW_RECORDS = 0; //current length of dynamic array allocated to hold window pointers.

#define PSYCH_ALLOC_WINDOW_RECORDS_INC 4096  // Increment when extending the window bank...

//Local function prototypes
PsychWindowIndexType FindEmptyWindowIndex(void);

//    Window accessor functions for the outside world.

/*
 *        PsychFindScreenWindowFromScreenNumber()
 *
 *        Walk down the list of onscreen windows looking for the window open on the specified screen and and set *winRec to point to its window recored if we find one.
 *        Otherwise set *winRec to NULL. If screenNumber is kPsychUnaffiliatedWindow, we return the first onscreen window we find, regardless of screen.
 */

void PsychFindScreenWindowFromScreenNumber(int screenNumber, PsychWindowRecordType **winRec)
{
    int i, numWindows;
    PsychWindowRecordType **windowArray;

    *winRec=NULL;
    PsychCreateVolatileWindowRecordPointerList(&numWindows, &windowArray);
    for (i=0;i<numWindows;i++) {
        if (PsychIsOnscreenWindow(windowArray[i])) {
            if ((windowArray[i]->screenNumber == screenNumber) || (screenNumber == kPsychUnaffiliatedWindow)) {
                *winRec=windowArray[i];
                break;
            }
        }
    }
    PsychDestroyVolatileWindowRecordPointerList(windowArray);
}

/*
 *        Initialize list of window record pointers.
 */
PsychError InitWindowBank(void)
{
    PsychWindowIndexType i;

    PSYCH_ALLOC_WINDOW_RECORDS=PSYCH_ALLOC_WINDOW_RECORDS_INC; // Initial length of array allocated to hold window pointers.

    // MK: Allocate an initial windowbank of default size PSYCH_ALLOC_WINDOW_RECORDS:
    windowRecordArrayWINBANK=malloc(PSYCH_ALLOC_WINDOW_RECORDS * sizeof(PsychWindowRecordType*));
    if (windowRecordArrayWINBANK==NULL) {
        // Out of memory!
        return(PsychError_outofMemory);
    }

    PSYCH_MAX_WINDOWS=PSYCH_ALLOC_WINDOW_RECORDS-10;
    PSYCH_LAST_WINDOW=PSYCH_ALLOC_WINDOW_RECORDS-1; //the highest possible window index

    // Initialize with NULL-Ptrs:
    for(i=PSYCH_FIRST_WINDOW;i<=PSYCH_LAST_WINDOW;i++)
        windowRecordArrayWINBANK[i] = NULL;

    return(PsychError_none); //no error
}

/*
 *        Free storage for window records.  All windows should be
 *        close first.
 */
PsychError CloseWindowBank(void)
{
    PsychWindowIndexType i;

    for (i = PSYCH_FIRST_WINDOW; i <= PSYCH_LAST_WINDOW; i++) {
        if (windowRecordArrayWINBANK[i] != NULL)
            free(windowRecordArrayWINBANK[i]);
    }

    // Release the array of pointers itself:
    free(windowRecordArrayWINBANK);

    windowRecordArrayWINBANK=NULL;
    PSYCH_MAX_WINDOWS=0;
    PSYCH_LAST_WINDOW=0;
    PSYCH_ALLOC_WINDOW_RECORDS=0;

    return(PsychError_none);
}

/*
 *        Count the number of windows of the specified type.
 */
int PsychCountOpenWindows(PsychWindowType winType)
{
    int i, total=0;

    for (i = PSYCH_FIRST_WINDOW; i <= PSYCH_LAST_WINDOW; i++) {
        if (windowRecordArrayWINBANK[i]) {
            if (winType==kPsychAnyWindow)
                ++total;
            else if (windowRecordArrayWINBANK[i]->windowType==winType)
                ++total;
        }
    }
    return(total);
}

/*
 * PsychIsLastOnscreenWindow() - Is this the last open onscreen window?
 */
psych_bool PsychIsLastOnscreenWindow(PsychWindowRecordType *windowRecord)
{
    if (!PsychIsOnscreenWindow(windowRecord))
        return(FALSE);

    if (PsychCountOpenWindows(kPsychDoubleBufferOnscreen) + PsychCountOpenWindows(kPsychSingleBufferOnscreen) > 1)
        return(FALSE);
    else
        return(TRUE);
}

/*
 *        PsychIsLastOnscreenWindowOnScreen()
 *
 *        Is this the last open onscreen window for the associated screen?
 */
psych_bool PsychIsLastOnscreenWindowOnScreen(PsychWindowRecordType *windowRecord)
{
    int i;

    if (!PsychIsOnscreenWindow(windowRecord))
        return(FALSE);

    for(i = PSYCH_FIRST_WINDOW; i <= PSYCH_LAST_WINDOW; i++) {
        if (windowRecordArrayWINBANK[i]) {
            if (windowRecordArrayWINBANK[i]->screenNumber == windowRecord->screenNumber &&
                windowRecordArrayWINBANK[i]->windowIndex != windowRecord->windowIndex &&
                PsychIsOnscreenWindow(windowRecordArrayWINBANK[i]))
                return(FALSE);
        }
    }

    return(TRUE);
}

/*
 *        Allocate and return an empty window record
 */
void PsychCreateWindowRecord(PsychWindowRecordType **winRec)
{
    PsychWindowRecordType **tmpwindowRecordArrayWINBANK=NULL;
    PsychWindowIndexType i;

    //check for space
    if(numWindowRecordsWINBANK==PSYCH_MAX_WINDOWS) {
        // Windowbank - array is full! We reallocate it, extending it
        // by PSYCH_ALLOC_WINDOW_RECORDS_INC additional slots for additional windows.
        tmpwindowRecordArrayWINBANK=realloc(windowRecordArrayWINBANK, (PSYCH_ALLOC_WINDOW_RECORDS + PSYCH_ALLOC_WINDOW_RECORDS_INC) * sizeof(PsychWindowRecordType*));
        if (tmpwindowRecordArrayWINBANK==NULL) {
            // realloc() failed due to out-of-memory!
            PsychErrorExit(PsychError_outofMemory);   //out of memory
        }

        // Success! Update limits and initialize new slots:
        windowRecordArrayWINBANK = tmpwindowRecordArrayWINBANK;
        PSYCH_ALLOC_WINDOW_RECORDS+=PSYCH_ALLOC_WINDOW_RECORDS_INC;
        PSYCH_MAX_WINDOWS+=PSYCH_ALLOC_WINDOW_RECORDS_INC;
        // Initialize new slots with NULL-Ptrs:
        i=PSYCH_LAST_WINDOW + 1;
        PSYCH_LAST_WINDOW+=PSYCH_ALLOC_WINDOW_RECORDS_INC;
        for(;i<=PSYCH_LAST_WINDOW;i++) windowRecordArrayWINBANK[i] = NULL;
        // Ready for addition of new windows.
    }

    // Allocate storage, zero-init it:
    if ((*winRec = (PsychWindowRecordType *) calloc(1, sizeof(PsychWindowRecordType))) == NULL)
        PsychErrorExit(PsychError_outofMemory);   //out of memory

    //increment counts
    ++numWindowRecordsWINBANK;

    //store the record at a free pointer index and set the records field to the index.
    (*winRec)->windowIndex = FindEmptyWindowIndex();
    windowRecordArrayWINBANK[(*winRec)->windowIndex] = *winRec;

    //set a flag to indicate that the contents of the window record are not completely valid.
    (*winRec)->isValid=FALSE;

    // Set window type to kPsychNoWindow to signal that the window doesn't yet exist.
    // Although the windowRecord is partially initialized, there's no Windowsystem onscreen
    // window associated with this and the OpenGL context isn't there either. This is important
    // for error-handling. Windows of type kPsychNoWindow are ignored by the OpenGL and Window system
    // cleanup routine PsychCloseWindow()...
    (*winRec)->windowType = kPsychNoWindow;

    // Assign default number of color channels: 4 is a good number (RGBA), but this
    // gets overwritten in appropriate places...
    (*winRec)->nrchannels=4;

    // Initialize all imaging pipeline related fields to safe defaults:
    PsychInitImagingPipelineDefaultsForWindowRecord(*winRec);

    //Intialize the text settings field within the window record to default values which should be (but are not yet) specified in Psychtoolbox preferences.
    PsychInitTextRecordSettings(&((*winRec)->textAttributes));

    // Init text 2D affine transform matrix to identity transformation:
    (*winRec)->text2DMatrix[0][0] = 1;
    (*winRec)->text2DMatrix[0][1] = 0;
    (*winRec)->text2DMatrix[1][0] = 0;
    (*winRec)->text2DMatrix[1][1] = 1;
    (*winRec)->text2DMatrix[0][2] = 0;
    (*winRec)->text2DMatrix[1][2] = 0;

    //Initialize the fields about textures.
    PsychInitWindowRecordTextureFields(*winRec);

    //Initialize the fields used to store alpha blending factors as set by glBlendFunc()
    PsychInitWindowRecordAlphaBlendingFactors(*winRec);

    //Initialize line stipple values
    (*winRec)->stipplePattern=0xAAAA;        //alternating pixels stipple pattern
    (*winRec)->stippleFactor=1;
    (*winRec)->stippleEnabled=FALSE;

    // Set GL color buffer writemask glColorMask to "all enabled":
    (*winRec)->colorMask[0] = GL_TRUE;
    (*winRec)->colorMask[1] = GL_TRUE;
    (*winRec)->colorMask[2] = GL_TRUE;
    (*winRec)->colorMask[3] = GL_TRUE;

    // Initialize stereo settings:
    (*winRec)->stereomode=0;
    (*winRec)->stereodrawbuffer=2;              // No stero drawbuffer selected at window open time.
    (*winRec)->slaveWindow=NULL;                // No slave window attached.
    (*winRec)->parentWindow=NULL;               // No parent window attached.
    (*winRec)->targetFlipFieldType=-1;          // Don't care if flipping should only happen in even or odd video refresh frames.
    (*winRec)->auxbuffer_dirty[0]=FALSE;        // AUX-Buffers clean on startup.
    (*winRec)->auxbuffer_dirty[1]=FALSE;

    // Initialize gamma table pointers for deferred loading:
    (*winRec)->inRedTable = NULL;
    (*winRec)->inGreenTable = NULL;
    (*winRec)->inBlueTable = NULL;
    (*winRec)->loadGammaTableOnNextFlip = 0;

    // No override projection matrices:
    (*winRec)->proj = NULL;

    // Set cached display list handles for drawing functions to "uninitialized":
    (*winRec)->fillOvalDisplayList = 0;
    (*winRec)->frameOvalDisplayList = 0;

    // No special flags set by default:
    (*winRec)->specialflags = 0;
    // No capabilities setup yet:
    (*winRec)->gfxcaps = 0;

    // No aux param vector set by default:
    (*winRec)->auxShaderParams = NULL;
    (*winRec)->auxShaderParamsCount = 0;

    // Reset memory accounting info for this windowRecord:
    (*winRec)->surfaceSizeBytes = 0;

    // NULL out flipinfo struct:
    (*winRec)->flipInfo = NULL;

    // Init our shader handles to zero -- Off by default:
    (*winRec)->unclampedDrawShader = 0;
    (*winRec)->defaultDrawShader = 0;

    // Set surface addresses to zero:
    (*winRec)->gpu_preflip_Surfaces[0] = 0;
    (*winRec)->gpu_preflip_Surfaces[1] = 0;

    // Bits per color component value: Initialize to 8 bits per channel. This is true for all
    // standard textures, offscreen- and onscreen windows. Only FBO backed drawables can start
    // off with 16 bpc (float/fixed) or 32 bpc float, or get upgraded to such resolutions. Such
    // upgrades always happen in a maketexture function or FBO creation function.
    // This will be later replaced by true bitdepths for onscreen windows, e.g., to account for
    // 10 bit native framebuffers on latest GPUs:
    (*winRec)->bpc = 8;

    // Sync of doublebuffer-swaps to vertical retrace signal starts off as disabled:
    (*winRec)->vSynced = FALSE;

    // Default to unknown maximum supported texture size:
    (*winRec)->maxTextureSize = 0;

    // No GPU core id assigned yet:
    (*winRec)->gpuCoreId[0] = 0;
    (*winRec)->gpuRenderTimeQuery = 0;
    (*winRec)->gpuRenderTime = 0.0;

    // No swap group or barrier assigned:
    (*winRec)->swapGroup = 0;
    (*winRec)->swapBarrier = 0;

    // Zero-init OpenML timing values:
    (*winRec)->reference_ust = 0;
    (*winRec)->reference_msc = 0;
    (*winRec)->reference_sbc = 0;
    (*winRec)->osbuiltin_swaptime = 0.0;
    (*winRec)->target_sbc = 0;
    (*winRec)->lastSwaptarget_msc = 0;

    // ...and other timing values:
    (*winRec)->postflip_vbltimestamp = -1.0;

    // Init to zero default:
    (*winRec)->VBL_Startline = 0;
    (*winRec)->VBL_Endline = 0;

    // One-Time setup of some parameters needed for userspace OpenGL context at first real bind:
    (*winRec)->needsViewportSetup = TRUE;

    // HDR scaling factors for SDR -> HDR or HDR normalized -> HDR mapping:
    // These default to good values for standard SDR/LDR windows, so both LDR and HDR content
    // can be scaled to display in a reasonable way on a SDR display device. These defaults
    // will get overriden in HDR mode by PsychHDR() during HDR window setup, and may get
    // overriden for SDR windows as appropriate for tone-mapping, but these defaults should
    // be good as a starter:
    (*winRec)->maxSDRToHDRScaleFactor = 1.0;      // SDR content: Set default 1.0 for standard SDR/LDR window. Iow. no need to map.
    (*winRec)->normalizedToHDRScaleFactor = 20.0; // HDR content: Set default for mapping 0 - 500 nits into standard SDR/LDR window range 0 - 1.

    return;
}

/*
 *        Free the window record memory and set the corresponding window array entry to null.
 *        Its up to the calling function to close the window FIRST.
 */
PsychError FreeWindowRecordFromIndex(PsychWindowIndexType windex)
{
    if (windex < PSYCH_FIRST_SCREEN)
        return(PsychError_scumberNotWindex); //I was passed a screen number, not a window index

    if (windex <= PSYCH_LAST_SCREEN)
        return(PsychError_scumberNotWindex); //I was passed a screen number, not a window pointer

    if(windowRecordArrayWINBANK[windex] ==NULL)
        return(PsychError_invalidWindex);    //window does not exist

    // Release temporary gamma tables, if any:
    free(windowRecordArrayWINBANK[windex]->inRedTable);
    free(windowRecordArrayWINBANK[windex]->inGreenTable);
    free(windowRecordArrayWINBANK[windex]->inBlueTable);

    // Release override projection matrices if any:
    free(windowRecordArrayWINBANK[windex]->proj);

    free(windowRecordArrayWINBANK[windex]);
    windowRecordArrayWINBANK[windex] = NULL;
    --numWindowRecordsWINBANK;
    return(PsychError_none);
}

/*
 *        Free the window record memory and set the corresponding window array entry to null.
 *        Its up to the calling function to close the window FIRST.
 */
PsychError FreeWindowRecordFromPntr(PsychWindowRecordType *winRec)
{
    PsychWindowIndexType windowIndex;

    if(winRec == NULL)
        return(PsychError_nullWinRecPntr); //passed a null window pointer

    windowIndex = winRec->windowIndex;
    return(FreeWindowRecordFromIndex(windowIndex));
}

/*
 *        Accept a window pointer or a screen number (psychIndex) and return true if its a valid screen
 *        number and false otherwise
 */
psych_bool IsValidScreenNumber(PsychNumdexType numdex)
{
    return((int)((numdex >=PSYCH_FIRST_SCREEN) && (numdex < PsychGetNumDisplays())));
}

/*
 *        IsValidScreenNumberOrUnaffiliated()
 *
 *        Accept a window pointer or a screen number (psychIndex) and return true iff its a valid screen
 *        number or is the token indicating an unaffiliated window.
 */
psych_bool IsValidScreenNumberOrUnaffiliated(PsychNumdexType numdex)
{
    return(((int)((numdex >=PSYCH_FIRST_SCREEN) && (numdex < PsychGetNumDisplays()))) || numdex == kPsychUnaffiliatedWindow);
}

/*
 *        IsScreenNumberUnaffiliated()
 *
 */
psych_bool IsScreenNumberUnaffiliated(PsychNumdexType numdex)
{
    return(numdex == kPsychUnaffiliatedWindow);
}

/*
 *        Accept a window pointer or a screen number and return true if its a valid window pointer
 *        number and false otherwise
 */
psych_bool IsWindowIndex(PsychNumdexType numdex)
{
    if(numdex<PSYCH_FIRST_WINDOW || numdex>PSYCH_LAST_WINDOW)
        return(FALSE);  //outside of index range

    //check to see if their is window record there.
    if(windowRecordArrayWINBANK[numdex] != NULL)
        return(1);

    return(0);
}

/*
 *        Accept a window index and return a pointer to the window record, which will be NULL if no
 *        window exists.  We return an error if the index is invalid.
 *
 *        This function also checks the valid flag in the window to make sure that that fields were
 *        successfully filled when the window was created.
 *
 *        All retrieval of window records should go through this function because it checks for valid
 *        windows.
 *
 */
PsychError FindWindowRecord(PsychWindowIndexType windowIndex, PsychWindowRecordType **windowRecord)
{
    // Check for valid index: Must be within bounds of our array of windowRecord pointers, and the referenced slot must be a non-NULL ptr to a windowRecord:
    // Preinit to safe default of NULL:
    *windowRecord = NULL;
    if(windowIndex<PSYCH_FIRST_WINDOW || windowIndex>PSYCH_LAST_WINDOW || ((*windowRecord = windowRecordArrayWINBANK[windowIndex]) == NULL)) return(PsychError_invalidWindex); // Invalid index!

    // It is a windowRecord: Check if it is valid, ie., has been properly initialized by PTB:
    PsychCheckIfWindowRecordIsValid(*windowRecord);  // This would, e.g., fail because of an early exit when the window was created. It is considered an internal error if this triggers.

    // Success.
    return(PsychError_none);
}


/*
 *        Accept a screen number and return the corresponding screen record
 */
PsychError FindScreenRecord(int screenNumber, PsychScreenRecordType **screenRecord)
{
    if(IsValidScreenNumber(screenNumber)){
        *screenRecord = screenRecordArrayWINBANK[screenNumber];
        return(PsychError_none);
    } else
        return(PsychError_invalidScumber);
}

/*
 *    PsychCreateVolatileWindowRecordPointerList()
 *
 *    Allocates memory for and returns an array holding indices of all open windows.
 *
 *    This is kind of lame. It iterates over the window list twice.
 *
 */
void PsychCreateVolatileWindowRecordPointerList(int *numWindows, PsychWindowRecordType ***pointerList)
{
    int i,j=0;
    PsychWindowRecordType **tempList;

    *numWindows=0;
    for(i=PSYCH_FIRST_WINDOW;i<=PSYCH_LAST_WINDOW;i++){
        if(windowRecordArrayWINBANK[i])
            ++(*numWindows);
    }

    tempList=(PsychWindowRecordType **)malloc(sizeof(PsychWindowRecordType *) * *numWindows);
    for(i=PSYCH_FIRST_WINDOW;i<=PSYCH_LAST_WINDOW;i++){
        if(windowRecordArrayWINBANK[i])
            tempList[j++]=windowRecordArrayWINBANK[i];
    }

    *pointerList=tempList;
}

/*
 *    PsychDestroyVolatileWindowIndexList()
 */
void PsychDestroyVolatileWindowRecordPointerList(PsychWindowRecordType **pointerList)
{
    free((void *)pointerList);
}

/* PsychAssignParentWindow()
 * Assign a specific windowRecord as the parent windowRecord of a windowRecord.
 * This sets up the "parent" pointer and copies a variety of state and settings from the
 * parent to the child, e.g., OpenGL context, device context, color clamp settings etc...,
 * i.e., it performs the one time setup of the childs properties with the parents properties.
 *
 * Usually called by all functions that create new offscreen windows or textures to assign
 * the creating onscreen window to a child offscreen window or texture or proxy.
 *
 */
void  PsychAssignParentWindow(PsychWindowRecordType *childWin, PsychWindowRecordType *parentWin)
{
    // Assign parent:
    childWin->parentWindow = parentWin;

    // Copy some state and settings from parent to child:
    memcpy(&childWin->targetSpecific, &parentWin->targetSpecific, sizeof(parentWin->targetSpecific));

    // Copy default drawing shaders from parent:
    childWin->defaultDrawShader   = parentWin->defaultDrawShader;
    childWin->unclampedDrawShader = parentWin->unclampedDrawShader;

    // Copy color range and mode from parent window:
    childWin->colorRange = parentWin->colorRange;
    childWin->applyColorRangeToDoubleInputMakeTexture = parentWin->applyColorRangeToDoubleInputMakeTexture;

    // Copy imaging mode flags from parent:
    childWin->imagingMode = parentWin->imagingMode;

    // Inherit capability bits of parents context:
    childWin->gfxcaps = parentWin->gfxcaps;
    childWin->maxTextureSize = parentWin->maxTextureSize;

    // Type of display backend and graphics api:
    childWin->glApiType = parentWin->glApiType;
    childWin->winsysType = parentWin->winsysType;

    return;
}

/* PsychGetParentWindow()
 * Return the windowRecord* of the parent window of a given window, or the windowRecord itself
 * if the window doesn't have a parent because its a top-level onscreen window, ie., it is its
 * own parent.
 */
PsychWindowRecordType* PsychGetParentWindow(PsychWindowRecordType *windowRecord)
{
    while (windowRecord->parentWindow) windowRecord = windowRecord->parentWindow;
    return(windowRecord);
}

//  ------------------------------------------------------------------
//    Accessor functions for stuff internal to WindowBank.cpp.
//

PsychWindowIndexType FindEmptyWindowIndex(void)
{
    int i;
    for(i=PSYCH_FIRST_WINDOW;i<=PSYCH_LAST_WINDOW;i++){
        if(windowRecordArrayWINBANK[i]==NULL)
            return(i);
    }

    PsychErrorExitMsg(PsychError_toomanyWin,NULL);
    return(PSYCH_INVALID_WINDEX);
}
