#include "tkInt.h"

const Tk_ClassProcs C3dCanvasProcs = {
    sizeof(Tk_ClassProcs),	/* size */
    C3d_Canvas_WorldChanged,	/* worldChangedProc */
    C3d_Native_CreateCanvas3d,	                /* createProc */
    NULL		        /* modalProc */
};

static void
C3d_Native_ResizeContext(C3dWidget *pCanvas,int w,int h)
{
    Window xwin = Tk_WindowId(pCanvas->tkwin);
    XResizeWindow(Tk_Display(pCanvas->tkwin), xwin, w, h);
    pCanvas->options.width = w;
    pCanvas->options.height = h;
}

/*
 * cd3Winmain.c --
 *
 *     This file contains the implementation of the Tcl interface of the
 *     canvas3d widget on windows.
 *
 *     This file is called by an include directive in c3dmain.c
 *-------------------------------------------------------------------------
 */
static void
C3d_Native_DeleteContext(C3dWidget *pCanvas)
{
    wglDeleteContext(pCanvas->context);
}

/*
 *---------------------------------------------------------------------------
 *
 * windowsError --
 *
 *     Store the string form of the win32 error returned by GetLastError() in
 *     the result of the interpreter interp.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
windowsError(Tcl_Interp *interp, const char *zFunc)
{
    char zBuf[128];
    CHAR *lpMsgBuf = NULL;
    DWORD errorcode = GetLastError();

    FormatMessageA(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        errorcode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (CHAR *) &lpMsgBuf,
        0, NULL
    );
    sprintf(zBuf, "() failed (%d) - ", (int)errorcode);

    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, zFunc, zBuf, (char *)lpMsgBuf, 0);
    Tcl_BackgroundError(interp);

    if (lpMsgBuf != NULL) {
	LocalFree(lpMsgBuf);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * getHBITMAP --
 *
 *     For a pixmap allocated with Tk_GetPixmap(), return the underlying
 *     win32 HBITMAP handle.
 *
 *     TODO: Eliminate the dependancy on an internal Tk struct.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static HBITMAP
getHBITMAP(Pixmap pixmap)
{
    struct TkInternalWinBitmap {
        int type;
        HBITMAP handle;
        Colormap colormap;
        int depth;
    };

    return ((struct TkInternalWinBitmap *)pixmap)->handle;
}


#if (TK_MAJOR_VERSION >= 8 && TK_MINOR_VERSION < 5)
/*
** Prior to 8.5.9 the tkWinChildProc was not present in the
** in the Tk stubs table. This function looks it up.
*/
static LRESULT CALLBACK
Win32WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static LRESULT (CALLBACK *tkWinChildProc)(HWND, UINT, WPARAM, LPARAM) = 0;
    if (tkWinChildProc == NULL) {
	WNDCLASS childClass;
        GetClassInfo(Tk_GetHINSTANCE(),"TkChild", &childClass);
        tkWinChildProc = childClass.lpfnWndProc;
    }
    return tkWinChildProc(hwnd, message, wParam, lParam);
}
#endif

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_FreePixMaps --
 *
 *     This function frees all currently allocated pixmaps used by the
 *     -saveunder option. This function is called in the following three
 *     circumstances:
 *
 *         * When the widget is being deleted,
 *         * When the -saveunder mode changes,
 *         * When the window size changes.
 *
 *     The pixmaps are allocated within C3d_Native_Select3dContext().
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_FreePixMaps(C3dWidget *pCanvas)
{
    pCanvas->pixmapheight = 0;
    pCanvas->pixmapwidth = 0;
    if (pCanvas->pixmap) {
        C3dFreePixmap(Tk_Display(pCanvas->tkwin), pCanvas->pixmap);
        pCanvas->pixmap = 0;
    }

    if (pCanvas->hpixDC) {
	DeleteDC(pCanvas->hpixDC);
	pCanvas->hpixDC = 0;
    }
    if (pCanvas->pixcontext) {
	wglDeleteContext(pCanvas->pixcontext);
	pCanvas->pixcontext = 0;
    }
    if (pCanvas->pixmap3d) {
	DeleteObject(pCanvas->pixmap3d);
	pCanvas->pixmap3d = 0;
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_CreateCanvas3d --
 *
 *     This function is invoked when Tk_MakeWindowExist() is called to
 *     create the 3d-canvas window (it is registered using
 *     Tk_SetClassProcs() in function C3d_CanvasObjCmd()). This is where we do
 *     platform specific OpenGL initialisation.
 *
 * Results:
 *     X-window Window identifier to be wrapped into a Tk_Window by Tk.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static Window
C3d_Native_CreateCanvas3d(
    Tk_Window tkwin,
    Window parent,                /* X-windows wrapper around parent HWND */
    ClientData clientData
) {
    C3dWidget *pCanvas = (C3dWidget *)clientData;
    Tcl_Interp *interp = pCanvas->interp;

    /* There are two almost entirely autonomous implementations of this
     * function.  One for windows and another for X11 displays. The windows
     * implementation appears first.
     */
    static int classRegistered = 0;  /* True after RegisterClass() is called */

    HWND hNew;                       /* New window created by this function */
    HDC hDC;                         /* Device Display Context of new window */
    HWND hParent;                    /* Win32 parent window of new window */
    HINSTANCE hInstance;             /* Application instance handle */
    Window wNew;                     /* X-window wrapper around hNew */
    PIXELFORMATDESCRIPTOR pfd;       /* Requested pixel format properties */
    int iPixelFormat;                /* Actual pixel format */

    hParent = Tk_GetHWND(parent);
    hInstance = Tk_GetHINSTANCE();

    /* TODO: Not threadsafe! */
    if (!classRegistered) {
        WNDCLASSA winClass;
        memset(&winClass, 0, sizeof(WNDCLASS));

        winClass.style = CS_HREDRAW | CS_VREDRAW;
        winClass.hInstance = hInstance;
        winClass.lpszClassName = "canvas3d class";
#if (TK_MAJOR_VERSION >= 8 && TK_MINOR_VERSION >= 6)
        winClass.lpfnWndProc = TkWinChildProc;
#else
        winClass.lpfnWndProc = Win32WinProc;
#endif

        if (!RegisterClassA(&winClass)) {
            Tcl_ResetResult(interp);
            Tcl_AppendResult(interp, "canvas3d: RegisterClass() failed", 0);
            return 0;
        }
        classRegistered = 1;
    }

    /* Call the Win32 API to create a new window at the top of the stacking
     * order. Then use Tk_AttachHWND() to associate the Win32 window with the
     * Tk window. Tk_AttachHWND() returns an X-windows wrapper around the Win32
     * window. This is the value this function will return (if successful).
     */
    hNew = CreateWindowA(
        "canvas3d class",               /* Class name for window. */
        NULL,                    /* Window name. We don't need one. */
        WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,    /* Window style */
        0, 0,                    /* Initial x,y coordinates for window */
        pCanvas->options.width,  /* Initial width of window */
        pCanvas->options.height, /* Initial height of window */
        hParent,                 /* Parent of new window */
        NULL,                    /* Menu widget handle. Not required. */
        hInstance,               /* Application instance. */
        NULL                     /* Context passed with WM_CREATE. */
    );
    SetWindowPos(
        hNew,                    /* The new window. */
        HWND_TOP,                /* Put it on the top of the stacking order. */
        0, 0, 0, 0,              /* (x,y,width,height) - all ignored. */
        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE    /* Flags */
    );
    wNew = Tk_AttachHWND(tkwin, hNew);

    /* Fill in the pixel format descriptor. */
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 32;
    pfd.iLayerType = PFD_MAIN_PLANE;

    hDC = GetDC(hNew);
    assert(hDC);
    iPixelFormat = ChoosePixelFormat(hDC, &pfd);
    if (0 == iPixelFormat) {
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "canvas3d: ChoosePixelFormat() failed", 0);
        return 0;
    }
    if (0 == SetPixelFormat(hDC, iPixelFormat, &pfd)) {
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "canvas3d: SetPixelFormat() failed", 0);
        return 0;
    }
    DescribePixelFormat(hDC, iPixelFormat, sizeof(pfd), &pfd);

    /* TODO: This assert will fail on non truecolor displays. */
    assert(!(pfd.dwFlags & PFD_NEED_PALETTE));

    pCanvas->hDC = hDC;
    pCanvas->context = wglCreateContext(hDC);
    assert(pCanvas->context);

    return wNew;
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Select3dContext --
 *
 *     This function selects the correct OpenGL context to draw the 3d scene
 *     using either glXMakeCurrent() or wglMakeCurrent(), depending on the
 *     platform (X11 or win32).
 *
 *     The context selected draws either to the back-buffer of the
 *     double-buffered window, or to a platform specific bitmap (a Pixmap for
 *     X11, a DIB for win32).
 *
 *     If required, this function may allocate and/or deallocate bitmaps.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Select3dContext(C3dWidget *pCanvas)
{
    Tk_Window win = pCanvas->tkwin;
    Display *dpy = Tk_Display(win);
    int saveunder = pCanvas->options.saveunder;
    int w = Tk_Width(win);
    int h = Tk_Height(win);

    /* On win32, "-saveunder all" behaves in the same way as "-saveunder 3d" */
    if (saveunder == SAVEUNDER_ALL) saveunder = SAVEUNDER_3D;

    /* If the -saveunder mode is not "none", then we need at least
     * C3dWidget.pixmap.
     */
    if (saveunder != SAVEUNDER_NONE && !pCanvas->pixmap) {
        Pixmap pixmap;
        pixmap = C3dGetPixmap(dpy, Tk_WindowId(win), w, h, Tk_Depth(win));
        assert(pixmap);
        pCanvas->pixmap = pixmap;
        pCanvas->pixmapwidth = w;
        pCanvas->pixmapheight = h;
    }

    assert(saveunder == SAVEUNDER_NONE || w == pCanvas->pixmapwidth);
    assert(saveunder == SAVEUNDER_NONE || h == pCanvas->pixmapheight);

    /* If the -saveunder mode is "3d", then we also need C3dWidget.pixmap3d */
    if (saveunder == SAVEUNDER_3D && !pCanvas->pixmap3d) {
        BITMAPINFOHEADER BIH;
        HBITMAP hB;
        HDC hDC;
        int iPixelFormat;
        PIXELFORMATDESCRIPTOR pfd;

        memset(&BIH, 0, sizeof(BITMAPINFOHEADER));
        BIH.biSize = sizeof(BITMAPINFOHEADER);
        BIH.biWidth = w;
        BIH.biHeight = h;
        BIH.biPlanes = 1;
        BIH.biBitCount = 32;
        BIH.biCompression = BI_RGB;

        hDC = CreateCompatibleDC(NULL);
        assert(hDC);
        pCanvas->hpixDC = hDC;
        hB = CreateDIBSection(hDC, (BITMAPINFO*)&BIH, DIB_PAL_COLORS, 0, 0, 0);
        assert(hB);
        pCanvas->pixmap3d = hB;
        SelectObject(hDC, hB);

        memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
        pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
        pfd.nVersion = 1;
        pfd.dwFlags = PFD_SUPPORT_GDI | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_BITMAP;
        pfd.iPixelType = PFD_TYPE_RGBA;
        pfd.cDepthBits = 32;
        pfd.cColorBits = 32;
        pfd.iLayerType = PFD_MAIN_PLANE;

        iPixelFormat = ChoosePixelFormat(hDC, &pfd);
        if (0 == iPixelFormat) {
            windowsError(pCanvas->interp, "ChoosePixelFormat");
            return;
        }
        if (0 == SetPixelFormat(hDC, iPixelFormat, &pfd)) {
            windowsError(pCanvas->interp, "SetPixelFormat");
            return;
        }
        pCanvas->pixcontext = wglCreateContext(hDC);
        assert(pCanvas->pixcontext);
    }

    /* Finally, select the appropriate context. */
    if (saveunder) {
	if (!wglMakeCurrent(pCanvas->hpixDC, pCanvas->pixcontext)) {
            windowsError(pCanvas->interp, "wglMakeCurrent (saveunder)");
	}
    } else {
	if (!wglMakeCurrent(pCanvas->hDC, pCanvas->context)) {
            windowsError(pCanvas->interp, "wglMakeCurrent (!saveunder)");
        }
    }
    glViewport(0, 0, w, h);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Release3dContext --
 *
 *     This function releases the current OpenGL context.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Release3dContext(C3dWidget *pCanvas)
{
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_Pixmap3dToPixmap --
 *
 *     Copy from C3dPixmap.pixmap to C3dPixmap.pixmap3d.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_Pixmap3dToPixmap(C3dWidget *pCanvas)
{
    int w = pCanvas->pixmapwidth;
    int h = pCanvas->pixmapheight;

    HDC hDC = CreateCompatibleDC(NULL);
    HBITMAP hBitmap = getHBITMAP(pCanvas->pixmap);
    assert(pCanvas->pixmap && pCanvas->pixmap3d);
    SelectObject(hDC, hBitmap);
    BitBlt(hDC, 0, 0, w, h, pCanvas->hpixDC, 0, 0, SRCCOPY);
    DeleteDC(hDC);
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_PixmapToPixmap3d --
 *
 *     Copy from C3dPixmap.pixmap3d to C3dPixmap.pixmap.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_PixmapToPixmap3d(C3dWidget *pCanvas)
{
    assert(!"Cannot happen");
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_PixmapToWindow --
 *
 *     If the -saveunder mode is not "none", copy from the pixmap
 *     C3dWidget.pixmap to the main window. If it is "none", then swap the
 *     OpenGL windows buffers.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_PixmapToWindow(C3dWidget *pCanvas)
{
    if (pCanvas->options.saveunder == SAVEUNDER_NONE) {
        SwapBuffers(pCanvas->hDC);
    } else {
        int w = pCanvas->pixmapwidth;
        int h = pCanvas->pixmapheight;
        Tk_Window win = pCanvas->tkwin;
        Window xwin = Tk_WindowId(win);
        GC gc = C3dGetGC(win, 0, 0);
	assert(pCanvas->pixmap);
        XCopyArea(Tk_Display(win), pCanvas->pixmap, xwin, gc, 0, 0, w, h, 0, 0);
        C3dFreeGC(Tk_Display(pCanvas->tkwin), gc);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_EventProc --
 *
 *     Widget callback for <Expose> events. In this case we need to redraw
 *     the scene. We don't do the drawing immediately, instead
 *     C3dDrawWhenIdle() is invoked.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void
C3d_Native_EventProc(ClientData clientData, XEvent *eventPtr)
{
}

static int
C3d_Native_Init(Tcl_Interp *interp)
{
    if (0) {
	/* Squelch compiler warning */
	C3d_Native_ResizeContext(NULL, 0, 0);
    }
    if (0) {
	/* Squelch compiler warning */
	C3d_Native_Pixmap3dToPixmap(NULL);
    }
    if (0) {
	/* Squelch compiler warning */
	C3d_Native_PixmapToPixmap3d(NULL);
    }
    return TCL_OK;
}
