/*****************************************************************************
 *                     Yumetech, Inc Copyright (c) 2005 - 2006
 *                               Java Source
 *
 * This source is licensed under the BSD-style License
 * Please read http://www.opensource.org/licenses/bsd-license.php for more
 * information or docs/BSD.txt in the downloaded code.
 *
 * This software comes with the standard NO WARRANTY disclaimer for any
 * purpose. Use it at your own risk. If there's a problem you get to fix it.
 *
 ****************************************************************************/

package org.j3d.opengl.swt.internal.ri.x1164;

// External imports
import org.eclipse.swt.internal.gtk.*;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLCapabilitiesChooser;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Display;

import com.sun.opengl.impl.x11.GLX;
import com.sun.opengl.impl.x11.XVisualInfo;

// Local imports
import org.j3d.opengl.swt.internal.ri.SurfaceController;

/**
 * An implementation of the {@link X11GLDrawable} that works with SWT
 * based implementations.
 * <p>
 *
 * @author Justin Couch
 * @version $Revision: 1.4 $
 */
public class X11OnscreenGLDrawable extends X11GLDrawable
    implements SurfaceController,
               Listener
{
    /** The component that this drawable is wrapping */
    private Composite component;

    /** Set the realisation state */
    private boolean realized;

    /*<WeakReferences of <GLContext>>*/
    private ArrayList createdContexts;

    /** Flag to say if the surface is locked or not */
    private boolean isLocked;

    /** The OpenGL window ID handle */
    private long glWindowID;

    /**
     * Create a new instance of the drawable that wraps the given component.
     *
     * @param component The component this drawable works with
     * @param capabilities The user requested capabilities, may be null
     * @param chooser User provided choice handler, may be null
     */
    public X11OnscreenGLDrawable(Composite component,
                                 GLCapabilities capabilities,
                                 GLCapabilitiesChooser chooser)
    {
        super(capabilities, chooser);

        this.component = component;
        isLocked = false;

        createdContexts = new ArrayList();

        component.addListener(SWT.Resize, this);
        component.addListener(SWT.Dispose, this);
    }

    //---------------------------------------------------------------
    // Methods defined by GLDrawable
    //---------------------------------------------------------------

    /**
     * Create a new context instance that may share data with the given
     * context.
     *
     * @param sharedWith The context instance to share, or null if not shared
     */
    public GLContext createContext(GLContext shareWith)
    {
        X11OnscreenGLContext context =
            new X11OnscreenGLContext(this, this, shareWith);

        // NOTE: Keep track of the contexts created using weak refs so that
        // shutdown works cleanly.
        synchronized(this)
        {
            ArrayList newContexts = new ArrayList();
            newContexts.addAll(createdContexts);
            newContexts.add(new WeakReference(context));
            createdContexts = newContexts;
        }

        return context;
    }

    /**
     * Indicates to on-screen GLDrawable implementations whether the underlying
     * window has been created and can be drawn into.
     *
     * @param state true when this has been realised by the canvas
     * @see GLDrawable#setRealized
     */
    public void setRealized(boolean realized)
    {
        this.realized = realized;
    }

    /**
     * Requests a new width and height for this drawable.
     *
     * @param width The new width of the drawable
     * @param height The new height of the drawable
     */
    public void setSize(int width, int height)
    {
        component.setSize(width, height);
    }

    /**
     * Get the current width of the drawable in pixels.
     *
     * @return A value greater than or equal to zero
     */
    public int getWidth()
    {
        Point pt = component.getSize();
        return pt.x;
    }

    /**
     * Get the current height of the drawable in pixels.
     *
     * @return A value greater than or equal to zero
     */
    public int getHeight()
    {
        Point pt = component.getSize();
        return pt.y;
    }

    /**
     * Swap the front and back buffers of this drawable now.
     */
    public void swapBuffers() throws GLException
    {
// From the AWT version. Do we need this for SWT?
//        lockToolkit();
        try
        {
            boolean didLock = false;

            if(drawable == 0)
            {
                if(lockSurface() == SURFACE_NOT_READY)
                  return;

                didLock = true;
            }

            GLX.glXSwapBuffers(display, drawable);

            if(didLock)
                unlockSurface();

        }
        finally
        {
//          unlockToolkit();
        }
    }

    //---------------------------------------------------------------
    // Methods defined by SurfaceController
    //---------------------------------------------------------------

    /**
     * Lock the surface now so that we can draw to it.
     *
     * @return One of the surface constants from this interface
     * @throws GLException The surface is already locked by someone else
     */
    public int lockSurface() throws GLException
    {
        if(!realized)
          return SURFACE_NOT_READY;

        if(isLocked)
          throw new GLException("Surface already locked");

        int ret_val = SUCCESS;

        if(drawable == 0)
        {
            // Have we just created a new surface?
            if(realized && visualID == 0)
            {
                Runnable r = new Runnable()
                {
                    public void run()
                    {
                        OS.gtk_widget_realize(component.handle);

                        long window = OS.GTK_WIDGET_WINDOW(component.handle);
                        display = OS.gdk_x11_drawable_get_xdisplay(window);

                        long screen = OS.gdk_screen_get_default();
                        int x_screen = OS.XDefaultScreen(display);

                        int[] glxAttrib =
                            X11GLXUtils.glCapabilities2AttribList(capabilities,
                                                                  false,
                                                                  false,
                                                                  display,
                                                                  x_screen);
                        XVisualInfo vinfo =
                            GLX.glXChooseVisual(display, x_screen, glxAttrib, 0);

                        visualID = vinfo.visualid();

                        long gdk_visual =
                          OS.gdk_x11_screen_lookup_visual(screen, (int)visualID);

                        GdkWindowAttr attrs = new GdkWindowAttr();
                        attrs.width = 1;
                        attrs.height = 1;
                        attrs.event_mask =
                            OS.GDK_KEY_PRESS_MASK | OS.GDK_KEY_RELEASE_MASK |
                            OS.GDK_FOCUS_CHANGE_MASK | OS.GDK_POINTER_MOTION_MASK |
                            OS.GDK_BUTTON_PRESS_MASK | OS.GDK_BUTTON_RELEASE_MASK |
                            OS.GDK_ENTER_NOTIFY_MASK | OS.GDK_LEAVE_NOTIFY_MASK |
                            OS.GDK_EXPOSURE_MASK | OS.GDK_VISIBILITY_NOTIFY_MASK |
                            OS.GDK_POINTER_MOTION_HINT_MASK;
                        attrs.window_type = OS.GDK_WINDOW_CHILD;
                        attrs.visual = gdk_visual;

                        glWindowID = OS.gdk_window_new(window, attrs, OS.GDK_WA_VISUAL);
                        OS.gdk_window_set_user_data(glWindowID, component.handle);

                        if((component.getStyle() & SWT.NO_BACKGROUND) != 0)
                            OS.gdk_window_set_back_pixmap(window, 0, false);

                        OS.gdk_window_show(glWindowID);

                        Rectangle rect = component.getClientArea();
                        if(rect.width != 0 && rect.height != 0)
                        {
                            OS.gdk_window_move(glWindowID, rect.x, rect.y);
                            OS.gdk_window_resize(glWindowID,
                                                 rect.width,
                                                 rect.height);
                        }
                    }
                };

                Display d = component.getDisplay();
                d.syncExec(r);
            }

            drawable = OS.gdk_x11_drawable_get_xid(glWindowID);
            ret_val = SURFACE_CHANGED;
        }

        return ret_val;
    }

    /**
     * Check to see if the surface is currently locked.
     *
     * @return true if the surface is locked, false otherwise
     */
    public boolean isLockedSurface()
    {
        return isLocked;
    }

    /**
     * Release the surface back to the OS. This may throw an exception if the
     * surface is already unlocked at this point.
     *
     * @throws GLException The surface is already unlocked
     */
    public void unlockSurface()
    {
        if(!isLocked)
          throw new GLException("Surface already unlocked");

        isLocked = false;
    }

    //---------------------------------------------------------------
    // Methods defined by Listener
    //---------------------------------------------------------------

    public void handleEvent(Event event)
    {
        if(glWindowID == 0)
            return;

        switch (event.type)
        {
            case SWT.Resize:
                Rectangle rect = component.getClientArea();
                OS.gdk_window_move(glWindowID, rect.x, rect.y);
                OS.gdk_window_resize(glWindowID, rect.width, rect.height);
                break;

            case SWT.Dispose:
                // Not sure if we need this. JOGL may already take care of it for us
                shutdown();
        }
    }

    //---------------------------------------------------------------
    // Local Methods
    //---------------------------------------------------------------

    /**
     * Shutdown the contents of this drawable now. Called in response to th
     * dispose event listener, so we don't really care how clean this is.
     */
    private void shutdown()
    {
        for(int i = 0; i < createdContexts.size(); i++)
        {
            WeakReference ref = (WeakReference)createdContexts.get(i);
            X11OnscreenGLContext ctx = (X11OnscreenGLContext)ref.get();

            // FIXME: clear out unreachable contexts
            if(ctx != null)
                ctx.shutdown();
        }

        createdContexts.clear();

        if(visualID != 0)
        {
            OS.gdk_window_destroy(glWindowID);
            visualID = 0;
            drawable = 0;
            display = 0;
            glWindowID = 0;
        }
    }
}
