// (c) MIT 2003.  All rights reserved.

////////////////////////////////////////////////////////////////////////////////
//
// FILE:	GOval.java
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
// DATE:	Jun 98
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Geometric modeling
//
// HISTORY:     Jun 98  archon  created
//
//              Nov 98  archon
//              Made mutable
//
//              Mar 99  archon
//              Bug fixes
//
//              Apr 03 cadlerun
//              implemented resize
//
////////////////////////////////////////////////////////////////////////////////

package rec.geo;

import java.awt.*;
import rec.util.*;

/**
 * GOval represents an oval.
 */
public class GOval extends GShape
{
  /**
   * Creates an oval with center center,
   *      x-axis length (2 * aLength), and
   *      y-axis length (2 * bLength) rotated angle radians.
   */
  public GOval( GPoint center, double radians,
		double aLength, double bLength )
  {
    Assert.check( aLength >= 0.0 && bLength >= 0.0,
		  "ovals cannot have negative axis lengths" );

    m_Center = new GPoint( center );
    m_Rotation = Radian.reduce( radians );
    m_ALength = aLength;
    m_BLength = bLength;
  }

  public GOval( GOval go )
  {
    m_Center = new GPoint( go.m_Center );
    m_Rotation = go.m_Rotation;
    m_ALength = go.m_ALength;
    m_BLength = go.m_BLength;
  }

  //////////////////////////////////////////////////////////////////////////////

  public GShape copy()
  {
    return( new GOval( this ) );
  }

  public void reflect()
  {
    m_Center.reflect();
    m_Rotation = Radian.reduce( Radian.reduce( Math.PI / 4.0 - m_Rotation )
                                + Math.PI / 4.0 );
  }

  public void translate( double dx, double dy )
  {
    m_Center.translate( dx, dy );
  }

  public void rotate( GPoint gp, double radians )
  {
    m_Center.rotate( gp, radians );
    m_Rotation = Radian.reduce( m_Rotation + radians );
  }

  //////////////////////////////////////////////////////////////////////////////

  public double size()
  {
    // Adds the lengths of the A and B axes.
    return( m_ALength + m_BLength );
  }

  public void resize( double scaleFactor )
  {
    // ada: implemented scaling
    m_ALength = m_ALength * scaleFactor;
    m_BLength = m_BLength * scaleFactor;
  }

  //////////////////////////////////////////////////////////////////////////////

  public Rectangle getBounds()
  {
    GPoint p = getPoint( 0.0 );

    int minX = p.getIntX() - 1;
    int minY = p.getIntY() - 1;

    int maxX = p.getIntX() + 1;
    int maxY = p.getIntY() + 1;

    double incr = 1.0 / (m_ALength + m_BLength);

    for( double d = incr; d < Math.PI * 2.0; d += incr ) {
      GPoint gp = getPoint( d );
      minX = Math.min( minX, gp.getIntX() - 1 );
      maxX = Math.max( maxX, gp.getIntX() + 1 );
      minY = Math.min( minY, gp.getIntY() - 1 );
      maxY = Math.max( maxY, gp.getIntY() + 1 );
    }
  
    /*
    double CosR = Geometry.cos( m_Rotation );
    double SinR = Geometry.sin( m_Rotation );

    double xT = Radian.reduce(
      Math.atan( -1.0 * m_BLength * SinR / m_ALength * CosR ) );
    double yT = Radian.reduce(
      Math.atan( m_BLength * CosR / m_ALength * SinR ) );

    int minX = Math.min(
      getPoint( xT ).getIntX(),
      getPoint( Radian.reduce( xT + Math.PI ) ).getIntX() ) - 1;
    int minY = Math.min(
      getPoint( yT ).getIntY(),
      getPoint( Radian.reduce( yT + Math.PI ) ).getIntY() ) - 1;

    int maxX = Math.max(
      getPoint( xT ).getIntX(),
      getPoint( Radian.reduce( xT + Math.PI ) ).getIntX() ) + 1;
    int maxY = Math.max(
      getPoint( yT ).getIntY(),
      getPoint( Radian.reduce( yT + Math.PI ) ).getIntY() ) + 1;
    */

    return( new Rectangle( minX, minY,
			   maxX - minX, maxY - minY ) );
  }

  //////////////////////////////////////////////////////////////////////////////

  public void display( Graphics g )
  {
    GPoint first = getPoint( 0.0 );
    GPoint prev = first;

    double incr = 1.0 / (m_ALength + m_BLength);

    for( double d = incr; d < 2.0 * Math.PI; d += incr ) {
      GPoint curr = getPoint( d );
      g.drawLine( prev.getIntX(), prev.getIntY(),
		  curr.getIntX(), curr.getIntY() );
      prev = curr;
    }

    g.drawLine( prev.getIntX(), prev.getIntY(),
		first.getIntX(), first.getIntY() );
  }

  //////////////////////////////////////////////////////////////////////////////

  public GPoint getPoint( double radians )
  {
    double x = m_ALength * Geometry.cos( radians );
    double y = m_BLength * Geometry.sin( radians );

    double CosR = Geometry.cos( m_Rotation );
    double SinR = Geometry.sin( m_Rotation );

    return( new GPoint( x * CosR - y * SinR + m_Center.x,
			x * SinR + y * CosR + m_Center.y ) );
  }

  /////////////////////////////////////////////////////////////////////////////

  public boolean contains( GPoint gp )
  {
    Assert.error( "Not implemented" );
    return false;
  }

  public boolean includes( GPoint gp )
  {
    double xD = gp.x - m_Center.x;
    double yD = gp.y - m_Center.y;
    double cosT = Geometry.cos( m_Rotation );
    double sinT = Geometry.sin( m_Rotation );

    double x1 = Math.pow( xD * cosT + yD * sinT, 2 );
    double y1 = Math.pow( yD * cosT - xD * sinT, 2 );

    return( x1 / Math.pow( m_ALength, 2 )
            + y1 / Math.pow( m_BLength, 2 ) == 1 );
  }

  public boolean includes( GPoint gp, double offset )
  {
    Assert.check( offset >= 0.0, "offset cannot be negative" );

    double xD = gp.x - m_Center.x;
    double yD = gp.y - m_Center.y;
    double cosT = Geometry.cos( m_Rotation );
    double sinT = Geometry.sin( m_Rotation );

    double x1 = Math.pow( xD * cosT + yD * sinT, 2 );
    double y1 = Math.pow( yD * cosT - xD * sinT, 2 );

    if( x1 / Math.pow( m_ALength + offset, 2 )
        + y1 / Math.pow( m_BLength + offset, 2 ) > 1.0 )
      return( false );

    return( x1 / Math.pow( m_ALength - offset, 2 )
            + y1 / Math.pow( m_BLength - offset, 2 ) >= 1.0 );
  }

  //////////////////////////////////////////////////////////////////////////////

  public GPoint getCenter()
  {
    return( m_Center );
  }

  public double getRotation()
  {
    return( m_Rotation );
  }

  public double getALength()
  {
    return( m_ALength );
  }

  public double getBLength()
  {
    return( m_BLength );
  }

  public GSegment getMajorAxis()
  {
    if( m_ALength >= m_BLength )
      return( new GSegment(
	getPoint( Radian.reduce( Math.PI ) ),
	getPoint( 0.0 ) ) );

    return( new GSegment(
      getPoint( Radian.reduce( 3.0 / 2.0 * Math.PI ) ),
      getPoint( Radian.reduce( Math.PI / 2.0 ) ) ) );
  }

  public GSegment getMinorAxis()
  {
    if( m_ALength < m_BLength )
      return( new GSegment(
	getPoint( Radian.reduce( Math.PI ) ),
	getPoint( 0.0 ) ) );

    return( new GSegment(
      getPoint( Radian.reduce( 3.0 / 2.0 * Math.PI ) ),
      getPoint( Radian.reduce( Math.PI / 2.0 ) ) ) );
  }

  public int hashCode()
  {
    return( m_Center.hashCode() + (int)m_ALength );
  }

  //////////////////////////////////////////////////////////////////////////////

  protected GPoint m_Center;
  private double m_Rotation;
  private double m_ALength;
  private double m_BLength;
}

////////////////////////////////////////////////////////////////////////////////
