import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.media.opengl.DebugGL2;
import javax.media.opengl.DebugGL3;
import javax.media.opengl.DebugGL3bc;
import javax.media.opengl.DebugGL4;
import javax.media.opengl.DebugGL4bc;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL2ES1;
import javax.media.opengl.GL2GL3;
import javax.media.opengl.GL3;
import javax.media.opengl.GL3bc;
import javax.media.opengl.GL4;
import javax.media.opengl.GL4bc;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.fixedfunc.GLLightingFunc;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import javax.media.opengl.glu.GLU;
import javax.swing.Timer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

import com.jogamp.opengl.util.Animator;

public class DummyRCPViewer3D extends ViewPart {

    /** identifier of the view used in the extension point */
    public static final String ID = DummyRCPViewer3D.class.getCanonicalName();

    /** SWT component embedding AWT components */
    private Composite embedded;

    private static final boolean isWindows = System.getProperty("os.name").equalsIgnoreCase(
            "windows");

    @Override
    public void createPartControl(final Composite parent) {
        // we can't use the default Composite because using the AWT bridge
        // requires the SWT.EMBEDDED style
        embedded = new Composite(parent, SWT.EMBEDDED);
        // set the layout so our canvas fills the whole control
        embedded.setLayout(new FillLayout());
        // workaround for the bug 6678385 under Linux
        final Frame frame = SWT_AWT_FrameHelper.new_Frame(embedded);
        // the canvas has to be created once the size of the frame that contains it is no more zero,
        // that is why it is called when the frame is resized
        frame.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                // use Display.getDefault().asyncExec to initialize SWT GUI elements there

                // get the profile
                GLProfile profile = GLProfile.getDefault();
                // this allows us to set particular properties for the GLCanvas
                GLCapabilities glCapabilities = new GLCapabilities(profile);
                // anti aliasing
                glCapabilities.setNumSamples(8);
                glCapabilities.setSampleBuffers(true);

                // we don't need either of these, just an example
                glCapabilities.setDoubleBuffered(true);
                glCapabilities.setHardwareAccelerated(true);

                final GLCanvas canvas = new GLCanvas(glCapabilities);
                canvas.setAutoSwapBufferMode(false);
                canvas.addKeyListener(new KeyAdapter() {

                    @Override
                    public void keyReleased(KeyEvent e) {
                        if (e.getKeyCode() == KeyEvent.VK_B) {
                            canvas.setAutoSwapBufferMode(!canvas.getAutoSwapBufferMode());
                        }
                    }
                });
                canvas.addGLEventListener(new GLEventListener() {

                    private float pyramidRotation;

                    private int frameIndex = 0;

                    @Override
                    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
                        final GL2 gl = GLContext.getCurrentGL().getGL2();
                        final GLU glu = new GLU();

                        gl.glViewport(0, 0, width, height);
                        gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
                        gl.glLoadIdentity();

                        glu.gluPerspective(45.0f, (double) width / (double) height, 0.1f, 1000.0f);

                        gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
                        gl.glLoadIdentity();
                    }

                    @Override
                    public void init(GLAutoDrawable drawable) {
                        // always get a fresh instance of GL to avoid using an invalidated GL one
                        final GL2 gl = GLContext.getCurrentGL().getGL2();
                        // enable V-sync
                        gl.setSwapInterval(1);

                        gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
                        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
                        gl.glClearDepth(1.0f);
                        gl.glEnable(GL.GL_DEPTH_TEST);
                        gl.glDepthFunc(GL.GL_LEQUAL);
                        gl.glHint(GL2ES1.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);

                        // set the debug pipeline
                        if (drawable.getGL().isGL4bc()) {
                            final GL4bc gl4bc = drawable.getGL().getGL4bc();
                            drawable.setGL(new DebugGL4bc(gl4bc));
                        }
                        else {
                            if (drawable.getGL().isGL4()) {
                                final GL4 gl4 = drawable.getGL().getGL4();
                                drawable.setGL(new DebugGL4(gl4));
                            }
                            else {
                                if (drawable.getGL().isGL3bc()) {
                                    final GL3bc gl3bc = drawable.getGL().getGL3bc();
                                    drawable.setGL(new DebugGL3bc(gl3bc));
                                }
                                else {
                                    if (drawable.getGL().isGL3()) {
                                        final GL3 gl3 = drawable.getGL().getGL3();
                                        drawable.setGL(new DebugGL3(gl3));
                                    }
                                    else {
                                        if (drawable.getGL().isGL2()) {
                                            final GL2 gl2 = drawable.getGL().getGL2();
                                            drawable.setGL(new DebugGL2(gl2));
                                        }
                                    }
                                }
                            }
                        }
                    }

                    @Override
                    public void dispose(GLAutoDrawable drawable) {

                    }

                    @Override
                    public void display(GLAutoDrawable drawable) {
                        final GL2 gl = GLContext.getCurrentGL().getGL2();
                        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
                        gl.glLoadIdentity();
                        gl.glTranslatef(-1.5f, 0.0f, -6.0f);
                        gl.glRotatef(pyramidRotation, 0.0f, 1.0f, 0.0f);
                        gl.glBegin(GL.GL_TRIANGLES);
                        gl.glColor3f(1.0f, 0.0f, 0.0f);
                        gl.glVertex3f(0.0f, 1.0f, 0.0f);
                        gl.glColor3f(0.0f, 1.0f, 0.0f);
                        gl.glVertex3f(-1.0f, -1.0f, 1.0f);
                        gl.glColor3f(0.0f, 0.0f, 1.0f);
                        gl.glVertex3f(1.0f, -1.0f, 1.0f);
                        gl.glColor3f(1.0f, 0.0f, 0.0f);
                        gl.glVertex3f(0.0f, 1.0f, 0.0f);
                        gl.glColor3f(0.0f, 0.0f, 1.0f);
                        gl.glVertex3f(1.0f, -1.0f, 1.0f);
                        gl.glColor3f(0.0f, 1.0f, 0.0f);
                        gl.glVertex3f(1.0f, -1.0f, -1.0f);
                        gl.glColor3f(1.0f, 0.0f, 0.0f);
                        gl.glVertex3f(0.0f, 1.0f, 0.0f);
                        gl.glColor3f(0.0f, 1.0f, 0.0f);
                        gl.glVertex3f(1.0f, -1.0f, -1.0f);
                        gl.glColor3f(0.0f, 0.0f, 1.0f);
                        gl.glVertex3f(-1.0f, -1.0f, -1.0f);
                        gl.glColor3f(1.0f, 0.0f, 0.0f);
                        gl.glVertex3f(0.0f, 1.0f, 0.0f);
                        gl.glColor3f(0.0f, 0.0f, 1.0f);
                        gl.glVertex3f(-1.0f, -1.0f, -1.0f);
                        gl.glColor3f(0.0f, 1.0f, 0.0f);
                        gl.glVertex3f(-1.0f, -1.0f, 1.0f);
                        gl.glEnd();
                        pyramidRotation += 0.2f;
                        if (!canvas.getAutoSwapBufferMode()) {
                            // this hack is only required under Windows
                            if (isWindows) {
                                // copy the colored content of the back buffer into the front buffer
                                gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT);
                                gl.glReadBuffer(GL.GL_BACK);
                                gl.glDrawBuffer(GL.GL_FRONT);
                                gl.glCopyPixels(0, 0, canvas.getWidth(), canvas.getHeight(),
                                        GL2GL3.GL_COLOR);
                                gl.glPopAttrib();
                            }
                            /**
                             * when the buffers are not swapped there, the memory consumption
                             * increases a lot
                             */
                            if (frameIndex % 10 == 0) {
                                canvas.swapBuffers();
                            }
                            frameIndex = (frameIndex + 1) % 10;
                        }
                    }
                });

                // finally, add our canvas as a child of the frame
                frame.add(canvas, BorderLayout.CENTER);

                /*
                 * Laying out the components before starting the animator is necessary because
                 * the width of the canvas cannot be equal to zero and the SWT/AWT helper returns
                 * AWT frames with a strange behavior (width and height equal to zero, lazy layout 
                 * and validation). This call is not necessary in plain Swing/AWT applications.
                 * */
                frame.doLayout();

                Animator animator = new Animator(canvas);
                animator.start();

                final ComponentAdapter thatComponentAdapter = this;
                /*
                 * This is a "one shot" listener, it should be called only once. Remove it
                 * so that it will never be called while disposing or changing the viewer.
                 * */
                EventQueue.invokeLater(new Runnable() {

                    @Override
                    public final void run() {
                        frame.removeComponentListener(thatComponentAdapter);
                    }
                });
            }
        });
    }

    @Override
    public void setFocus() {

    }

    private static final class SWT_AWT_FrameHelper {

        private static boolean x11ErrorHandlerFixInstalled = false;

        /**
         * this fix is required for GTK but not for Motif
         */
        private static final boolean x11ErrorHandlerFixRequiredForThisPlatform = "gtk".equals(SWT
                .getPlatform());

        /**
         * this fix is not necessary in Java 1.7
         */
        private static final boolean x11ErrorHandlerFixRequiredForThisJavaVersion = isX11ErrorHandlerFixRequired();

        private static final boolean isX11ErrorHandlerFixRequired() {
            final String javaVersionWithoutUpdateNumber = System.getProperty("java.version")
                    .substring(0, 3);
            return (javaVersionWithoutUpdateNumber.equals("1.0")
                    || javaVersionWithoutUpdateNumber.equals("1.1")
                    || javaVersionWithoutUpdateNumber.equals("1.2")
                    || javaVersionWithoutUpdateNumber.equals("1.3")
                    || javaVersionWithoutUpdateNumber.equals("1.4")
                    || javaVersionWithoutUpdateNumber.equals("1.5") || javaVersionWithoutUpdateNumber
                    .equals("1.6"));
        }

        /**
         * Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=171432
         */
        private static final void initX11ErrorHandlerFix() {
            assert EventQueue.isDispatchThread();

            try {
                // get XlibWrapper.SetToolkitErrorHandler() and XSetErrorHandler() methods
                Class xlibwrapperClass = Class.forName("sun.awt.X11.XlibWrapper");
                final Method setToolkitErrorHandlerMethod = xlibwrapperClass.getDeclaredMethod(
                        "SetToolkitErrorHandler", null);
                final Method setErrorHandlerMethod = xlibwrapperClass.getDeclaredMethod(
                        "XSetErrorHandler", new Class[] { Long.TYPE });
                setToolkitErrorHandlerMethod.setAccessible(true);
                setErrorHandlerMethod.setAccessible(true);

                // get XToolkit.saved_error_handler field
                Class xtoolkitClass = Class.forName("sun.awt.X11.XToolkit");
                final Field savedErrorHandlerField = xtoolkitClass
                        .getDeclaredField("saved_error_handler");
                savedErrorHandlerField.setAccessible(true);

                // determine the current error handler and the value of
                // XLibWrapper.ToolkitErrorHandler
                // (XlibWrapper.SetToolkitErrorHandler() sets the X11 error handler to
                // XLibWrapper.ToolkitErrorHandler and returns the old error handler)
                final Object defaultErrorHandler = setToolkitErrorHandlerMethod.invoke(null, null);
                final Object toolkitErrorHandler = setToolkitErrorHandlerMethod.invoke(null, null);
                setErrorHandlerMethod.invoke(null, new Object[] { defaultErrorHandler });

                // create timer that watches XToolkit.saved_error_handler whether its value is equal
                // to XLibWrapper.ToolkitErrorHandler, which indicates the start of the trouble
                Timer timer = new Timer(200, new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        try {
                            Object savedErrorHandler = savedErrorHandlerField.get(null);
                            if (toolkitErrorHandler.equals(savedErrorHandler)) {
                                // Last saved error handler in XToolkit.WITH_XERROR_HANDLER
                                // is XLibWrapper.ToolkitErrorHandler, which will cause
                                // the StackOverflowError when the next X11 error occurs.
                                // Workaround: restore the default error handler.
                                // Also update XToolkit.saved_error_handler so that
                                // this is done only once.
                                setErrorHandlerMethod.invoke(null,
                                        new Object[] { defaultErrorHandler });
                                savedErrorHandlerField.setLong(null,
                                        ((Long) defaultErrorHandler).longValue());
                            }
                        }
                        catch (Exception ex) {
                            // ignore
                        }

                    }
                });
                timer.start();
            }
            catch (Exception ex) {
                // ignore
            }
        }

        private static final Frame new_Frame(Composite parent) {
            final Frame frame = SWT_AWT.new_Frame(parent);
            if (x11ErrorHandlerFixRequiredForThisJavaVersion
                    && x11ErrorHandlerFixRequiredForThisPlatform && !x11ErrorHandlerFixInstalled) {
                x11ErrorHandlerFixInstalled = true;
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        initX11ErrorHandlerFix();
                    }
                });
            }
            return (frame);
        }
    }
}
