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

////////////////////////////////////////////////////////////////////////////////
//
// FILE:	GSegment.java
//
// AUTHOR:	Manoj D. Muzumdar
//		archon@ai.mit.edu
//
// DATE:	06-XX-98
//
// COPYRIGHT:	Massachusetts Institute of Technology
//		All rights reserved.
//
// PURPOSE:	Geometric modeling
//
// HISTORY:     11-15-98 archon
//              Made mutable
//
////////////////////////////////////////////////////////////////////////////////

package rec.geo;

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

/**
 * A GSegment is a mutable class that represents a line segment.
 */
public class GSegment extends GShape
{
  /**
   * @param head  The head of the segment.
   * @param tail  The tail of the segment.
   */
  public GSegment( GPoint head, GPoint tail )
  {
    Assert.check( !head.equals( tail ),
      "two distinct points needed to determine a segment" );

    m_Point1 = new GPoint( head );
    m_Point2 = new GPoint( tail );
  }

  public GSegment( double hx, double hy, double tx, double ty ) 
  {
    this( new GPoint( hx, hy ), new GPoint( tx, ty ) );
  }

  public GSegment( GPoint gp, double intercept )
  {
    this( gp, new GPoint( 0.0, intercept ) );
  }

  public GSegment( GSegment gs )
  {
    this( gs.m_Point1, gs.m_Point2 );
  }

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

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

  public void reflect()
  {
    m_Point1.reflect();
    m_Point2.reflect();
  }

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

  public void rotate( GPoint gp, double angle )
  {
    m_Point1.rotate( gp, angle );
    m_Point2.rotate( gp, angle );
  }

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

  public double size()
  {
    // A segment's size is its length
    return( getLength() );
  }

  public void resize( double scaleFactor )
  {
    // Scale around the segment's midpoint.
    GPoint mp = getMidpoint();

    double x1 = scaleFactor * (m_Point1.x - mp.x) + mp.x;
    double y1 = scaleFactor * (m_Point1.y - mp.y) + mp.y;

    double x2 = scaleFactor * (m_Point2.x - mp.x) + mp.x;
    double y2 = scaleFactor * (m_Point2.y - mp.y) + mp.y;

    m_Point1 = new GPoint( x1, y1 );
    m_Point2 = new GPoint( x2, y2 );
  }

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

  public Rectangle getBounds()
  {
    int x1 = m_Point1.getIntX();
    int y1 = m_Point1.getIntY();
    int x2 = m_Point2.getIntX();
    int y2 = m_Point2.getIntY();

    if( x1 < x2 && y1 < y2 )
      return( new Rectangle( x1 - 1, y1 - 1,
			     x2 - x1 + 2, y2 - y1 + 2 ) );

    if( x1 < x2 && y1 >= y2 )
      return( new Rectangle( x1 - 1, y2 -1,
			     x2 - x1 + 2, y1 - y2 + 2 ) );

    if( x1 >= x2 && y1 < y2 )
      return( new Rectangle( x2 - 1, y1 - 1,
			     x1 - x2 + 2, y2 - y1 + 2 ) );

    // x1 >= x2 && y1 >= y2
    return( new Rectangle( x2 - 1, y2 - 1,
			   x1 - x2 + 2, y1 - y2 + 2 ) );
  }

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

  public void display( Graphics g )
  {
    g.drawLine( m_Point1.getIntX(), m_Point1.getIntY(),
		m_Point2.getIntX(), m_Point2.getIntY() );
  }

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

  public boolean contains( GPoint gp ) 
  {
    return includes( gp );
  }

  /**
   * This might be dangerous due to floating point rounding.
   */
  public boolean includes( GPoint gp )
  {
    if( m_Point1.equals( gp ) || m_Point2.equals( gp ) )
      return( true );

    //    return( (new GSegment( m_Point1, gp )).getSlope() == getSlope()
    //	&& getBounds().contains( gp.getPoint() ) );

    return( includes( gp, 2.0 ) );
  }

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

    if( Geometry.distance( gp, m_Point1 ) <= offset )
      return( true );

    if( Geometry.distance( gp, m_Point2 ) <= offset )
      return( true );

    // The point is not around the endpoints.

    GPoint p = perpendicular( gp );

    /*    Console.writeln( getBounds().x + ", " + getBounds().y + ", "
		     + getBounds().width + ", " + getBounds().height );
          Console.writeln( p.x + ", " + p.y );
    */

    if( !getBounds().contains( p.getPoint() ) ) {
      //      Console.writeln( "perpendicular not on segment" );
      return( false );
    }

    /*    Console.writeln( "perpendicular intersection: ("
		     + p.getIntX() + ", " + p.getIntY() + ")" );
		     */

    if( Geometry.distance( gp, p ) <= offset ) {
      //      Console.writeln( "connection at: (" + p.getIntX() + ", "
      //                       + p.getIntY() + ")" );
      return( true );
    }

    return( false );
  }

  // gp is assumed to be on the line which is defined by the segment.  
  // Return true if it is also between the endpoints of the segment 
  public boolean onSegment( GPoint gp )
  {
        // now make sure that the intersection point is on both of the segments
    double bigX;
    double smallX;
    double bigY;
    double smallY;

    if ( gp == null ) {
      return false;
    }

    if ( getHeadPoint().x < getTailPoint().x ) {
      bigX = getTailPoint().x;
      smallX = getHeadPoint().x;
    } 
    else {
      bigX = getHeadPoint().x;
      smallX = getTailPoint().x;
    }

    if ( getHeadPoint().y < getTailPoint().y ) {
      bigY = getTailPoint().y;
      smallY = getHeadPoint().y;
    } 
    else {
      bigY = getHeadPoint().y;
      smallY = getTailPoint().y;
    }
    
    if ( (gp.x > bigX) ||
	 (gp.x < smallX) ||
	 (gp.y > bigY) ||
	 (gp.y < smallY) ) {
      return false;
    }
    return true;
  }

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

  /**
   * @return  A segment that has this segment's head and tail
   *     reversed.
   */
  public void flip()
  {
    GPoint tmp = m_Point1;

    m_Point1 = m_Point2;
    m_Point2 = tmp;
  }

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

  public double getLength()
  {
    return( Geometry.distance( m_Point1, m_Point2 ) );
  }

  // I am not sure if +y is up or down, so this may be backwards
  public GPoint getTopPoint()
  {
    if (m_Point1.y > m_Point2.y) {
      return m_Point1;
    }
    else {
      return m_Point2;
    }
  }

  public GPoint getBottomPoint()
  {
    if (m_Point1.y > m_Point2.y) {
      return m_Point2;
    }
    else {
      return m_Point1;
    }
  }

  public GPoint getRightPoint()
  {
    if (m_Point1.x > m_Point2.x) {
      return m_Point1;
    }
    else {
      return m_Point2;
    }
  }

  public GPoint getLeftPoint()
  {
    if (m_Point1.x > m_Point2.x) {
      return m_Point2;
    }
    else {
      return m_Point1;
    }
  }


  public GPoint getHeadPoint()
  {
    return( m_Point1 );
  }

  public GPoint getTailPoint()
  {
    return( m_Point2 );
  }

  public GPoint getMidpoint()
  {
    return( new GPoint( (m_Point1.x + m_Point2.x) / 2.0,
			(m_Point1.y + m_Point2.y) / 2.0 ) );
  }

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

  public double getAngle()
  {
    return Math.atan2( m_Point2.y - m_Point1.y,
		       m_Point2.x - m_Point1.x );
		       
  }

  public double getSlope()
  {
    if( m_Point1.x == m_Point2.x )
      return( Double.POSITIVE_INFINITY );
    else
      return( (m_Point2.y - m_Point1.y)
	/ (m_Point2.x - m_Point1.x) );
  }

  public boolean isSlopeZero()
  {
    return( getSlope() == 0.0 );
  }

  public boolean isSlopeInfinite()
  {
    return( Double.isInfinite( getSlope() ) );
  }

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

  public double getIntercept()
  {
    double slope = getSlope();

    if( Double.isInfinite( slope ) )
      return( Double.POSITIVE_INFINITY );
    else
      return( m_Point1.y - slope * m_Point1.x );
  }

  public boolean isInterceptValid()
  {
    return( !Double.isInfinite( getIntercept() ) );
  }

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

  public GPoint perpendicular( GPoint p )
  {
    double slope = getSlope();

    if( Double.isInfinite( slope ) )
      return( new GPoint( m_Point1.x, p.y ) );

    if( slope == 0.0 )
      return( new GPoint( p.x, m_Point1.y ) );

    double m = -1.0 / slope;
    double d = m - slope;

    double intercept = getIntercept();

    return( new GPoint( (m * p.x - p.y + intercept) / d,
			(m * intercept + slope
			 * (m * p.x - p.y)) / d ) );
  }

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

  public boolean isParallel( GSegment gs )
  {
    return( gs.getSlope() == getSlope() );
  }

  // If the segment is connected to and endpoint of this segment
  // (within dist) returns the endpoint that it connects to.  otherwise
  // returns null.
  public GPoint connects( GSegment gs, double dist )
  {
    double dist11 = Geometry.distance( getHeadPoint(), gs.getHeadPoint() );
    double dist12 = Geometry.distance( getHeadPoint(), gs.getTailPoint() );
    double dist21 = Geometry.distance( getTailPoint(), gs.getHeadPoint() );
    double dist22 = Geometry.distance( getTailPoint(), gs.getTailPoint() );
    
    double smallHeadDist = dist11;
    if (dist12 < smallHeadDist) {
      smallHeadDist = dist12;
    }
    double smallTailDist = dist21;
    if (dist22 < smallTailDist) {
      smallTailDist = dist22;
    }
    if (smallHeadDist < smallTailDist) {
      if (smallHeadDist < dist) {
	return (new GPoint( getHeadPoint() ));
      }
    }
    else if (smallTailDist < dist) {
      return (new GPoint( getTailPoint() ) );
    }

    return null;
    
  }

  /**
   * @return  If there is a single point of intersection within the bounds 
   *    of the segments, returns the intersection point.
   *    Otherwise, returns null.
   */
  public GPoint getIntersectionOnSegment( GSegment gs )
  {
    GPoint intersect = getIntersection( gs );
    
    if ( intersect == null ) {
      return null;
    }
    
    if ( !gs.onSegment( intersect ) ) {
      return null; 
    }

    if ( !onSegment( intersect ) ) {
      return null;
    }
    
    return intersect;

  }

  /**
   * @return If the intersection is close enough to the midpoint of both 
   * segments, returns the intersection point.
   * otherwise returns null.
   */
  public GPoint getIntersectionAtMidpoint( GSegment gs, int dist )
  {
    GPoint intersect = getIntersectionOnSegment( gs );

    if ( intersect == null ) {
      return null;
    }
    else {
      double midX;
      double midY;

      midX = (m_Point1.x + m_Point2.x) / 2;
      midY = (m_Point1.y + m_Point2.y) / 2;
      GPoint mid1 = new GPoint( midX, midY );
      
      midX = (gs.getHeadPoint().x + gs.getTailPoint().x) / 2;
      midY = (gs.getHeadPoint().y + gs.getTailPoint().y) / 2;
      GPoint mid2 = new GPoint( midX, midY );
      
      if (( Geometry.distance( mid1, intersect ) < dist ) &&
	  ( Geometry.distance( mid2, intersect ) < dist ) ) {
	return intersect;
      }
      else {
	return null;
      }
    }
  }


  /**
   * @return  If there is a single point of intersection between the two lines
   *               that are defined by the segments, returns the
   *               intersection;
   *          Otherwise, returns null.
   */
  public GPoint getIntersection( GSegment gs )
  {
    double m1 = getSlope();
    double m2 = gs.getSlope();

    if( m1 == m2 )
      return( null );

    //    Console.writeln( "hello 1");
    if( m1 == 0 && Double.isInfinite( m2 ) )
      return( new GPoint( gs.getHeadPoint().x,
			  m_Point1.y ) );
    if( Double.isInfinite( m1 ) && m2 == 0 )
      return( new GPoint( m_Point1.x,
			  gs.getHeadPoint().y ) );

    //    Console.writeln( "hello! m1=" + m1 + ", m2=" + m2 );
    double b1 = getIntercept();
    double b2 = gs.getIntercept();

    if( m1 == 0 )
      return( new GPoint( (m_Point1.y - b2) / m2,
			  m_Point1.y ) );

    if( m2 == 0 )
      return( new GPoint(
	(gs.getHeadPoint().y - b1) / m1,
	gs.getHeadPoint().y ) );

    if( Double.isInfinite( m1 ) )
      {
	//	Console.writeln( "Info!" );
	return( new GPoint(
			   m_Point1.x,
			   m_Point1.x * m2 + b2 ) );
      }

    if( Double.isInfinite( m2 ) )
      {
	//	Console.writeln( "Info1!" );
	return( new GPoint(
			   gs.getHeadPoint().x,
			   gs.getHeadPoint().x * m1 + b1 ) );
      }

    //    Console.writeln( "Normalo!" );
    return( new GPoint(
      (b1 - b2) / (m2 - m1),
      (m2 * b1 - m1 * b2) / (m2 - m1) ) );
  }

  /**
   * @return  Returns the intersection if there is one and it is not one
   *            of the endpoints of gs; otherwise, returns null.
   */
  public GPoint getSafeIntersection( GSegment gs )
  {
    GPoint gp = getIntersection( gs );

    if( gp == null
	|| gp.equals( getHeadPoint() )
	|| gp.equals( getTailPoint() )
	|| gp.equals( gs.getHeadPoint() )
	|| gp.equals( gs.getTailPoint() ) )
      return( null );

    return( gp );
  }

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

  public String toString( )
  {
    return "[" + m_Point1 + "," + m_Point2 + "]";
  }
  
  /////////////////////////////////////////////////////////////////////////////

  private GPoint m_Point1;
  private GPoint m_Point2;
}


