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

// $Id: PolygonStrokeRecognizer.java,v 1.6 2003/03/06 00:40:47 moltmans Exp $
// Time-stamp: <2000-04-06 10:38:24 moltmans>
package rec.core.recognizers;


import java.util.*;
import java.awt.*;
import rec.util.*;
import rec.sys.*;
import rec.geo.*;
import rec.core.widgets.*;
import rec.core.*;

public class PolygonStrokeRecognizer extends Recognizer
{
  public PolygonStrokeRecognizer()
  {
  }

  protected int N = -1;
  protected GPoint[] points = null;

  public boolean recognize( Surface s, Widget w )
  {
    Squiggle sq = (Squiggle)w;

    // not a loop, then not a poly.
    if ( !LooseCircleRecognizer.isLoop( sq ) )
      return false;

    // hack to make sure little circles are not polygons
    if ( !generalCheck( sq ) )
      return false;

    int angleconst = 135;
    int bevconst = 150;  // bevel angle threshhold for round corners.

    if ( sq.getLength() < 160 )
      {
	bevconst = 360 / 3;
	angleconst = 360 / 5;
      }

    boolean did_it = runCalculation( sq, angleconst, bevconst );

    if ( !did_it )
      {
	//Console.writeln( "Not single stroke polygon." );
	return false;
      }
    else
      {
	//HACK -- should look for lines outside the expected
	// but... redo with looser bounds to make sure we have corners
	//	runCalculation( sq, 150, 125 );

	//Console.writeln( "This poly has " + N + " vertices" );
	
	// make a poly
	WPolygon wp = new WPolygon( points, N );

	s.replaceWidget( sq, wp );

	return true;
      }
  } // end recognize


  public boolean runCalculation( Squiggle sq, int thresh1, int thresh2 )
  {
    // get the squiggle in an array
    N = sq.getNumberOfPoints();
    points = new GPoint[ N ];
    for ( int i = 0; i < N; i++ )
      points[i] = sq.getPointAt( i );

    boolean did_it = defacit( thresh1, thresh2 );
    //    clean();

    return did_it;
  }

  public void clean( )
  {
    int p;
    double d1, d2, d3;
    double t1, t2, t3;
    GPoint p1, p2, p3, p4;
    double del1, del2;

    if ( N < 4 )
      return;    

    boolean changed = true;
    while( changed )
      {
	changed = false;
	for ( int i = 0; i < N; i++ )
	  {
	    p1 = points[ i ];
	    p2 = points[ (i+1) % N ];
	    p3 = points[ (i+2) % N ];
	    p4 = points[ (i+3) % N ];
		
	    d1 = Geometry.distance( p1, p2 );
	    d2 = Geometry.distance( p2, p3 );
	    d3 = Geometry.distance( p3, p4 );

	    // compute if line should be cut

	    // d2 less than 10, or less than 1/4 average of adj. lines
	    boolean cut = ( d2 < ( Math.max( 10, (d1 + d3) / 8 ) ) );
	    //	    System.err.println( "p1 " + p1 + " p2 = " + p2 + " d " + d2 );

	    if ( cut )
	      {
		changed = true;
		
		//		System.err.println( "Clobber!" );
		
		del1 = Geometry.delta_angle( p1, p2, p3 );
		del2 = Geometry.delta_angle( p2, p3, p4 );
		
		if ( Math.abs( del1 ) < Math.abs( del2 ) )
		  removePoint( (i+1) % N );
		else
		  removePoint( (i+2) % N );
		
		if ( N < 4 )
		  return;
	      }
	  }
      }

    // we might have some lines which should be merged now...
    //    defacit();

  } // end clean



  boolean defacit( int thresh_1, int bevel_threshold )
  {
    boolean[] hits = new boolean[ N ];
    int num_hits = 0;
    for ( int i = 0; i < N; i++ ) hits[i] = false;

    // the beginning is always a corner
    hits[0] = true;
    num_hits = 1;

    boolean bevel = false;

    // go to N-1 cause the end is _never_ a corner.
    for ( int i = 1; i < N - 1; i++ )
      {
	GPoint p1 = points[i-1];
	GPoint p2 = points[i];
	GPoint p3 = points[i+1];
	
	// if the three points form a corner, mark it.
	int a = Geometry.delta_angle( p1, p2, p3 );

	//	Console.writeln( "" + i + " - Angle = " + a );
	if ( a < thresh_1 )
	  {
	    //Console.writeln( "hit!" );
	    hits[i] = true;
	    num_hits++;
	    bevel = false;
	  }
	else if ( a < bevel_threshold ) // hack for rounded corners (bleh!)
	  {
	    if ( bevel )
	      {
		if ( Geometry.distance( p1, p2 ) < 15 )
		  {
		    //    Console.writeln( "bevel hit!" );
		    hits[i] = true;
		    num_hits++;
		    bevel = false;
		  }
	      }
	    else
	      bevel = true;
	  }
	else
	  bevel = false;
      }

    if ( num_hits >= 3 )
      {
	//Console.writeln( "Going to strip non-corner points." );
	int cur = 0;
	for ( int i = 0; i < N; i++ )
	  {
	    if ( hits[i] )
	      points[cur++] = points[i];
	  }
	N = num_hits;

	//Console.writeln( "Poly now has " + N + " sides." );
	return true;
      }

    // no action taken
    return false;
  }

  /**
   * Go through a sequence of points, and remove any point if the two
   * adjacent line segments are within some number of degrees of the
   * same slope.    
   */
  void defacit2( )
  {
    int cur;
    int x, y;
    boolean have = false;
    double t = 0, t2 = 0, ts = 0;
    GPoint l2 = null, l = null;

    // this comparison should be more easily done.  For example,
    // if we can easily compute the sin of 1/2 the angles, we would
    // have an easy way to compare.

    // certainly this is not the best.  Some math shows a way to do it
    // without any calls to atan2, for example.

    cur = 1;
    //    points[cur++] = points[0];
    l = points[0];
    have = false;

    for ( int i = 1; i < N + 1; i++ )
      {
	l2 = points[ i % N ];

	if ( have )
	  {
	    t2 = Geometry.angle( points[i-1], l2 );

	    //	    System.err.println( "t2 = " + ( t2 / Math.PI ) + " PI " 
	    //				+ x + " " + y );
	    
	    ts = t - t2;
	    if ( ts > Math.PI ) ts -= 2 * Math.PI;
	    if ( ts < -1 * Math.PI ) ts += 2 * Math.PI;

	    if ( Math.abs( ts ) > 0.6 ) // approx 45 degrees or less
	      {
		l = points[i-1];
		points[cur++] = points[i-1];
		have = false;
	      }
	  }

	t = Geometry.angle( l, l2 );
	have = true;
      }

    // reset proper N.
    N = cur;

//      for ( int i = 0; i < N; i++ )
//        System.err.println( "" + i + "\t " + points[i] 
//  			  + "\t " + Geometry.angle( points[i],
//  					       points[ (i+1) % N ] ) );
  }


  protected void removePoint( int p )
  {
    N--;
    for ( int j = p; j < N; j++ )
      points[j] = points[j+1];
  }

    /*
    for ( int i = 0; i < N; i++ )
      {
	p1 = points[i];
	p2 = points[ (i+1) % N ];
	p3 = points[ (i+2) % N ];
	p4 = points[ (i+3) % N ];

	d1 = Geometry.distance( p1, p2 );
	d2 = Geometry.distance( p2, p3 );
	d3 = Geometry.distance( p3, p4 );

	if ( d2 < ( (( d1 < d3 ) ? d1 : d3) / 3) )
	  {
	    System.err.println( "Clobber!" );
	    d1 = Geometry.theta( p1, p2 );
	    d2 = Geometry.theta( p2, p3 );
	    d3 = Geometry.theta( p3, p4 );
	    
	    del1 = Geometry.delta( d1, d2 );
	    del2 = Geometry.delta( d2, d3 );
	    
	    if ( del1 < del2 ) // merge to first seg
	      p = (i+1) % N;
	    else
	      p = (i + 2) % N;

	    N--;
	    for ( int j = p; j < N; j++ )
	      points[j] = points[j+1];
	    if ( N < 4 )
	      return;
	  }
      }
      */

  public boolean generalCheck( Squiggle sq )
  {

    double len = sq.getLength();
    if ( len < 80 )
      {
	//Console.writeln( "Stroke too short for single poly recog." );
	return false;
      }

    return true;
  }


  public Iterator getInputTypes()
  {
    Vector v = new Vector();

    v.addElement( "rec.sys.Squiggle" );

    return( v.iterator() );
  }

  public Iterator getOutputTypes()
  {
    Vector v = new Vector();

    v.addElement( "rec.core.widgets.WPolygon" );

    return( v.iterator() );
  }

}
