package de.javagl.swogl.samples;



import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.awt.GLJPanel;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;

import com.jogamp.opengl.util.Animator;

import de.javagl.swogl.MatrixUtils;
import de.javagl.swogl.SwoglComponent;
import de.javagl.swogl.SwoglComponents;
import de.javagl.swogl.SwoglContainer;
import de.javagl.swogl.rendering.SwoglSetupJOGL;

/**
 * Demo for the integration of Swogl into an existing JOGL application.<br />
 * <br />
 * Minimal modifications have been applied to the original 'Gears' example. 
 * The places that have been modified for the integration of Swogl
 * are marked with comments containing the String "SwoglDemo".<br />
 * <br />
 * <pre>
 * --- START of original header of Gears.java --------------------------
 * Gears.java <BR>
 * author: Brian Paul (converted to Java by Ron Cemer and Sven Goethel)
 * <P>
 * 
 * This version is equal to Brian Paul's version 1.2 1999/10/21
 * --- END of original header of Gears.java ----------------------------
 * </pre>
 */
public class Gears implements GLEventListener, MouseListener, MouseMotionListener
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Gear Demo");
        GLCanvas canvas = new GLCanvas();

        final Gears gears = new Gears(canvas);
        canvas.addGLEventListener(gears);

        // SwoglDemo: Instead of the canvas, the main component
        // from the SwoglContainer is added to the frame.
        //frame.add(canvas); 
        frame.add(gears.getMainComponent());
        
        frame.setSize(300, 300);
        final Animator animator = new Animator(canvas);
        frame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent e)
            {
                // Run this on another thread than the AWT event queue to
                // make sure the call to Animator.stop() completes before
                // exiting
                new Thread(new Runnable()
                {
                    public void run()
                    {
                        animator.stop();
                        System.exit(0);
                    }
                }).start();
            }
        });
        frame.setVisible(true);
        animator.start();
    }

    private float view_rotx = 20.0f, view_roty = 30.0f, view_rotz = 0.0f;
    private int gear1, gear2, gear3;
    private float angle = 0.0f;

    private int prevMouseX, prevMouseY;
    private boolean mouseRButtonDown = false;


    //--- SwoglDemo part start -----------------------------------------------
    // SwoglDemo: The SwoglSetup and SwoglContainer used for this application
    private SwoglSetupJOGL swoglSetup;
    private SwoglContainer swoglContainer;
    
    // SwoglDemo: Added Swing components to demonstrate the functionality  
    private JTextField angleTextField;
    private float angleDelta = 1.0f;
    
    public Gears(GLCanvas canvas)
    {
        // SwoglDemo: Create a SwoglSetup for JOGL and a 
        // SwoglContainer with some demo components
        swoglSetup = new SwoglSetupJOGL();
        swoglContainer = SwoglContainer.create(canvas, swoglSetup);
        
        JComponent controlPanel = createControlPanel();
        SwoglComponent swoglComponent = 
            SwoglComponents.create(controlPanel);
        swoglComponent.setTransform(
            MatrixUtils.mul(
                MatrixUtils.translate(-2,3,2), 
                MatrixUtils.scale(0.05f),
                MatrixUtils.rotateY(Math.toRadians(-10))));
        swoglContainer.add(swoglComponent);
    }
    
    // SwoglDemo: Creates the Swing components to be shown as SwoglComponents
    private JComponent createControlPanel()
    {
        JComponent controlPanel = new JPanel(new GridLayout(1,0));
        controlPanel.add(new JLabel("Angle:", JLabel.CENTER));
        angleTextField = new JTextField(5);
        controlPanel.add(angleTextField);
        
        final JToggleButton button = new JToggleButton("Run");
        button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (button.isSelected())
                {
                    angleDelta = 1.0f;
                }
                else
                {
                    angleDelta = 0.0f;
                }
            }
        });
        button.setSelected(true);
        controlPanel.add(button);
        return controlPanel;
    }
    
    JComponent getMainComponent()
    {
        return swoglContainer.getMainComponent();
    }
    //--- SwoglDemo part end -------------------------------------------------
    

    public void init(GLAutoDrawable drawable)
    {
        // Use debug pipeline
        // drawable.setGL(new DebugGL(drawable.getGL()));

        GL2 gl = drawable.getGL().getGL2();
        
        // SwoglDemo: Pass the current GL to Swogl
        swoglSetup.setGL(gl);
        
        System.err.println("INIT GL IS: " + gl.getClass().getName());

        System.err.println("Chosen GLCapabilities: " + drawable.getChosenGLCapabilities());

        gl.setSwapInterval(1);

        float pos[] =
        { 5.0f, 5.0f, 10.0f, 0.0f };
        float red[] =
        { 0.8f, 0.1f, 0.0f, 1.0f };
        float green[] =
        { 0.0f, 0.8f, 0.2f, 1.0f };
        float blue[] =
        { 0.2f, 0.2f, 1.0f, 1.0f };

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, pos, 0);
        gl.glEnable(GL2.GL_CULL_FACE);
        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);
        gl.glEnable(GL2.GL_DEPTH_TEST);

        /* make the gears */
        gear1 = gl.glGenLists(1);
        gl.glNewList(gear1, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, red, 0);
        gear(gl, 1.0f, 4.0f, 1.0f, 20, 0.7f);
        gl.glEndList();

        gear2 = gl.glGenLists(1);
        gl.glNewList(gear2, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, green, 0);
        gear(gl, 0.5f, 2.0f, 2.0f, 10, 0.7f);
        gl.glEndList();

        gear3 = gl.glGenLists(1);
        gl.glNewList(gear3, GL2.GL_COMPILE);
        gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, blue, 0);
        gear(gl, 1.3f, 2.0f, 0.5f, 10, 0.7f);
        gl.glEndList();

        gl.glEnable(GL2.GL_NORMALIZE);

        // SwoglDemo: The listeners are not added to the canvas,
        // but to the main component:
        /*
        if (drawable instanceof AWTGLAutoDrawable)
        {
            AWTGLAutoDrawable awtDrawable = (AWTGLAutoDrawable)drawable;
            awtDrawable.addMouseListener(this);
            awtDrawable.addMouseMotionListener(this);
        }
        */
        swoglContainer.getMainComponent().addMouseListener(this);
        swoglContainer.getMainComponent().addMouseMotionListener(this);
        
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
    {
        GL2 gl = drawable.getGL().getGL2();

        float h = (float)height / (float)width;

        gl.glMatrixMode(GL2.GL_PROJECTION);

        System.err.println("GL_VENDOR: " + gl.glGetString(GL2.GL_VENDOR));
        System.err.println("GL_RENDERER: " + gl.glGetString(GL2.GL_RENDERER));
        System.err.println("GL_VERSION: " + gl.glGetString(GL2.GL_VERSION));
        gl.glLoadIdentity();
        gl.glFrustum(-1.0f, 1.0f, -h, h, 5.0f, 60.0f);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glTranslatef(0.0f, 0.0f, -40.0f);
    }

    public void dispose(GLAutoDrawable drawable)
    {
        System.out.println("Gears.dispose: " + drawable);
    }

    public void display(GLAutoDrawable drawable)
    {
        // Turn the gears' teeth
        if (angleDelta > 0)
        {
            angle += angleDelta;
            angleTextField.setText(String.valueOf(angle));
        }


        // Get the GL corresponding to the drawable we are animating
        GL2 gl = drawable.getGL().getGL2();

        // Special handling for the case where the GLJPanel is translucent
        // and wants to be composited with other Java 2D content
        if ((drawable instanceof GLJPanel) && !((GLJPanel)drawable).isOpaque() && ((GLJPanel)drawable).shouldPreserveColorBufferIfTranslucent())
        {
            gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);
        }
        else
        {
            gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
        }

        // Rotate the entire assembly of gears based on how the user
        // dragged the mouse around
        gl.glPushMatrix();
        gl.glRotatef(view_rotx, 1.0f, 0.0f, 0.0f);
        gl.glRotatef(view_roty, 0.0f, 1.0f, 0.0f);
        gl.glRotatef(view_rotz, 0.0f, 0.0f, 1.0f);

        // SwoglDemo: Update the picking ray computer with the current
        // modelview matrix, and and render all SwoglComponents
        swoglSetup.getPickingRayComputer().update();
        swoglSetup.getSwoglComponentHandler().renderAllSwoglComponents();
        
        // Place the first gear and call its display list
        gl.glPushMatrix();
        gl.glTranslatef(-3.0f, -2.0f, 0.0f);
        gl.glRotatef(angle, 0.0f, 0.0f, 1.0f);
        gl.glCallList(gear1);
        gl.glPopMatrix();

        // Place the second gear and call its display list
        gl.glPushMatrix();
        gl.glTranslatef(3.1f, -2.0f, 0.0f);
        gl.glRotatef(-2.0f * angle - 9.0f, 0.0f, 0.0f, 1.0f);
        gl.glCallList(gear2);
        gl.glPopMatrix();

        // Place the third gear and call its display list
        gl.glPushMatrix();
        gl.glTranslatef(-3.1f, 4.2f, 0.0f);
        gl.glRotatef(-2.0f * angle - 25.0f, 0.0f, 0.0f, 1.0f);
        gl.glCallList(gear3);
        gl.glPopMatrix();

        // Remember that every push needs a pop; this one is paired with
        // rotating the entire gear assembly
        gl.glPopMatrix();
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged)
    {}

    public static void gear(GL2 gl, float inner_radius, float outer_radius, float width, int teeth, float tooth_depth)
    {
        int i;
        float r0, r1, r2;
        float angle, da;
        float u, v, len;

        r0 = inner_radius;
        r1 = outer_radius - tooth_depth / 2.0f;
        r2 = outer_radius + tooth_depth / 2.0f;

        da = 2.0f * (float)Math.PI / teeth / 4.0f;

        gl.glShadeModel(GL2.GL_FLAT);

        gl.glNormal3f(0.0f, 0.0f, 1.0f);

        /* draw front face */
        gl.glBegin(GL2.GL_QUAD_STRIP);
        for (i = 0; i <= teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), width * 0.5f);
            if (i < teeth)
            {
                gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), width * 0.5f);
                gl.glVertex3f(r1 * (float)Math.cos(angle + 3.0f * da), r1 * (float)Math.sin(angle + 3.0f * da), width * 0.5f);
            }
        }
        gl.glEnd();

        /* draw front sides of teeth */
        gl.glBegin(GL2.GL_QUADS);
        for (i = 0; i < teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + da), r2 * (float)Math.sin(angle + da), width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + 2.0f * da), r2 * (float)Math.sin(angle + 2.0f * da), width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle + 3.0f * da), r1 * (float)Math.sin(angle + 3.0f * da), width * 0.5f);
        }
        gl.glEnd();

        /* draw back face */
        gl.glBegin(GL2.GL_QUAD_STRIP);
        for (i = 0; i <= teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), -width * 0.5f);
            gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), -width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle + 3 * da), r1 * (float)Math.sin(angle + 3 * da), -width * 0.5f);
            gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), -width * 0.5f);
        }
        gl.glEnd();

        /* draw back sides of teeth */
        gl.glBegin(GL2.GL_QUADS);
        for (i = 0; i < teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glVertex3f(r1 * (float)Math.cos(angle + 3 * da), r1 * (float)Math.sin(angle + 3 * da), -width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + 2 * da), r2 * (float)Math.sin(angle + 2 * da), -width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + da), r2 * (float)Math.sin(angle + da), -width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), -width * 0.5f);
        }
        gl.glEnd();

        /* draw outward faces of teeth */
        gl.glBegin(GL2.GL_QUAD_STRIP);
        for (i = 0; i < teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle), r1 * (float)Math.sin(angle), -width * 0.5f);
            u = r2 * (float)Math.cos(angle + da) - r1 * (float)Math.cos(angle);
            v = r2 * (float)Math.sin(angle + da) - r1 * (float)Math.sin(angle);
            len = (float)Math.sqrt(u * u + v * v);
            u /= len;
            v /= len;
            gl.glNormal3f(v, -u, 0.0f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + da), r2 * (float)Math.sin(angle + da), width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + da), r2 * (float)Math.sin(angle + da), -width * 0.5f);
            gl.glNormal3f((float)Math.cos(angle), (float)Math.sin(angle), 0.0f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + 2 * da), r2 * (float)Math.sin(angle + 2 * da), width * 0.5f);
            gl.glVertex3f(r2 * (float)Math.cos(angle + 2 * da), r2 * (float)Math.sin(angle + 2 * da), -width * 0.5f);
            u = r1 * (float)Math.cos(angle + 3 * da) - r2 * (float)Math.cos(angle + 2 * da);
            v = r1 * (float)Math.sin(angle + 3 * da) - r2 * (float)Math.sin(angle + 2 * da);
            gl.glNormal3f(v, -u, 0.0f);
            gl.glVertex3f(r1 * (float)Math.cos(angle + 3 * da), r1 * (float)Math.sin(angle + 3 * da), width * 0.5f);
            gl.glVertex3f(r1 * (float)Math.cos(angle + 3 * da), r1 * (float)Math.sin(angle + 3 * da), -width * 0.5f);
            gl.glNormal3f((float)Math.cos(angle), (float)Math.sin(angle), 0.0f);
        }
        gl.glVertex3f(r1 * (float)Math.cos(0), r1 * (float)Math.sin(0), width * 0.5f);
        gl.glVertex3f(r1 * (float)Math.cos(0), r1 * (float)Math.sin(0), -width * 0.5f);
        gl.glEnd();

        gl.glShadeModel(GL2.GL_SMOOTH);

        /* draw inside radius cylinder */
        gl.glBegin(GL2.GL_QUAD_STRIP);
        for (i = 0; i <= teeth; i++)
        {
            angle = i * 2.0f * (float)Math.PI / teeth;
            gl.glNormal3f(-(float)Math.cos(angle), -(float)Math.sin(angle), 0.0f);
            gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), -width * 0.5f);
            gl.glVertex3f(r0 * (float)Math.cos(angle), r0 * (float)Math.sin(angle), width * 0.5f);
        }
        gl.glEnd();
    }

    // Methods required for the implementation of MouseListener
    public void mouseEntered(MouseEvent e)
    {}

    public void mouseExited(MouseEvent e)
    {}

    public void mousePressed(MouseEvent e)
    {
        prevMouseX = e.getX();
        prevMouseY = e.getY();
        if ((e.getModifiers() & e.BUTTON3_MASK) != 0)
        {
            mouseRButtonDown = true;
        }
    }

    public void mouseReleased(MouseEvent e)
    {
        if ((e.getModifiers() & e.BUTTON3_MASK) != 0)
        {
            mouseRButtonDown = false;
        }
    }

    public void mouseClicked(MouseEvent e)
    {}

    // Methods required for the implementation of MouseMotionListener
    public void mouseDragged(MouseEvent e)
    {
        int x = e.getX();
        int y = e.getY();
        Dimension size = e.getComponent().getSize();

        float thetaY = 360.0f * ((float)(x - prevMouseX) / (float)size.width);
        float thetaX = 360.0f * ((float)(prevMouseY - y) / (float)size.height);

        prevMouseX = x;
        prevMouseY = y;

        view_rotx += thetaX;
        view_roty += thetaY;
    }

    public void mouseMoved(MouseEvent e)
    {}
}
