/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind;

import javax.media.opengl.*;

/**
 * @author Tom Gaskins
 * @version $Id: BasicFrameController.java 1561 2007-04-21 09:29:58Z tgaskins $
 */
public class BasicFrameController implements FrameController
{
    public void initializeFrame(DrawContext dc) // TODO:Recover matrix and attribute stacks in case of Exception
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        javax.media.opengl.GL gl = dc.getGL();

        gl.glPushAttrib(javax.media.opengl.GL.GL_VIEWPORT_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
            | javax.media.opengl.GL.GL_TRANSFORM_BIT);

        gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST);
    }

    public void finalizeFrame(DrawContext dc)
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        GL gl = dc.getGL();

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();

        gl.glPopAttrib();

        gl.glFlush();

//        checkGLErrors(dc);
    }

    /**
     * Called to check for openGL errors. This method includes a "round-trip" between the application and renderer,
     * which is slow. Therefore, this method is excluded from the "normal" render pass. It is here as a matter of
     * convenience to developers, and is not part of the API.
     *
     * @param dc the relevant <code>DrawContext</code>
     */
    @SuppressWarnings({"UNUSED_SYMBOL", "UnusedDeclaration"})
    private void checkGLErrors(DrawContext dc)
    {
        GL gl = dc.getGL();
        int err = gl.glGetError();
        if (err != GL.GL_NO_ERROR)
        {
            String msg = dc.getGLU().gluErrorString(err);
            msg += err;
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
        }
    }

    public void drawFrame(DrawContext dc)
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.clearFrame(dc);

        if (dc.getView() == null || dc.getModel() == null || dc.getLayers() == null)
            return;

        try
        {
            dc.getView().apply(dc);
        }
        catch (Exception e)
        {
            String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileApplyingView");
            WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
            return;
        }

        try
        {
            if (dc.getModel().getTessellator() != null)
            {
                SectorGeometryList sgl = dc.getModel().getTessellator().tessellate(dc);
                dc.setSurfaceGeometry(sgl);
            }

            if (dc.getSurfaceGeometry() == null)
            {
                String message = WorldWind.retrieveErrMsg("generic.NoSurfaceGeometry");
                WorldWind.logger().log(java.util.logging.Level.FINE, message);
                // keep going because some layers, etc. may have meaning w/o surface geometry
            }
        }
        catch (Exception e)
        {
            String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileTessellatingGlobe");
            WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
            return;
        }

        gov.nasa.worldwind.LayerList layers = dc.getLayers();
        java.util.Iterator<Layer> iter = layers.iterator();
        while (iter.hasNext())
        {
            Layer layer = null;
            try
            {
                layer = iter.next();
                if (layer != null)
                    layer.render(dc);
            }
            catch (Exception e)
            {
                String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileRenderingLayer");
                message += (layer != null ? layer.getClass().getName() : WorldWind.retrieveErrMsg("term.unknown"));
                WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
                // Don't abort the frame; continue on to the next layer.
            }
        }

        while (dc.getOrderedRenderables().peek() != null)
        {
            dc.getOrderedRenderables().poll().render(dc);
        }

        // Diagnostic displays.
        if (dc.getSurfaceGeometry() != null
            && dc.getModel().isShowWireframeExterior()
            || dc.getModel().isShowWireframeInterior()
            || dc.getModel().isShowTessellationBoundingVolumes())
        {
            Model model = dc.getModel();

            float[] previousColor = new float[4];
            dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0);

            for (SectorGeometry sg : dc.getSurfaceGeometry())
            {
                if (model.isShowWireframeInterior() || model.isShowWireframeExterior())
                    sg.renderWireframe(dc, model.isShowWireframeInterior(), model.isShowWireframeExterior());

                if (model.isShowTessellationBoundingVolumes())
                {
                    dc.getGL().glColor3d(1, 0, 0);
                    sg.renderBoundingVolume(dc);
                }
            }
            
            dc.getGL().glColor4fv(previousColor, 0);
        }
    }

    public void initializePicking(DrawContext dc)
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        javax.media.opengl.GL gl = dc.getGL();

        gl.glPushAttrib(javax.media.opengl.GL.GL_VIEWPORT_BIT | javax.media.opengl.GL.GL_ENABLE_BIT
            | javax.media.opengl.GL.GL_TRANSFORM_BIT);

        gl.glMatrixMode(javax.media.opengl.GL.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glEnable(javax.media.opengl.GL.GL_DEPTH_TEST);
    }

    public void pick(DrawContext dc, java.awt.Point pickPoint)
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.clearFrame(dc);

        if (dc.getView() == null || dc.getModel() == null || dc.getLayers() == null)
            return;

        try
        {
            dc.getView().apply(dc);
        }
        catch (Exception e)
        {
            String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhileApplyingView");
            WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
            return;
        }

        // Pick against the surface geometry that was used to draw the last frame.
        if (dc.getSurfaceGeometry() != null && dc.getSurfaceGeometry().size() > 0)
        {
            dc.getSurfaceGeometry().pick(dc, pickPoint);
        }

        gov.nasa.worldwind.LayerList layers = dc.getLayers();
        java.util.Iterator<Layer> iter = layers.iterator();
        while (iter.hasNext())
        {
            Layer layer = null;
            try
            {
                layer = iter.next();
                if (layer != null && layer.isPickEnabled())
                    layer.pick(dc, pickPoint);
            }
            catch (Exception e)
            {
                String message = WorldWind.retrieveErrMsg("BasicFrameController.ExceptionWhilePickingInLayer");
                message += (layer != null ? layer.getClass().getName() : WorldWind.retrieveErrMsg("term.unknown"));
                WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
                // Don't abort the frame; continue on to the next layer.
            }
        }

        // Pick against the deferred/ordered renderables
        while (dc.getOrderedRenderables().peek() != null)
        {
            dc.getOrderedRenderables().poll().pick(dc, pickPoint);
        }

        // let's make a last reading to find out which is a top (resultant) color
        PickedObjectList pickedObjectsList = dc.getPickedObjects();
        if (null != pickedObjectsList && (0 < pickedObjectsList.size())) // zz: garakl: put 1 here, if only one object
        {
            int[] viewport = new int[4];
            java.nio.ByteBuffer pixel = com.sun.opengl.util.BufferUtil.newByteBuffer(3);
            GL gl = dc.getGL();
            gl.glGetIntegerv(javax.media.opengl.GL.GL_VIEWPORT, viewport, 0);
            gl.glReadPixels(pickPoint.x, viewport[3] - pickPoint.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, pixel);

            java.awt.Color topColor = new java.awt.Color(pixel.get(0) & 0xff, pixel.get(1) & 0xff,
                pixel.get(2) & 0xff, 0);

            if (null != topColor)
            {
                int colorCode = topColor.getRGB();
                if (0 != colorCode)
                {   // let's find the picked object in the list and set "OnTop" flag
                    for (PickedObject po : pickedObjectsList)
                    {
                        if (null != po && po.getColorCode() == colorCode)
                        {
                            po.setOnTop();
                            break;
                        }
                    }
                }
            }
        } // endf of top pixel reading
    }

    public void finalizePicking(DrawContext dc)
    {
        if (null == dc)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.DrawContextIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        GL gl = dc.getGL();

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();

        gl.glPopAttrib();
    }

    private void clearFrame(DrawContext dc)
    {
        java.awt.Color cc = dc.getClearColor();
        dc.getGL().glClearColor(cc.getRed(), cc.getGreen(), cc.getBlue(), cc.getAlpha());
        dc.getGL().glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    }
}
