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

import com.sun.opengl.util.*;
import gov.nasa.worldwind.*;

import javax.media.opengl.*;
import java.awt.*;
import java.nio.*;
import java.util.*;
import java.util.List;

/**
 * @author tag
 * @version $Id: Polyline.java 1787 2007-05-08 17:11:30Z dcollins $
 */
public class Polyline implements Renderable
{
    private ArrayList<Position> positions;
    private Point referenceCenter;
    private DoubleBuffer vertices;
    private int antiAliasHint = GL.GL_FASTEST;
    private Color color = Color.WHITE;
    private boolean filled = false; // makes it a polygon
    private boolean followGreatCircles = true;
    private int numEdgeIntervals = 10;

    public Polyline(Iterable<Position> positions)
    {
        if (positions == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.setPositions(positions);
    }

    public Polyline(Iterable<LatLon> positions, double elevation)
    {
        if (positions == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.setPositions(positions, elevation);
    }

    public Color getColor()
    {
        return color;
    }

    public void setColor(Color color)
    {
        if (color == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.ColorIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.color = color;
    }

    public int getAntiAliasHint()
    {
        return antiAliasHint;
    }

    public void setAntiAliasHint(int hint)
    {
        if (!(hint == GL.GL_DONT_CARE || hint == GL.GL_FASTEST || hint == GL.GL_NICEST))
        {
            String msg = WorldWind.retrieveErrMsg("generic.InvalidHint");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.antiAliasHint = hint;
    }

    public boolean isFilled()
    {
        return filled;
    }

    public void setFilled(boolean filled)
    {
        this.filled = filled;
    }

    public boolean isFollowGreatCircles()
    {
        return followGreatCircles;
    }

    public void setFollowGreatCircles(boolean followGreatCircles)
    {
        this.followGreatCircles = followGreatCircles;
    }

    public int getNumEdgeIntervals()
    {
        return numEdgeIntervals;
    }

    public void setNumEdgeIntervals(int numEdgeIntervals)
    {
        this.numEdgeIntervals = numEdgeIntervals;
    }

    public void setPositions(Iterable<Position> inPositions)
    {
        if (inPositions == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.positions = new ArrayList<Position>();
        for (Position position : inPositions)
        {
            this.positions.add(position);
        }

        if ((this.filled && this.positions.size() < 3) || (!this.filled && this.positions.size() < 2))
        {
            String msg = WorldWind.retrieveErrMsg("generic.InsufficientPositions");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.vertices = null;
    }

    public void setPositions(Iterable<LatLon> inPositions, double elevation)
    {
        if (inPositions == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PositionsListIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.positions = new ArrayList<Position>();
        for (LatLon position : inPositions)
        {
            this.positions.add(new Position(position.getLatitude(), position.getLongitude(), elevation));
        }

        if (this.positions.size() < 2 || (this.filled && this.positions.size() < 3))
        {
            String msg = WorldWind.retrieveErrMsg("generic.InsufficientPositions");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }

        this.vertices = null;
    }

    public Iterable<Position> getPositions()
    {
        return this.positions;
    }

    private void intializeGeometry(DrawContext dc)
    {
        if (this.positions.size() < 2)
            return;

        double[] refCenter = new double[3];
        if (this.followGreatCircles)
            this.vertices = this.makeGreatCircleVertices(dc, this.positions, refCenter);
        else
            this.vertices = makeVertices(dc, this.positions, refCenter);
        this.referenceCenter = new Point(refCenter[0], refCenter[1], refCenter[2]);

        double avgX = this.referenceCenter.getX();
        double avgY = this.referenceCenter.getY();
        double avgZ = this.referenceCenter.getZ();

        this.vertices.rewind();
        for (int i = 0; i < this.vertices.limit(); i += 3)
        {
            this.vertices.put(i, this.vertices.get(i) - avgX);
            this.vertices.put(i + 1, this.vertices.get(i + 1) - avgY);
            this.vertices.put(i + 2, this.vertices.get(i + 2) - avgZ);
        }
    }

    protected DoubleBuffer makeVertices(DrawContext dc, List<Position> posList, double[] refCenter)
    {
        DoubleBuffer verts = BufferUtil.newDoubleBuffer(posList.size() * 3);

        double avgX = 0;
        double avgY = 0;
        double avgZ = 0;

        int n = 0;
        for (Position p : posList)
        {
            Point pt = dc.getGlobe().computePointFromPosition(p.getLatitude(), p.getLongitude(), p.getElevation());
            verts.put(pt.x()).put(pt.y()).put(pt.z());
            avgX += pt.x();
            avgY += pt.y();
            avgZ += pt.z();
            ++n;
        }

        refCenter[0] = avgX / (double) n;
        refCenter[1] = avgY / (double) n;
        refCenter[2] = avgZ / (double) n;

        return verts;
    }

    protected DoubleBuffer makeGreatCircleVertices(DrawContext dc, List<Position> posList, double[] refCenter)
    {
        if (posList.size() < 1)
            return null;

        int size = posList.size() + (this.numEdgeIntervals - 1) * (posList.size() - 1);
        DoubleBuffer verts = BufferUtil.newDoubleBuffer(size * 3);

        double avgX = 0;
        double avgY = 0;
        double avgZ = 0;
        int n = 0;

        Iterator<Position> iter = posList.iterator();
        Position pos = iter.next();
        Point pt = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), pos.getElevation());
        verts.put(pt.x());
        verts.put(pt.y());
        verts.put(pt.z());
        avgX += pt.x();
        avgY += pt.y();
        avgZ += pt.z();
        ++n;

        double delta = 1d / this.numEdgeIntervals;
        while (iter.hasNext())
        {
            Position posNext = iter.next();
            for (int i = 1; i < numEdgeIntervals; i++)
            {
                LatLon ll = LatLon.interpolate(i * delta, new LatLon(pos.getLatitude(), pos.getLongitude()),
                    new LatLon(posNext.getLatitude(), posNext.getLongitude()));
                double elevation = (i * delta) * pos.getElevation() + (1d - i * delta) * posNext.getElevation();
                pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(), elevation);
                verts.put(pt.x()).put(pt.y()).put(pt.z());
                avgX += pt.x();
                avgY += pt.y();
                avgZ += pt.z();
                ++n;
            }

            pt = dc.getGlobe().computePointFromPosition(posNext.getLatitude(), posNext.getLongitude(),
                pos.getElevation());
            verts.put(pt.x()).put(pt.y()).put(pt.z());
            avgX += pt.x();
            avgY += pt.y();
            avgZ += pt.z();
            ++n;

            pos = posNext;
        }

        refCenter[0] = avgX / (double) n;
        refCenter[1] = avgY / (double) n;
        refCenter[2] = avgZ / (double) n;

        return verts;
    }

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

        if (this.vertices == null)
        {
            this.intializeGeometry(dc);

            if (this.vertices == null)
                return; // TODO: log a warning
        }

        GL gl = dc.getGL();

        int attrBits = GL.GL_HINT_BIT | GL.GL_CURRENT_BIT;
        if (!dc.isPickingMode())
        {
            attrBits += GL.GL_CURRENT_BIT;
            if (this.color.getAlpha() != 255)
                attrBits += GL.GL_COLOR_BUFFER_BIT;
        }

        gl.glPushAttrib(attrBits);
        gl.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT);
        dc.getView().pushReferenceCenter(dc, this.referenceCenter);

        try
        {
            if (!dc.isPickingMode())
            {
                if (this.color.getAlpha() != 255)
                {
                    gl.glEnable(GL.GL_BLEND);
                    gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
                }
                dc.getGL().glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(),
                    (byte) this.color.getBlue(), (byte) this.color.getAlpha());
            }

            int hintAttr = GL.GL_LINE_SMOOTH_HINT;
            if (this.filled)
                hintAttr = GL.GL_POLYGON_SMOOTH_HINT;
            gl.glHint(hintAttr, this.antiAliasHint);

            gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
            gl.glVertexPointer(3, GL.GL_DOUBLE, 0, this.vertices.rewind());

            int primType = GL.GL_LINE_STRIP;
            if (this.filled)
                primType = GL.GL_POLYGON;
            gl.glDrawArrays(primType, 0, this.vertices.capacity() / 3);
        }
        finally
        {
            gl.glPopClientAttrib();
            gl.glPopAttrib();
            dc.getView().popReferenceCenter(dc);
        }
    }
}
