#include <tkMacOSXPrivate.h>


/* Private things from tk */
int tkMacOSXGCEnabled=0;

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

/* Copy of a non-exported function from Tk */
NSView*
canvas3dMacOSXDrawableView(
    MacDrawable *macWin)
{
    NSView *result = nil;

    if (!macWin) {
	result = nil;
    } else if (!macWin->toplevel) {
	result = macWin->view;
    } else if (!(macWin->toplevel->flags & TK_EMBEDDED)) {
	result = macWin->toplevel->view;
    } else {
	TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr);
	if (contWinPtr) {
	    result = canvas3dMacOSXDrawableView(contWinPtr->privatePtr);
	}
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * 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(pCanvas)
    C3dWidget *pCanvas;
{     
    Tk_Window tkwin = pCanvas->tkwin;
    TkWindow *winPtr = (TkWindow *) tkwin;  
    MacDrawable *macWin =  (MacDrawable *) winPtr->window;
    NSView *view = canvas3dMacOSXDrawableView(macWin);
    int saveunder = pCanvas->options.saveunder;
    int w = Tk_Width(tkwin);
    int h = Tk_Height(tkwin);

    if(!view) return;

    if(!pCanvas->context) {
      NSOpenGLPixelFormat *pix;
      NSOpenGLPixelFormatAttribute   attribs[] = {
        NSOpenGLPFAWindow,
        NSOpenGLPFADoubleBuffer,    // double buffered
        NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32, // 24 bit depth buffer
        (NSOpenGLPixelFormatAttribute)nil
      };
  
      pix = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
      if (pix == nil) {
          Tcl_SetResult(pCanvas->interp, "couldn't choose pixel format",
                  TCL_STATIC);
          return;
      }


      NSOpenGLView *nsview= [[NSOpenGLView alloc] initWithFrame:NSZeroRect 
                                     pixelFormat:pix];
      pCanvas->context = TkMacOSXMakeUncollectable(nsview);
      [view addSubview:pCanvas->context];
      
      NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pix shareContext:nil];
      if (context == nil) {
        Tcl_SetResult(pCanvas->interp, "couldn't create context", TCL_STATIC);
        return;
      }
    }

    C3d_Native_ResizeContext(pCanvas,w,h);
    [[pCanvas->context openGLContext] setView:pCanvas->context];

    /* 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) {
        /* Noop.  saveunder is not yet supported on AGL */
    }


    /* Finally, select the appropriate context. */
    [[pCanvas->context openGLContext] makeCurrentContext];
    pCanvas->options.width = w;
    pCanvas->options.height = h;
    glViewport(0, 0, w, h);
}

static void C3d_Native_Release3dContext(pCanvas)
    C3dWidget *pCanvas;
{
}

static void C3d_Native_ResizeContext(C3dWidget *pCanvas,int w,int h) {
  Window xwin = Tk_WindowId(pCanvas->tkwin);
  Rect r, rt;
  NSRect    rect;
  TkWindow *tkw = (TkWindow *) pCanvas->tkwin;
  TkWindow *tkt = tkw->privatePtr->toplevel->winPtr;

  XResizeWindow(Tk_Display(pCanvas->tkwin), xwin, w, h);
  TkMacOSXWinBounds(tkw, &r);
  TkMacOSXWinBounds(tkt, &rt);

  pCanvas->options.width = w;
  pCanvas->options.height = h;
  rect.origin.x = r.left - rt.left;
  rect.origin.y = rt.bottom - r.bottom;
  rect.size.width = r.right - r.left;
  rect.size.height = r.bottom - r.top;
  [pCanvas->context setFrame:rect];
  //[pCanvas->context update];  
}

/*
TODO: It appears that Tk only makes an NSView for toplevel windows.
Also it looks like NSOpenGL does not have the equivalent of AGL_BUFFER_RECT
that allows opengl drawing to just part of an NSView.  So we might need to
create our own NSView for controlling the opengl bounds.
Look at TkMacOSXMakeRealWindowExist() in tkMacOSXWm.c.
*/

static void C3d_Native_DeleteContext(C3dWidget *pCanvas) {
  if(pCanvas->context) {
    TkMacOSXMakeCollectableAndRelease(pCanvas->context);
    pCanvas->context=nil;
  }
}


/*
 *---------------------------------------------------------------------------
 *
 * 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(pCanvas)
    C3dWidget *pCanvas;
{
    if (pCanvas->pixmap) {
        pCanvas->pixmapheight = 0;
        pCanvas->pixmapwidth = 0;
        C3dFreePixmap(Tk_Display(pCanvas->tkwin), pCanvas->pixmap);
        pCanvas->pixmap = 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(tkwin, parent, clientData)
    Tk_Window tkwin; 
    Window parent;                /* X-windows wrapper around parent HWND */
    ClientData clientData;
{
    C3dWidget *pCanvas=(C3dWidget *)clientData;
    TkWindow *winPtr = (TkWindow *) tkwin;
    Display *pDisplay = Tk_Display(tkwin); /* Application display */

    Colormap cmap;
    Window window;
    pCanvas->tkwin=tkwin;
    
    window = TkpMakeWindow(winPtr, parent);
    (void) XMapWindow(pDisplay, window);

    cmap = Tk_Colormap(tkwin);    
    Tk_SetWindowVisual(tkwin,
	    DefaultVisual(pDisplay, DefaultScreen(pDisplay)), 32,
	    cmap);
    {

    }
    return window;
}

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

    w = h; /* lint */
    assert(!"cannot happen");
}


/*
 *---------------------------------------------------------------------------
 *
 * C3d_Native_PixmapToPixmap3d --
 *
 *     Copy from C3dPixmap.pixmap3d to C3dPixmap.pixmap.
 *
 * Results:
 *     None.
 *
 * Side effects:
 *     None.
 *
 *---------------------------------------------------------------------------
 */
static void C3d_Native_PixmapToPixmap3d(pCanvas)
    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(pCanvas)
    C3dWidget *pCanvas;
{
    [[pCanvas->context openGLContext] flushBuffer];
    if (pCanvas->options.saveunder != SAVEUNDER_NONE) {
        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) {
  tkMacOSXGCEnabled = ([NSGarbageCollector defaultCollector] != nil);
  return TCL_OK;
}
