/*
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 gov.nasa.worldwind.geom.*;

import javax.media.opengl.*;
import java.util.*;

import com.sun.opengl.util.texture.*;

/**
 * @author Tom Gaskins
 * @version $Id: DrawContextImpl.java 1465 2007-04-14 01:05:37Z tgaskins $
 */
public class DrawContextImpl extends WWObjectImpl implements DrawContext
{
    private javax.media.opengl.GLContext glContext;
    private javax.media.opengl.glu.GLU glu = new javax.media.opengl.glu.GLU();
    private View view;
    private Model model;
    private Globe globe;
    private double verticalExaggeration = 1d;
    private gov.nasa.worldwind.geom.Sector visibleSector;
    private SectorGeometryList surfaceGeometry;// = new SectorGeometryList();
    private gov.nasa.worldwind.PickedObjectList pickedObjects = new gov.nasa.worldwind.PickedObjectList();
    private int uniquePickNumber = 0;
    private java.awt.Color clearColor = new java.awt.Color(0, 0, 0, 0);
    private boolean isPickingMode = false;
    private int numTextureUnits = -1;
    private SurfaceTileRenderer surfaceTileRenderer = new SurfaceTileRenderer();

    PriorityQueue<OrderedRenderable> orderedRenderables = new PriorityQueue<OrderedRenderable>(100,
        new Comparator<OrderedRenderable>()
        {
            public int compare(OrderedRenderable orA, OrderedRenderable orB)
            {
                double eA = orA.getDistanceFromEye();
                double eB = orB.getDistanceFromEye();

                return eA > eB ? -1 : eA == eB ? 0 : 1;
            }
        });

    public final javax.media.opengl.GL getGL()
    {
        return this.getGLContext().getGL();
    }

    public final javax.media.opengl.glu.GLU getGLU()
    {
        return this.glu;
    }

    public final javax.media.opengl.GLContext getGLContext()
    {
        return this.glContext;
    }

    public final int getDrawableHeight()
    {
        return this.getGLDrawable().getHeight();
    }

    public final int getDrawableWidth()
    {
        return this.getGLDrawable().getWidth();
    }

    public final javax.media.opengl.GLDrawable getGLDrawable()
    {
        return this.getGLContext().getGLDrawable();
    }

    public final void initialize(javax.media.opengl.GLContext glContext)
    {
        if (glContext == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.GLContextIsNull");
            gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        this.glContext = glContext;

        if(this.isPickingMode())
        {
            // do not clean rendered sectors for picking
        }
        else
        {
            this.visibleSector = null;
            if (this.surfaceGeometry != null)
                this.surfaceGeometry.clear();
            this.surfaceGeometry = null;
        }
        this.pickedObjects.clear();
        this.orderedRenderables.clear();
        this.uniquePickNumber = 0;

        if (this.numTextureUnits < 1)
            this.numTextureUnits = queryMaxTextureUnits(glContext);
    }

    private static int queryMaxTextureUnits(GLContext glContext)
    {
        int[] mtu = new int[1];
        glContext.getGL().glGetIntegerv(GL.GL_MAX_TEXTURE_UNITS, mtu, 0);
        return mtu[0];
    }

    public final void setModel(gov.nasa.worldwind.Model model)
    {
        this.model = model;
        if (this.model == null)
            return;

        Globe g = this.model.getGlobe();
        if (g != null)
            this.globe = g;
    }

    public final Model getModel()
    {
        return this.model;
    }

    public final LayerList getLayers()
    {
        return this.model.getLayers();
    }

    public final Sector getVisibleSector()
    {
        return this.visibleSector;
    }

    public final void setVisibleSector(Sector s)
    {
        // don't check for null - it is possible that no globe is active, no view is active, no sectors visible, etc.
        this.visibleSector = s;
    }

    public void setSurfaceGeometry(SectorGeometryList surfaceGeometry)
    {
        this.surfaceGeometry = surfaceGeometry;
    }
    
    public SectorGeometryList getSurfaceGeometry()
    {
        return surfaceGeometry;
    }

    public final Globe getGlobe()
    {
        return this.globe != null ? this.globe : this.model.getGlobe();
    }

    public final void setView(gov.nasa.worldwind.View view)
    {
        this.view = view;
    }

    public final View getView()
    {
        return this.view;
    }

    public final void setGLContext(javax.media.opengl.GLContext glContext)
    {
        if (glContext == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.GLContextIsNull");
            gov.nasa.worldwind.WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        this.glContext = glContext;
    }

    public final double getVerticalExaggeration()
    {
        return verticalExaggeration;
    }

    public final void setVerticalExaggeration(double verticalExaggeration)
    {
        this.verticalExaggeration = verticalExaggeration;
    }

    /**
     * Add picked objects to the current list of picked objects.
     * @param pickedObjects the list of picked objects to add
     * @throws IllegalArgumentException if <code>pickedObjects is null</code>
     */
    public void addPickedObjects(gov.nasa.worldwind.PickedObjectList pickedObjects)
    {
        if (pickedObjects == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PickedObjectList");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        if (this.pickedObjects == null)
        {
            this.pickedObjects = pickedObjects;
            return;
        }

        for (gov.nasa.worldwind.PickedObject po : pickedObjects)
        {
            this.pickedObjects.add(po);
        }
    }

    /**
     * Adds a single insatnce of the picked object to the current picked-object list
     * @param pickedObject the object to add
     * @throws IllegalArgumentException if <code>picked Object is null</code>
     */
    public void addPickedObject(PickedObject pickedObject)
    {
        if(null == pickedObject)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PickedObject");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        if(null == this.pickedObjects)
            this.pickedObjects = new gov.nasa.worldwind.PickedObjectList();

        this.pickedObjects.add(pickedObject);
    }

    public gov.nasa.worldwind.PickedObjectList getPickedObjects()
    {
        return this.pickedObjects;
    }

    public java.awt.Color getUniquePickColor()
    {
        this.uniquePickNumber++;
        int clearColorCode = this.getClearColor().getRGB();

        if(clearColorCode == this.uniquePickNumber)
            this.uniquePickNumber++;

        if(this.uniquePickNumber >= 0x00FFFFFF)
        {
            this.uniquePickNumber = 1;  // no black, no white
            if(clearColorCode == this.uniquePickNumber)
                this.uniquePickNumber++;
        }
        
        return new java.awt.Color(this.uniquePickNumber, true); // has alpha
    }

    public java.awt.Color getClearColor()
    {
        return this.clearColor;
    }

    /**
     * Returns true if the Picking mode is active, otherwise return false
     * @return true for Picking mode, otherwise false
     */
    public boolean isPickingMode()
    {
        return this.isPickingMode;    
    }

    /**
     * Enables color picking mode
     */
    public void enablePickingMode()
    {
        this.isPickingMode = true;
    }

    /**
     * Disables color picking mode
     */
    public void disablePickingMode()
    {
        this.isPickingMode = false;
    }

    public void addOrderedRenderable(OrderedRenderable orderedRenderable)
    {
        if(null == orderedRenderable)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.OrderedRenderable");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            return; // benign event
        }

        this.orderedRenderables.add(orderedRenderable);
    }

    public java.util.Queue<OrderedRenderable> getOrderedRenderables()
    {
        return this.orderedRenderables;
    }

    public void drawUnitQuad()
    {
        GL gl = this.getGL();

        gl.glBegin(GL.GL_QUADS); // TODO: use a vertex array or vertex buffer
        gl.glVertex2d(0d, 0d);
        gl.glVertex2d(1, 0d);
        gl.glVertex2d(1, 1);
        gl.glVertex2d(0d, 1);
        gl.glEnd();
    }

    public void drawUnitQuad(TextureCoords texCoords)
    {
        GL gl = this.getGL();

        gl.glBegin(GL.GL_QUADS); // TODO: use a vertex array or vertex buffer
        gl.glTexCoord2d(texCoords.left(), texCoords.bottom());
        gl.glVertex2d(0d, 0d);
        gl.glTexCoord2d(texCoords.right(), texCoords.bottom());
        gl.glVertex2d(1, 0d);
        gl.glTexCoord2d(texCoords.right(), texCoords.top());
        gl.glVertex2d(1, 1);
        gl.glTexCoord2d(texCoords.left(), texCoords.top());
        gl.glVertex2d(0d, 1);
        gl.glEnd();
    }

    public int getNumTextureUnits()
    {
        return numTextureUnits;
    }

    public void setNumTextureUnits(int numTextureUnits)
    {
        // TODO: validate arg for >= 1
        this.numTextureUnits = numTextureUnits;
    }

    public Point getPointOnGlobe(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (!this.getVisibleSector().contains(latitude, longitude))
            return null;

        SectorGeometryList sectorGeometry = this.getSurfaceGeometry();
        if (sectorGeometry != null)
        {
            Point p = sectorGeometry.getSurfacePoint(latitude, longitude);
            if (p != null)
                return p;
        }

        return null;

//        Globe globe = this.getGlobe();
//        if (globe == null)
//            return null;
//
//        double elevation = this.getVerticalExaggeration() * globe.getElevation(latitude, longitude);
//        return this.getGlobe().computeSurfacePoint(latitude, longitude, elevation);
    }

    public SurfaceTileRenderer getSurfaceTileRenderer()
    {
        return this.surfaceTileRenderer;
    }
}
