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

package rec.core.recognizers;

import edu.mit.sketch.geom.Line;
import edu.mit.sketch.geom.Point;
import edu.mit.sketch.geom.Polygon;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import rec.core.widgets.WLine;
import rec.core.widgets.WPolyLine;
import rec.sys.GeometryCalculator;
import rec.sys.Surface;
import rec.sys.Widget;
import rec.util.Assert;



/**
 * 
 * <p>
 * Created:   Tue Mar 19 18:01:05 2002<br>
 * Copyright: Copyright (C) 2002 by MIT.  All rights reserved.<br>
 * 
 * @author <a href="mailto:moltmans@ai.mit.edu">Michael Oltmans</a>
 * @version $Id: ToolkitPolygonParser.java,v 1.2 2003/03/06 00:40:47 moltmans Exp $
 **/

public class ToolkitPolygonParser {

  private Widget m_sparkWidget;
  private HashMap m_lines;
  private ParserFrame m_resultFrame;
  
  public ToolkitPolygonParser( Surface surface, Widget w )
  {
    m_lines = new HashMap();
    m_sparkWidget = w;
    GeometryCalculator gc = new GeometryCalculator( surface );
    Iterator it = gc.getVisibleWidgets().iterator();

    while( it.hasNext() ) {
      Object o = it.next();

      // don't include the widget in question as one of the options
      if( o == w ) {
	continue;
      }
      
      if ( o instanceof WLine ) {
	WLine wline = (WLine)o;
	Polygon p = new Polygon();
	p.addPoint( wline.getHead().getIntX(), wline.getHead().getIntY() );
	p.addPoint( wline.getTail().getIntX(), wline.getTail().getIntY() );

	m_lines.put( p, wline );
	//	Debug.println("poly", "added: " + p + " to " + wline );
      }
      else if( o instanceof WPolyLine ) {
	m_lines.put( ((WPolyLine)o).getPolyLine(), o );
	//	Debug.println("poly", "added: " + ((WPolyLine)o).getPolyLine() + " to " + o );
      }
      
    }


  }

  public Polygon getPolygon() 
  {
    return m_resultFrame.m_polyline;
  }

  public HashSet getSubWidgets() 
  {
    HashSet set = new HashSet();
    Iterator it = m_resultFrame.m_usedOptions.iterator();
    //    Debug.println("poly", "Getting subs for: " );
    while( it.hasNext() ) {
      Polygon polyline = (Polygon)it.next();
      Widget w = (Widget)m_lines.get( polyline );
      //      Debug.println("poly", "  " + polyline + " to " + m_lines.get( polyline ) );
      Assert.notNull( m_lines.get( polyline ),
		      "No mapping to a widget in the polygon parser for: " + polyline );
      set.add( m_lines.get( polyline ) );
    }
    set.add( m_sparkWidget );
    return set;
  }
  

  public boolean tryCombining( WLine wline, int tolerance )
  {
    Polygon p = new Polygon();
    p.addPoint( wline.getHead().getIntX(), wline.getHead().getIntY() );
    p.addPoint( wline.getTail().getIntX(), wline.getTail().getIntY() );
    return tryCombining( p, tolerance );
  }

  public boolean tryCombining( WPolyLine polyLine, int tolerance )
  {
    return tryCombining( polyLine.getPolyLine(), tolerance );
  }

  /**
   * Describe <code>tryCombining</code> method here.
   *
   * @param polyline a <code>Polygon</code> value
   * @param tolerance an <code>int</code> value
   * @return a <code>boolean</code> to indicate that there was a
   * successful combination
   **/
  private boolean tryCombining( Polygon polyline, int tolerance )
  {
    // first check if the current polyline is itself a polygon
    if( isClosed( polyline, tolerance ) ) {
      //      Debug.println("poly", "      poly is closed: " + polyline );
      polyline.xpoints[ polyline.npoints - 1 ] = polyline.xpoints[0];
      polyline.ypoints[ polyline.npoints - 1 ] = polyline.ypoints[0];
      
      m_resultFrame = new ParserFrame( polyline, new HashSet(), new HashSet() );
      return true;
    }



    // the general idea is that at each step of the algorithm we have
    // a polyline, a set of other polylines that we have not checked
    // for adjacency, and a set of polylines that were used to
    // construct the given polyline.  All this info is stored in a
    // ParserFrame.
    //    Debug.println("poly", "parsing: " + polyline );
    LinkedList stack = new LinkedList();
    ParserFrame pf = new ParserFrame( polyline,
				      new HashSet( m_lines.keySet() ), 
				      new HashSet() );
    //    pf.useOption( polyline );
    stack.add( pf );

    while( !stack.isEmpty() ) {
      ParserFrame frame = (ParserFrame)stack.removeFirst();
      Polygon p = new Polygon( frame.m_polyline );
      Iterator it = frame.m_options.iterator();

      while( it.hasNext() ) {
	Object obj = it.next();

	//	Debug.println("poly", "  testing: " + p );
	//	Debug.println("poly", "  against: " + obj );
	
	
	if( p.tryCombining( obj, tolerance ) ) {
	  //	  Debug.println("poly", "    combined poly: " + p );
	  // if its a closed polygon then return it; we win!
	  if( isClosed( p, tolerance ) ) {
	    //	    Debug.println("poly", "      poly is closed: " + p );
	    p.xpoints[ p.npoints - 1 ] = p.xpoints[0];
	    p.ypoints[ p.npoints - 1 ] = p.ypoints[0];

	    frame.useOption( obj );
	    frame.m_polyline = p;
	    m_resultFrame = frame;
	    return true;
	  }
	  ParserFrame newFrame = new ParserFrame( p,
						  (HashSet)frame.m_options.clone(),
						  (HashSet)frame.m_usedOptions.clone() );
	  newFrame.useOption( obj );
	  stack.addLast( newFrame );

	  // we need to make a new scratch p so that we don't use and
	  // mutate the one in the frame we just created.
	  p = new Polygon( frame.m_polyline );
	}
      }
    }
    return false;
  }

  private boolean isClosed( Polygon polygon, int tolerance )
  {
    if( polygon.npoints < 3 ) {
      return false;
    }
    
    Point first = polygon.pointAt(0);
    Point last = polygon.pointAt( polygon.npoints - 1 );
    double distance = first.distance( last );
    //    Debug.println("poly", "    checking closed:  " + distance);
    
    return  distance < tolerance;
  }



  class ParserFrame
  {
    Polygon m_polyline;
    HashSet m_options;
    HashSet m_usedOptions;

    ParserFrame( Polygon polyline, HashSet options, HashSet usedOptions ) 
    {
      m_polyline = polyline;
      m_options = options;
      m_usedOptions = usedOptions;
    }

    void useOption( Object obj )
    {
      //      Debug.println( "poly", "    Used: " + obj );
      Iterator it = m_options.iterator();
      //      Debug.println( "poly", "    Options:" );
      while( it.hasNext() ) {
	Object o = it.next();
	//	Debug.println( "poly", "      " + o);
      }
       
      m_options.remove( obj );
      m_usedOptions.add( obj );

      it = m_options.iterator();
      //      Debug.println( "poly", "    Options now are:" );
      while( it.hasNext() ) {
	Object o = it.next();
	//	Debug.println( "poly", "      " + o);
      }
    }
    
  }
  

  
  
}// ToolkitPolygonParser
