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

//todo: check javadoc accuracy,

/**
 * Instances of <code>Frustum</code> are immutable. </p>
 *
 * @author Tom Gaskins
 * @version $Id: Frustum.java 1774 2007-05-08 01:03:37Z dcollins $
 */
public class Frustum
{
    private final Plane left;
    private final Plane right;
    private final Plane bottom;
    private final Plane top;
    private final Plane near;
    private final Plane far;

    /**
     * Create a default frustum with six <code>Plane</code>s. This defines a box of dimension (2, 2, 2) centered at the
     * origin.
     */
    public Frustum()
    {
        this.near = new Plane(0d, 0d, 1d, 1d);
        this.far = new Plane(0d, 0d, 0d - 1d, 1d);
        this.left = new Plane(1d, 0d, 0d, 1d);
        this.right = new Plane(0d - 1d, 0d, 0d, 1d);
        this.bottom = new Plane(0d, 1d, 0d, 1d);
        this.top = new Plane(0d, 0d - 1d, 0d, 1d);
    }

    /**
     * Create a frustum from six <code>Plane</code>s, which define its boundaries. Does not except null arguments.
     *
     * @param near   the near plane
     * @param far    the far plane
     * @param left   the left side of the view frustum
     * @param right  the right side of the view frustm
     * @param top    the top of the view frustum
     * @param bottom the bottom of the view frustum
     * @throws IllegalArgumentException if any argument is null
     */
    public Frustum(Plane near, Plane far, Plane left, Plane right, Plane bottom, Plane top)
    {
        if (near == null || far == null || left == null || right == null || bottom == null || top == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PlaneIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }
        this.near = near;
        this.far = far;
        this.left = left;
        this.right = right;
        this.bottom = bottom;
        this.top = top;
    }

    /**
     * Obtain the near <code>Plane</code>.
     *
     * @return the near <code>Plane</code>
     */
    public final Plane getNear()
    {
        return this.near;
    }

    /**
     * Obtain the far <code>Plane</code>.
     *
     * @return the far <code>Plane</code>
     */
    public final Plane getFar()
    {
        return this.far;
    }

    /**
     * Obtain the left <code>Plane</code>.
     *
     * @return the left <code>Plane</code>
     */
    public final Plane getLeft()
    {
        return this.left;
    }

    /**
     * Obtain the right <code>Plane</code>.
     *
     * @return the right <code>Plane</code>
     */
    public final Plane getRight()
    {
        return this.right;
    }

    /**
     * Obtain the bottom <code>Plane</code>.
     *
     * @return the bottom <code>Plane</code>
     */
    public final Plane getBottom()
    {
        return this.bottom;
    }

    /**
     * Obtain the top <code>Plane</code>.
     *
     * @return the top <code>Plane</code>
     */
    public final Plane getTop()
    {
        return this.top;
    }

    /**
     * @param m
     * @return
     * @throws IllegalArgumentException if <code>m</code> is null
     */
    public final Frustum getInverseTransformed(Matrix m)
    {
        if (m == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.MatrixIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        // Assumes orthogonal matrices with translation.
        Matrix it = m.getTranspose();

        Plane n = new Plane(it.transform(this.near.getVector()));
        Plane f = new Plane(it.transform(this.far.getVector()));
        Plane l = new Plane(it.transform(this.left.getVector()));
        Plane r = new Plane(it.transform(this.right.getVector()));
        Plane b = new Plane(it.transform(this.bottom.getVector()));
        Plane t = new Plane(it.transform(this.top.getVector()));

        return new Frustum(n, f, l, r, b, t);
    }

    /**
     * @param extent
     * @return
     * @throws IllegalArgumentException if <code>extent</code> is null
     */
    public final boolean intersects(Extent extent)
    {
        if (extent == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.ExtentIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        // See if the extent's bounding sphere is within or intersects the frustum.
        Point c = extent.getCenter();
        double nr = -extent.getRadius();

        if (this.far.dot(c) <= nr)
            return false;
        if (this.left.dot(c) <= nr)
            return false;
        if (this.right.dot(c) <= nr)
            return false;
        if (this.top.dot(c) <= nr)
            return false;
        if (this.bottom.dot(c) <= nr)
            return false;
        //noinspection RedundantIfStatement
        if (this.near.dot(c) <= nr)
            return false;

        return true;
    }

    /**
     * @param point
     * @return
     * @throws IllegalArgumentException if <code>point</code> is null
     */
    public final boolean contains(Point point)
    {
        if (point == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (this.far.dot(point) < 0)
            return false;
        if (this.left.dot(point) < 0)
            return false;
        if (this.right.dot(point) < 0)
            return false;
        if (this.top.dot(point) < 0)
            return false;
        if (this.bottom.dot(point) < 0)
            return false;
        //noinspection RedundantIfStatement
        if (this.near.dot(point) < 0)
            return false;

        return true;
    }

    @Override
    public String toString()
    {
        return "near: " + near.toString() + "... far: " + far.toString() + "... left: " + left.toString()
            + "... right: " + right.toString() + "... bottom: " + bottom.toString() + "... top: " + top.toString();
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final gov.nasa.worldwind.geom.Frustum frustum = (gov.nasa.worldwind.geom.Frustum) o;

        if (!bottom.equals(frustum.bottom))
            return false;
        if (!far.equals(frustum.far))
            return false;
        if (!left.equals(frustum.left))
            return false;
        if (!near.equals(frustum.near))
            return false;
        if (!right.equals(frustum.right))
            return false;
        //noinspection RedundantIfStatement
        if (!top.equals(frustum.top))
            return false;

        return true;
    }

    @Override
    public int hashCode()
    {
        int result;
        result = near.hashCode();
        result = 31 * result + far.hashCode();
        result = 29 * result + left.hashCode();
        result = 23 * result + right.hashCode();
        result = 19 * result + bottom.hashCode();
        result = 17 * result + top.hashCode();
        return result;
    }
}
