/** @file
 * VirtualBox OpenGL Cocoa Window System implementation
 */

/*
 * Copyright (C) 2009-2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

#include <OpenGL/OpenGL.h>

#include "renderspu.h"
#include <iprt/process.h>
#include <iprt/string.h>
#include <iprt/path.h>

#include <cr_string.h>
#include <cr_mem.h>

GLboolean renderspu_SystemInitVisual(VisualInfo *pVisInfo)
{
    CRASSERT(pVisInfo);

/*    cocoaGLVisualCreate(&pCtxInfo->context);*/

    return GL_TRUE;
}

GLboolean renderspu_SystemCreateContext(VisualInfo *pVisInfo, ContextInfo *pCtxInfo, ContextInfo *pSharedCtxInfo)
{
    CRASSERT(pVisInfo);
    CRASSERT(pCtxInfo);

    pCtxInfo->currentWindow = NULL;

    cocoaGLCtxCreate(&pCtxInfo->context, pVisInfo->visAttribs, pSharedCtxInfo ? pSharedCtxInfo->context : NULL);

    return GL_TRUE;
}

void renderspu_SystemDestroyContext(ContextInfo *pCtxInfo)
{
    if(!pCtxInfo)
        return;

    if(pCtxInfo->context)
    {
        cocoaGLCtxDestroy(pCtxInfo->context);
        pCtxInfo->context = NULL;
    }
}

void renderspuFullscreen(WindowInfo *pWinInfo, GLboolean fFullscreen)
{
    /* Real fullscreen isn't supported by VirtualBox */
}

GLboolean renderspu_SystemVBoxCreateWindow(VisualInfo *pVisInfo, GLboolean fShowIt, WindowInfo *pWinInfo)
{
    CRASSERT(pVisInfo);
    CRASSERT(pWinInfo);

    /* VirtualBox is the only frontend which support 3D right now. */
    char pszName[256];
    if (RTProcGetExecutablePath(pszName, sizeof(pszName)))
        /* Check for VirtualBox and VirtualBoxVM */
        if (RTStrNICmp(RTPathFilename(pszName), "VirtualBox", 10) != 0)
            return GL_FALSE;

    pWinInfo->visual = pVisInfo;
    pWinInfo->window = NULL;
    pWinInfo->nativeWindow = NULL;
    pWinInfo->currentCtx = NULL;

#ifdef __LP64__
    NativeNSViewRef pParentWin = (NativeNSViewRef)render_spu_parent_window_id;
#else /* __LP64__ */
    NativeNSViewRef pParentWin = (NativeNSViewRef)(uint32_t)render_spu_parent_window_id;
#endif /* __LP64__ */

    cocoaViewCreate(&pWinInfo->window, pWinInfo, pParentWin, pVisInfo->visAttribs);

    if (fShowIt)
        renderspu_SystemShowWindow(pWinInfo, fShowIt);

    return GL_TRUE;
}

void renderspu_SystemReparentWindow(WindowInfo *pWinInfo)
{
#ifdef __LP64__
    NativeNSViewRef pParentWin = (NativeNSViewRef)render_spu_parent_window_id;
#else /* __LP64__ */
    NativeNSViewRef pParentWin = (NativeNSViewRef)(uint32_t)render_spu_parent_window_id;
#endif /* __LP64__ */
    cocoaViewReparent(pWinInfo->window, pParentWin);
}

void renderspu_SystemDestroyWindow(WindowInfo *pWinInfo)
{
    CRASSERT(pWinInfo);

    cocoaViewDestroy(pWinInfo->window);
}

void renderspu_SystemWindowPosition(WindowInfo *pWinInfo, GLint x, GLint y)
{
    CRASSERT(pWinInfo);

#ifdef __LP64__
    NativeNSViewRef pParentWin = (NativeNSViewRef)render_spu_parent_window_id;
#else /* __LP64__ */
    NativeNSViewRef pParentWin = (NativeNSViewRef)(uint32_t)render_spu_parent_window_id;
#endif /* __LP64__ */

    /*pParentWin is unused in the call, otherwise it might hold incorrect value if for ex. last reparent call was for
      a different screen*/
    cocoaViewSetPosition(pWinInfo->window, pParentWin, x, y);
}

void renderspu_SystemWindowSize(WindowInfo *pWinInfo, GLint w, GLint h)
{
    CRASSERT(pWinInfo);

    cocoaViewSetSize(pWinInfo->window, w, h);
}

void renderspu_SystemGetWindowGeometry(WindowInfo *pWinInfo, GLint *pX, GLint *pY, GLint *pW, GLint *pH)
{
    CRASSERT(pWinInfo);

    cocoaViewGetGeometry(pWinInfo->window, pX, pY, pW, pH);
}

void renderspu_SystemGetMaxWindowSize(WindowInfo *pWinInfo, GLint *pW, GLint *pH)
{
    CRASSERT(pWinInfo);

    *pW = 10000;
    *pH = 10000;
}

void renderspu_SystemShowWindow(WindowInfo *pWinInfo, GLboolean fShowIt)
{
    CRASSERT(pWinInfo);

    cocoaViewShow(pWinInfo->window, fShowIt);
}

void renderspu_SystemVBoxPresentComposition( WindowInfo *window, const struct VBOXVR_SCR_COMPOSITOR_ENTRY *pChangedEntry )
{
    cocoaViewPresentComposition(window->window, pChangedEntry);
}

void renderspu_SystemMakeCurrent(WindowInfo *pWinInfo, GLint nativeWindow, ContextInfo *pCtxInfo)
{
/*    if(pWinInfo->visual != pCtxInfo->visual)*/
/*        printf ("visual mismatch .....................\n");*/

    nativeWindow = 0;

    if (pWinInfo && pCtxInfo)
        cocoaViewMakeCurrentContext(pWinInfo->window, pCtxInfo->context);
    else
        cocoaViewMakeCurrentContext(NULL, NULL);
}

void renderspu_SystemSwapBuffers(WindowInfo *pWinInfo, GLint flags)
{
    CRASSERT(pWinInfo);

    cocoaViewDisplay(pWinInfo->window);
}

GLboolean renderspu_SystemWindowNeedEmptyPresent(WindowInfo *pWinInfo)
{
    return cocoaViewNeedsEmptyPresent(pWinInfo->window);
}

void renderspu_SystemWindowVisibleRegion(WindowInfo *pWinInfo, GLint cRects, const GLint* paRects)
{
    CRASSERT(pWinInfo);

    cocoaViewSetVisibleRegion(pWinInfo->window, cRects, paRects);
}

void renderspu_SystemWindowApplyVisibleRegion(WindowInfo *pWinInfo)
{
}

int renderspu_SystemInit()
{
    return VINF_SUCCESS;
}

int renderspu_SystemTerm()
{
    CrGlslTerm(&render_spu.GlobalShaders);
    return VINF_SUCCESS;
}

static SPUNamedFunctionTable * renderspuFindEntry(SPUNamedFunctionTable *aFunctions, const char *pcszName)
{
    SPUNamedFunctionTable *pCur;

    for (pCur = aFunctions ; pCur->name != NULL ; pCur++)
    {
        if (!crStrcmp( pcszName, pCur->name ) )
        {
            return pCur;
        }
    }

    AssertFailed();

    return NULL;
}

typedef struct CR_RENDER_CTX_INFO
{
    ContextInfo * pContext;
    WindowInfo * pWindow;
} CR_RENDER_CTX_INFO;

void renderspuCtxInfoInitCurrent(CR_RENDER_CTX_INFO *pInfo)
{
    GET_CONTEXT(pCurCtx);
    pInfo->pContext = pCurCtx;
    pInfo->pWindow = pCurCtx ? pCurCtx->currentWindow : NULL;
}

void renderspuCtxInfoRestoreCurrent(CR_RENDER_CTX_INFO *pInfo)
{
    GET_CONTEXT(pCurCtx);
    if (pCurCtx == pInfo->pContext && (!pCurCtx || pCurCtx->currentWindow == pInfo->pWindow))
        return;
    renderspuPerformMakeCurrent(pInfo->pWindow, 0, pInfo->pContext);
}

GLboolean renderspuCtxSetCurrentWithAnyWindow(ContextInfo * pContext, CR_RENDER_CTX_INFO *pInfo)
{
    WindowInfo * window;
    renderspuCtxInfoInitCurrent(pInfo);

    if (pInfo->pContext == pContext)
        return GL_TRUE;

    window = pContext->currentWindow;
    if (!window)
    {
        window = renderspuGetDummyWindow(pContext->BltInfo.Base.visualBits);
        if (!window)
        {
            WARN(("renderspuGetDummyWindow failed"));
            return GL_FALSE;
        }
    }

    Assert(window);

    renderspuPerformMakeCurrent(window, 0, pContext);
    return GL_TRUE;
}

void renderspu_SystemDefaultSharedContextChanged(ContextInfo *fromContext, ContextInfo *toContext)
{
    CRASSERT(fromContext != toContext);

    if (!CrGlslIsInited(&render_spu.GlobalShaders))
    {
        CrGlslInit(&render_spu.GlobalShaders, &render_spu.blitterDispatch);
    }

    if (fromContext)
    {
        if (CrGlslNeedsCleanup(&render_spu.GlobalShaders))
        {
            CR_RENDER_CTX_INFO Info;
            if (renderspuCtxSetCurrentWithAnyWindow(fromContext, &Info))
            {
                CrGlslCleanup(&render_spu.GlobalShaders);
                renderspuCtxInfoRestoreCurrent(&Info);
            }
            else
                WARN(("renderspuCtxSetCurrentWithAnyWindow failed!"));
        }
    }
    else
    {
        CRASSERT(!CrGlslNeedsCleanup(&render_spu.GlobalShaders));
    }

    CRASSERT(!CrGlslNeedsCleanup(&render_spu.GlobalShaders));

    if (toContext)
    {
        CR_RENDER_CTX_INFO Info;
        if (renderspuCtxSetCurrentWithAnyWindow(toContext, &Info))
        {
            int rc = CrGlslProgGenAllNoAlpha(&render_spu.GlobalShaders);
            if (!RT_SUCCESS(rc))
                WARN(("CrGlslProgGenAllNoAlpha failed, rc %d", rc));

            renderspuCtxInfoRestoreCurrent(&Info);
        }
        else
            crWarning("renderspuCtxSetCurrentWithAnyWindow failed!");
    }
}

AssertCompile(sizeof (GLhandleARB) == sizeof (void*));

static VBoxGLhandleARB crHndlSearchVBox(GLhandleARB hNative)
{
    CRASSERT(!(((uintptr_t)hNative) >> 32));
    return (VBoxGLhandleARB)((uintptr_t)hNative);
}

static GLhandleARB crHndlSearchNative(VBoxGLhandleARB hVBox)
{
    return (GLhandleARB)((uintptr_t)hVBox);
}

static VBoxGLhandleARB crHndlAcquireVBox(GLhandleARB hNative)
{
    CRASSERT(!(((uintptr_t)hNative) >> 32));
    return (VBoxGLhandleARB)((uintptr_t)hNative);
}

static GLhandleARB crHndlReleaseVBox(VBoxGLhandleARB hVBox)
{
    return (GLhandleARB)((uintptr_t)hVBox);
}

static void SPU_APIENTRY renderspu_SystemDeleteObjectARB(VBoxGLhandleARB obj)
{
    GLhandleARB hNative = crHndlReleaseVBox(obj);
    if (!hNative)
    {
        crWarning("no native for %d", obj);
        return;
    }

    render_spu.pfnDeleteObject(hNative);
}

static void SPU_APIENTRY renderspu_SystemGetAttachedObjectsARB( VBoxGLhandleARB containerObj, GLsizei maxCount, GLsizei * pCount, VBoxGLhandleARB * obj )
{
    GLhandleARB *paAttachments;
    GLhandleARB hNative = crHndlSearchNative(containerObj);
    GLsizei count, i;

    if (pCount)
        *pCount = 0;

    if (!hNative)
    {
        crWarning("no native for %d", obj);
        return;
    }

    paAttachments = crCalloc(maxCount * sizeof (*paAttachments));
    if (!paAttachments)
    {
        crWarning("crCalloc failed");
        return;
    }

    render_spu.pfnGetAttachedObjects(hNative, maxCount, &count, paAttachments);
    if (pCount)
        *pCount = count;
    if (count > maxCount)
    {
        crWarning("count too big");
        count = maxCount;
    }

    for (i = 0; i < count; ++i)
    {
        obj[i] = crHndlSearchVBox(paAttachments[i]);
        CRASSERT(obj[i]);
    }

    crFree(paAttachments);
}

static VBoxGLhandleARB SPU_APIENTRY renderspu_SystemGetHandleARB(GLenum pname)
{
    GLhandleARB hNative = render_spu.pfnGetHandle(pname);
    VBoxGLhandleARB hVBox;
    if (!hNative)
    {
        crWarning("pfnGetHandle failed");
        return 0;
    }
    hVBox = crHndlAcquireVBox(hNative);
    CRASSERT(hVBox);
    return hVBox;
}

static void SPU_APIENTRY renderspu_SystemGetInfoLogARB( VBoxGLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog )
{
    GLhandleARB hNative = crHndlSearchNative(obj);
    if (!hNative)
    {
        crWarning("invalid handle!");
        return;
    }

    render_spu.pfnGetInfoLog(hNative, maxLength, length, infoLog);
}

static void SPU_APIENTRY renderspu_SystemGetObjectParameterfvARB( VBoxGLhandleARB obj, GLenum pname, GLfloat * params )
{
    GLhandleARB hNative = crHndlSearchNative(obj);
    if (!hNative)
    {
        crWarning("invalid handle!");
        return;
    }

    render_spu.pfnGetObjectParameterfv(hNative, pname, params);
}

static void SPU_APIENTRY renderspu_SystemGetObjectParameterivARB( VBoxGLhandleARB obj, GLenum pname, GLint * params )
{
    GLhandleARB hNative = crHndlSearchNative(obj);
    if (!hNative)
    {
        crWarning("invalid handle!");
        return;
    }

    render_spu.pfnGetObjectParameteriv(hNative, pname, params);
}

uint32_t renderspu_SystemPostprocessFunctions(SPUNamedFunctionTable *aFunctions, uint32_t cFunctions, uint32_t cTable)
{
    SPUNamedFunctionTable * pEntry;

    pEntry = renderspuFindEntry(aFunctions, "DeleteObjectARB");
    if (pEntry)
    {
        render_spu.pfnDeleteObject = (PFNDELETE_OBJECT)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemDeleteObjectARB;
    }

    pEntry = renderspuFindEntry(aFunctions, "GetAttachedObjectsARB");
    if (pEntry)
    {
        render_spu.pfnGetAttachedObjects = (PFNGET_ATTACHED_OBJECTS)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemGetAttachedObjectsARB;
    }

    pEntry = renderspuFindEntry(aFunctions, "GetHandleARB");
    if (pEntry)
    {
        render_spu.pfnGetHandle = (PFNGET_HANDLE)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemGetHandleARB;
    }

    pEntry = renderspuFindEntry(aFunctions, "GetInfoLogARB");
    if (pEntry)
    {
        render_spu.pfnGetInfoLog = (PFNGET_INFO_LOG)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemGetInfoLogARB;
    }

    pEntry = renderspuFindEntry(aFunctions, "GetObjectParameterfvARB");
    if (pEntry)
    {
        render_spu.pfnGetObjectParameterfv = (PFNGET_OBJECT_PARAMETERFV)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemGetObjectParameterfvARB;
    }

    pEntry = renderspuFindEntry(aFunctions, "GetObjectParameterivARB");
    if (pEntry)
    {
        render_spu.pfnGetObjectParameteriv = (PFNGET_OBJECT_PARAMETERIV)pEntry->fn;
        pEntry->fn = (SPUGenericFunction)renderspu_SystemGetObjectParameterivARB;
    }

    return cFunctions;
}
