////////////////////////////////////////////////////////////////////////////////
//																			  //
// AUTHOR:      Tevfik Metin Sezgin	                        				  //
//				Massachusetts Institute of Technology						  //
//				Department of Electrical Engineering and Computer Science	  //
//				Artificial Intelligence Laboratory							  //
//																			  //
// E-MAIL:		mtsezgin@ai.mit.edu, mtsezgin@mit.edu						  //
//																			  //
// COPYRIGHT:   Tevfik Metin Sezgin                    						  //
//              All rights reserved. This code can not be copied, modified,   //
//              or distributed in whole or partially without the written 	  //
//				permission of the author. Also see the COPYRIGHT file.		  //
//																			  //
////////////////////////////////////////////////////////////////////////////////

  /**
  *
  * See the end of the file for the log of changes.
  *
  * $Author: mtsezgin $
  * $Date: 2000/06/29 02:09:33 $
  * $Revision: 1.1 $
  * $Headers$
  * $Id: Blackboard.java,v 1.1 2000/06/29 02:09:33 mtsezgin Exp $
  * $Name:  $
  * $Locker:  $
  * $Source: /mit/mtsezgin/CVS/Repository/REC/Blackboard.java,v $
  *
  **/
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Line2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.CubicCurve2D;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;

import java.util.Arrays;
import java.util.Vector;


/**
  *
  * Polygons recognized by the tablet are sent here. This class 
  * decides between candidate polygons and returns it.
  *
  **/

class Blackboard
{
    /**
    *
    * The array of hybrid fits obtained from speed and direction 
    * fits.
	*
    **/
	public static Fit hybrid_fits[];


    /**
    *
    * A general path object obtained from the hybrid fits.
    *
    **/
	public static GeneralPath general_path;


    /**
    *
    * Index difference tolerance
    *
    **/
	public static int index_difference = 3;

    /**
    *
    * Curvature vertices
    *
    **/
	private static Vertex curvature_vertices[];

	
    /**
    *
    * Pixel distance tolerance
    *
    **/
	public static double distance_difference = 5.0;


    /**
    *
    * a and b are two possible polygon matches for points. Return
	* the one that better approximates points.
    *
    **/
    public static Vertex[]
    decide( Vertex     speed_fit[], 
			Vertex     direction_fit[], 
			Point      points[],
			StrokeData data )
    {		
		hybrid_fits = getHybridFits( speed_fit, direction_fit, points );

        double speed_LSQE      = leastSquaresForPolygon( speed_fit, points );
        double direction_LSQE  = leastSquaresForPolygon( direction_fit,points );

		System.out.println( "LSQE for speed      : " + speed_LSQE	   );
        System.out.println( "LSQE for direction  : " + direction_LSQE  );

		Arrays.sort( hybrid_fits, hybrid_fits[0] );

		int    best_index   = 0;
		int    min_vertices = Integer.MAX_VALUE;
		for ( int i=0; i<hybrid_fits.length; i++ ) {
			if ( ( hybrid_fits[i].getLSQError()   < Tablet.LSQE_treshold ) &&
				 ( hybrid_fits[i].vertices.length < min_vertices         ) ) {
				min_vertices = hybrid_fits[i].vertices.length;
				best_index   = i; 
			}
		}
		
		curvature_vertices = 
			getCurvatureFit( points, hybrid_fits[best_index].vertices, 1.05 );
		
		Util.printArray( curvature_vertices, "curvature_vertices" );
		
		general_path = computeGeneralPath( speed_fit,
										   curvature_vertices,
										   hybrid_fits,
										   data );

		return hybrid_fits[best_index].vertices;
    }
    
		
    /**
    *
    * Return a fit with the vertices.
    *
    **/
    public static Vertex[]
	getCurvatureFit( Point points[], Vertex vertices[], double scale )
	{
		Vector result = new Vector();
		
		double distance = 0.0;
		double length   = 0.0;
		for ( int i=0; i<vertices.length-1; i++ ) {
			distance = vertices[i].distance( vertices[i+1] );
			length   = 0.0;
			result.addElement( new Vertex( vertices[i] ) );
			for ( int j=vertices[i].index; j<vertices[i+1].index; j++ ) {
				length += points[j].distance( points[j+1] );
				System.out.println( "scaled distance = " + distance*scale + 
									" length  = " + length );
				if ( length > distance*scale ) {
				System.out.println( "the treshold exceeded for " +
									"scaled distance = " + distance*scale + 
									" length  = " + length );

					result.addElement( new Vertex( points[j+1] ) );
					distance = points[j+1].distance( vertices[i+1] );
					length   = 0.0;
				}
			}
			result.addElement( new Vertex( vertices[i+1] ) );
		}
		
		return Vertex.vectorToArray( result );
    }


    /**
    *
    * a and b are two possible polygon matches for points. Return
	* the one that better approximates points.
    *
    **/
    public static Fit[]
    getHybridFits( Vertex speed_fit[], Vertex direction_fit[], Point points[] ) 
	{
        double speed_LSQE     = leastSquaresForPolygon( speed_fit,     points );
        double direction_LSQE = leastSquaresForPolygon( direction_fit, points );

		int bias = ( speed_LSQE < direction_LSQE ) ? 0 : 1;
		
		Vertex intersection[] = 
			getIntersection( speed_fit, 
							 direction_fit,  
							 bias );
			
		Vertex points_left_out_in_speed_fit[] = 
			getSymmetricDifference( speed_fit, 
									intersection, 
									intersection, 
									bias );
			
		Vertex points_left_out_in_direction_fit[] = 
			getSymmetricDifference( direction_fit, 
									intersection, 
									intersection, 
									bias );
		Vertex comparator = new Vertex();
		
		Arrays.sort( points_left_out_in_speed_fit,     comparator );
		Arrays.sort( points_left_out_in_direction_fit, comparator );

		printPoints( speed_fit,                        "speed" );
		printPoints( direction_fit,                    "direction" );
		printPoints( intersection,                     "intersection" );
		printPoints( points_left_out_in_speed_fit,     "speed_prime" );
		printPoints( points_left_out_in_direction_fit, "direction_prime" );		

		double augmented_speed_LSQE;
		double augmented_direction_LSQE;
		double augmented_intersection_LSQE;
		Vector fits                         = new Vector();		
		Vertex augmented_by_speed_fit[]     = null;
		Vertex augmented_by_direction_fit[] = null;
		Vertex augmented_intersection[] = Vertex.cloneVertices( intersection );
		int    speed_pointer     = points_left_out_in_speed_fit.length;
		int    direction_pointer = points_left_out_in_direction_fit.length;
		int    max_iterations    = speed_pointer + direction_pointer;
		fits.addElement( new Fit( 
						points, 
						Vertex.cloneVertices( direction_fit ) ) );
		fits.addElement( new Fit( 
						points, 
						Vertex.cloneVertices( speed_fit ) ) );
		for ( int i=0; i<max_iterations; i++ ) {
			augmented_speed_LSQE		= Double.MAX_VALUE;
			augmented_direction_LSQE	= Double.MAX_VALUE;
			augmented_intersection_LSQE = Double.MAX_VALUE;
			if ( speed_pointer > 0 ) {
				augmented_by_speed_fit = insertVertex( 
					augmented_intersection,
					points_left_out_in_speed_fit[speed_pointer-1] );
				augmented_speed_LSQE   = leastSquaresForPolygon(
									    	 augmented_by_speed_fit,
									    	 points );
				System.out.println( "LSQE when augmented by speed     = " +
									augmented_speed_LSQE );
			}
			if ( direction_pointer > 0 ) {
				augmented_by_direction_fit = insertVertex( 
					augmented_intersection,
					points_left_out_in_direction_fit[direction_pointer-1] );
				augmented_direction_LSQE   = leastSquaresForPolygon(
									    	 augmented_by_direction_fit,
									    	 points );
				System.out.println( "LSQE when augmented by direction = " +
									augmented_direction_LSQE );
			}
			
			if ( augmented_speed_LSQE < augmented_direction_LSQE ) {
				System.out.println( "Including vertex from speed fit" );
				augmented_intersection = augmented_by_speed_fit;
				augmented_intersection_LSQE = augmented_speed_LSQE;
				speed_pointer--;
			} else {
				System.out.println( "Including vertex from direction fit" );
				augmented_intersection = augmented_by_direction_fit;
				augmented_intersection_LSQE = augmented_direction_LSQE;
				direction_pointer--;
			}
			if ( augmented_direction_LSQE == Double.MAX_VALUE )
				break;
			fits.addElement( new Fit( 
							points, 
							Vertex.cloneVertices( augmented_intersection ),
							augmented_intersection_LSQE ) );
		}
		
		Fit result[] = new Fit[fits.size()];
		for ( int i=0; i<fits.size(); i++ ) {
			result[i] = ((Fit)(fits.elementAt(i)));
		}
				
		return result;
	}

    
    /**
    *
    * Compute a GeneralPath given the information in the arguments.
    *
    **/
    public static GeneralPath
	computeGeneralPath( Vertex     intersection[],
						Vertex     superset[],
						Fit        fits[],
						StrokeData data )
	{
		if ( fits.length > 3 ) {
			System.out.println( "\n\nI got something curved...\n\n" );
		}
		
		Util.printArray( intersection, "Before overlay subset" );
		Util.printArray( superset,     "Before overlay superset" );

		intersection = removeCloseVertices( intersection, 
											index_difference, 
											distance_difference );
		superset     = removeCloseVertices( superset, 
											index_difference,
											distance_difference );
		
		System.out.println( "Doing overlay..." );
		superset = overlay( intersection, superset, 
							index_difference, 
							distance_difference );
		
		Util.printArray( intersection, "After overlay subset" );
		Util.printArray( superset,     "After overlay superset" );
		
		Vertex excluded_points[];
		
		GeneralPath general_path = new GeneralPath();
		general_path.moveTo( intersection[0].x, intersection[0].y );
		
		Vertex last_added_vertex = intersection[0]; 
		Vertex next_vertex;
		
		for ( int i=0; i<intersection.length-1; i++ ) {
			excluded_points = getContiguosExcludedPointSequence( 
								i,
								intersection, 
								superset );
			if ( excluded_points.length == 0 ) {
				System.out.println( "No points excluded between indices " + 
									i + ", " + (i+1) + " in subset" );
			}
			Util.printArray( excluded_points, 
				"excluded points after intersection[" + i + "]" );

			if ( excluded_points.length == 0 ) {
				System.out.println( "Adding line from: " + last_added_vertex + 
									" to: "				 + intersection[i+1] );
				general_path.lineTo( intersection[i+1].x, intersection[i+1].y );
				last_added_vertex = intersection[i+1];
			}
			
			int   points_left         = excluded_points.length;
			int   unadded_point_index = 0;
			Point control_points[];
			while ( points_left > 0 ) {
				next_vertex      = excluded_points[unadded_point_index];
				control_points   = getControlPoints( last_added_vertex,
				                                     next_vertex,
													 data );
				System.out.println( "Adding curve from: " + last_added_vertex + 
									" to: "				  + next_vertex );
				general_path.curveTo( (float)control_points[0].x,
									  (float)control_points[0].y,
									  (float)control_points[1].x,
									  (float)control_points[1].y,
									  (float)next_vertex.x,
									  (float)next_vertex.y );
				Tablet.debug_graphics.setColor( Color.red );
				Tablet.debug_graphics.drawString( "" + i,
												  (int)control_points[0].x,
												  (int)control_points[0].y );
				Tablet.debug_graphics.fillOval( (int)control_points[0].x,
												(int)control_points[0].y,
												5,
												5 );
				Tablet.debug_graphics.drawLine( (int)last_added_vertex.x,
												(int)last_added_vertex.y,
												(int)control_points[0].x,
												(int)control_points[0].y );

				Tablet.debug_graphics.setColor( Color.blue );
				Tablet.debug_graphics.drawString( "" + i,
												  (int)control_points[1].x,
												  (int)control_points[1].y );
				Tablet.debug_graphics.fillOval( (int)control_points[1].x,
												(int)control_points[1].y,
												5,
												5 );
				Tablet.debug_graphics.drawLine( (int)next_vertex.x,
												(int)next_vertex.y,
												(int)control_points[1].x,
												(int)control_points[1].y );


				last_added_vertex = excluded_points[unadded_point_index];
				points_left -=1;
				unadded_point_index +=1;
				System.out.println( "points_left = " + points_left );
			}
		}

		return general_path;
	}
	
    
    /**
    *
    * Return the sequence of contiguos point sequence that 
	* is included in superset, but is excluded in subset. We
	* start the search from start_index in subset. So the indices
	* of the vertices returned are strictly between the indices of
	* subset[start_pointer] and subset[start_pointer+1]. If 
	* start_pointer points to the last in the subset, then all 
	* vertices in superset whose indices are larger than the 
	* index of the last element in subset are returned. This 
	* method assumes that the input vertices are sorted by index.
    *
    **/
    public static Vertex[]
    getContiguosExcludedPointSequence( int    start_pointer, 
									   Vertex subset[], 
									   Vertex superset[] )
    {
		Vector excluded_points = new Vector();
		
		int beginning_pointer_in_superset = Integer.MAX_VALUE;
		for ( int i=0; i<superset.length; i++ ) {
			if ( superset[i].index > subset[start_pointer].index ) {
				beginning_pointer_in_superset = i;
				break;
			}
		}
		
		if ( beginning_pointer_in_superset == Integer.MAX_VALUE ) {
			System.out.println( "NO EXCLUDED POINT SEQUENCE" );
			return Vertex.vectorToArray( excluded_points );
		}
		
		if ( start_pointer+1 == subset.length ) {
			for ( int i = beginning_pointer_in_superset; 
				  i<superset.length; 
				  i++ ) { 
				excluded_points.addElement( superset[i] );
			}
			
			return Vertex.vectorToArray( excluded_points );
		}
		
		for ( int i = beginning_pointer_in_superset;
			  ( ( i<superset.length ) &&
			    ( superset[i].index < subset[start_pointer+1].index ) );
			  i++ ) {
			excluded_points.addElement( superset[i] );
		}
		
		return Vertex.vectorToArray( excluded_points );
	}
	    
    /**
    *
    * calculate the lsq error. points is the actual data points,
	* polgon is the match 
    *
    **/
    public static double
    leastSquaresForPolygon( Vertex vertices[], Point points[] )
    {
        double lsq_error = 0.0;
            
        Vertex v1;
        Vertex v2;
        Point  p;

		if ( ( vertices == null ) || ( vertices.length < 2 ) ) {
			return Double.MAX_VALUE;
		}
	
        for ( int i=0; i<vertices.length-1; i++ ) {
			v1 = vertices[i];
			v2 = vertices[i+1];
			            
            for ( int j=v1.index; j<=v2.index; j++ ) {
				p = points[j];
                lsq_error += GeometryUtil.ptSegDistSq( v1, v2, p );
            }// niye circle tanimiyor????
        }


        return (lsq_error/(points.length-1));
    }
    
    
    /**
    *
    * getPolygonIndices
    *
    **/
    public static int[]
    getPolygonIndices( Point output_polygon[], Point points[] )
    {
		int last_index = points.length-1;
		
        if ( (output_polygon == null) || (output_polygon.length<2) ) {
            System.err.println("Error: getPolgonIndices no polygon detected.");
        }
        
        if ( Tablet.debug ) {
            for ( int i=0; i<output_polygon.length; i++ ) {
                System.out.println( "@ " + i + " " + output_polygon[i] );
            }
        }
        
        int index     = 0;
        int indices[] = new int[output_polygon.length];

        for ( int i=0; (i<=last_index) && (index<output_polygon.length); i++ ) {
            if ( points[i].equals( output_polygon[index] ) ){
                indices[index] = i;
                if ( Tablet.debug ) {
                    System.out.println( "# " + i + " " + index +
                                        " " + output_polygon[index] + 
                                        " " + points[indices[index]] + " equal");
                }
                index++;
            } else {
                if ( Tablet.debug ) {
                    System.out.println( "# " + i + " " + index +
                                        " " + output_polygon[index] + 
                                        " " + points[i] + " not equal");
                }
			}
        }
        
        if ( Tablet.debug ) {
            for ( int i=0; i<indices.length; i++ ) {
                 System.out.println( "* " + i + " " + indices[i] +
                                     " " + output_polygon[i] +
                                     " " + points[indices[i]] );
             }
        System.out.println( index + " " + indices.length );
        }
        
        if ( index != indices.length ) {
            System.err.println( "Error: getPolgonIndices missed a point" );
            for ( int i=0; i<last_index; i++ ) {
                System.err.println( "Error: getPolgonIndices " + 
                                     i + " " + points[indices[i]] );
            }
        }
        return indices;
    }
	
	
    /**
    *
    * Print the points array.
    *
    **/
    public static void
    printPoints( Point points[], String name )
    {
		Util.printArray( points, name );
	}
	
	
    /**
    *
    * Return true if the array contains a Vertex that is sufficiently
	* close to the argument vertex given the tolerances. 
    *
    **/
    public static boolean
    containsWithTolerance( Vertex vertices[],
						   Vertex vertex,
						   int    index_difference_tolerance, 
						   double distance_tolerance )
    {
		for ( int i=0; i<vertices.length; i++ ) {
			if ( ( Math.abs( vertex.index - vertices[i].index ) < 
				   index_difference_tolerance                      ) ||
				 ( vertex.distance( vertices[i] )				< 
				   distance_tolerance                              ) ) {
				return true;
			}
		}
		
		return false;
	}
	
	
    /**
    *
    * Get the symmetric difference of a and b. Use a reasonable 
	* measure of "closeness" for the points in a and b so that 
	* points are considered to be equal even if they do not have 
	* the same index. Intersection can be null or it may contain
	* the correct intersection of a and b.
    *
    **/
    public static Vertex[]
    getSymmetricDifference( Vertex a[], 
							Vertex b[], 
							Vertex intersection[], 
							int bias )
    {
		if ( intersection == null ) {
			intersection = getIntersection( a, b, bias );
		}
	
		Vector difference = new Vector();
		
		for ( int i=0; i<a.length; i++ ) {
			if ( !containsWithTolerance( intersection, 
										 a[i], 
										 index_difference, 
										 distance_difference ) )
			difference.addElement( new Vertex( a[i] ) );
		}
		for ( int i=0; i<b.length; i++ ) {
			if ( !containsWithTolerance( intersection, 
										 b[i], 
										 index_difference,
										 distance_difference ) )
			difference.addElement( new Vertex( b[i] ) );
		}
		
		Vertex result[] = new Vertex[difference.size()];
		for ( int i=0; i<result.length; i++ ) {
			result[i] = (Vertex)difference.elementAt( i );
		}
		
		return result;
	}
	
	
    /**
    *
    * Overlay subset and superset so the vertices very close to
	* one another are collapsed into one.
    *
    **/
    public static Vertex[]
	overlay( Vertex subset[], 
			 Vertex superset[], 
			 int    index_tolerance, 
			 double distance_tolerance )
	{
		Vector superset_result = new Vector();
		
		if ( ( subset        == null ) || ( superset        == null ) ||
			 ( subset.length == 0    ) || ( superset.length == 0    ) ) {
			 return superset;
		}
		
		subset   = removeCloseVertices( subset,   
										index_tolerance, 
										distance_tolerance );
		superset = removeCloseVertices( superset,
										index_tolerance, 
										distance_tolerance );

		int index = 0;
		for ( int i=0; i<superset.length; i++ ) {
			index = getIndexInVertices( subset, 
										superset[i], 
										index_tolerance, 
										distance_tolerance );
			if ( index != -1 ) {
				superset_result.addElement( subset[index] );
				System.out.println( "Replacing item superset[" + i + "] with " +
				                    "subset[" + index + "]" );
			} else {
				superset_result.addElement( superset[i]   );
			}
		}
		
		superset = Vertex.vectorToArray( superset_result );
		
		return superset;
	}
	
	
    /**
    *
    * Return the index in subset if the array contains a Vertex that
	* is sufficiently close to the argument vertex given the 
	* tolerances. Return -1 if no such vertex exists.
    *
    **/
    public static int
    getIndexInVertices( Vertex vertices[],
						Vertex vertex,
						int    index_difference_tolerance, 
						double distance_tolerance )
    {
		for ( int i=0; i<vertices.length; i++ ) {
			if ( ( Math.abs( vertex.index - vertices[i].index ) < 
				   index_difference_tolerance                      ) ||
				 ( vertex.distance( vertices[i] )				< 
				   distance_tolerance                              ) ) {
				return i;
			}
		}
		
		return -1;
	}
	
	
    /**
    *
    * Remove close vertices.
    *
    **/
    public static Vertex[]
	removeCloseVertices( Vertex vertices[],
			 			 int    index_tolerance, 
			 			 double distance_tolerance )
	{
		Vector result = new Vector();
		
		for ( int i=0; i<vertices.length; i++ ) {
			if ( !containsWithTolerance( Vertex.vectorToArray( result ),
										 vertices[i],
										 index_tolerance, 
										 distance_tolerance ) ) {
				result.addElement( vertices[i] );
			}
		}
		
		return Vertex.vectorToArray( result );
	}
	
	
    /**
    *
    * Get the common points in a and b. Use a reasonable measure of
	* "closeness" for the points in a and b so that points are 
	* considered to be equal even if they do not have the same 
	* index.
    *
    **/
    public static Vertex[]
    getIntersection( Vertex a[], Vertex b[], int bias )
    {
		Vector intersection = new Vector();
		
		for ( int i=0; i<a.length; i++ ) {
			for ( int j=0; j<b.length; j++ ) {
				if ( ( Math.abs( a[i].index - b[j].index ) < 3 ) ||
					 ( a[i].distance( b[j] )               < 5 ) ) {
					if ( bias == 0 ) {
						intersection.addElement( new Vertex( a[i] ) );
					} else {
						intersection.addElement( new Vertex( b[j] ) );
					}
					break;
				}
			}
		}
		
		Vertex result[] = new Vertex[intersection.size()];
		for ( int i=0; i<result.length; i++ ) {
			result[i] = (Vertex)intersection.elementAt( i );
		}
		
		return result;
	}
	
	
    /**
    *
    * Get all the points in a and b. Use a reasonable measure of
	* "closeness" for the points in a and b so that points are 
	* considered to be equal even if they do not have the same 
	* index.
    *
    **/
    public static Vertex[]
    getUnion( Vertex a[], Vertex b[] )
    {
		Vertex union[]     = new Vertex[a.length+b.length];		
		int a_pointer      = 0;
		int b_pointer      = 0;
		int union_pointer  = 0;
		
		while ( ( a_pointer < a.length ) && ( b_pointer < b.length ) ) {
			if ( a[a_pointer].index < b[b_pointer].index ) {
				union[union_pointer] = a[a_pointer];
				union_pointer++;
				a_pointer++;
			} else {
				union[union_pointer] = b[b_pointer];
				union_pointer++;
				b_pointer++;
			}
		}
		
		if ( a_pointer < a.length ) {
			for ( ; union_pointer<union.length; a_pointer++, union_pointer++ ) {
				union[union_pointer] = a[a_pointer];
			}
		}

		if ( b_pointer < b.length ) {
			for ( ; union_pointer<union.length; b_pointer++, union_pointer++ ) {
				union[union_pointer] = b[b_pointer];
			}
		}
		
		return union;
	}
	
	
    /**
    *
    * Print the points assuming with their corresponding indices
	* in real_points. Assume that original points contains all
	* points[i].
    *
    **/
    public static void
    printPointsWithIndices( Point points[], String name, Point real_points[] )
    {
		int indices[] = getPolygonIndices( points, real_points );
		System.out.println( name + ".length is " + points.length );
		for ( int i=0; i<points.length; i++ ) {
			System.out.println( name + "[" + i + "] = " + 
								points[i] +
								"\t index = " + indices[i] );
		}
	}
	
	
    /**
    *
    * See if all the certainty values are between 0 and 1.
    *
    **/
    public static void
    checkCertaintyBounds( Vertex vertices[] )
    {
		double certainty = 0.0;
		for ( int i=0; i<vertices.length; i++ ) {
			certainty = vertices[i].certainty;
			if ( ( certainty > 1.0 ) || ( certainty < 0.0 ) ) {
				System.err.println( "ERROR : certainty bound exceeded " + 
									certainty );
			}
		}
	}
	
	
    /**
    *
    * Paint the general path
    *
    **/
    public static void
    paintGeneralPath( GeneralPath general_path )
    {
		Graphics2D g2D = (Graphics2D)(Tablet.debug_graphics);
		
		g2D.setColor( Color.magenta );
		g2D.setStroke( new BasicStroke( 1.0f ) );
		g2D.draw( general_path );

		System.out.println( "The general path has " + 
							AWTUtil.getSegmentCount( general_path ) +
							" segments " );

		// Tablet.debug_graphics.setColor( Color.red );
		// (new Polygon( curvature_vertices )).paint( Tablet.debug_graphics );
	}
	
	
    /**
    *
    * Paint the general path
    *
    **/
    public static void
    paintGeneralPathInSegments( GeneralPath general_path )
    {
		PathIterator path_iterator;
		Graphics2D g2D = (Graphics2D)(Tablet.debug_graphics);
		
		g2D.setStroke( new BasicStroke( 1.0f ) );
		path_iterator = general_path.getPathIterator( new AffineTransform() );
		
		Shape  shape          = null;
		Point  last_point     = new Point();
		Color  colors[]       = { Color.red, Color.blue, Color.green };
		double coefficients[] = new double[6];
		int    i              = 0;
		int    type           = 0;
		while ( !path_iterator.isDone() ) {
			g2D.setColor( colors[i++%colors.length] );
			type = path_iterator.currentSegment( coefficients );
			
			switch ( type ) {
				case PathIterator.SEG_CUBICTO :
					shape = new CubicCurve2D.Double( last_point.x,
													 last_point.y,
													 coefficients[0],
													 coefficients[1],
													 coefficients[2],
													 coefficients[3],
													 coefficients[4],
													 coefficients[5] );
					last_point.setLocation( coefficients[4], coefficients[5] );
					break;

				case PathIterator.SEG_QUADTO :
					shape = new QuadCurve2D.Double( last_point.x,
													last_point.y,
													coefficients[0],
													coefficients[1],
													coefficients[2],
													coefficients[3] );
					last_point.setLocation( coefficients[2], coefficients[3] );
					break;

				case PathIterator.SEG_LINETO :
					shape = new Line2D.Double( last_point.x,
											   last_point.y,
											   coefficients[0],
											   coefficients[1] );
					last_point.setLocation( coefficients[0], coefficients[1] );
					break;

				case PathIterator.SEG_MOVETO :
					shape = null;
					last_point.setLocation( coefficients[0], coefficients[1] );
					break;

				case PathIterator.SEG_CLOSE :
					shape = null;
					break;
					
				default:
					System.out.println( "Error generalPath no matching case" );
			}
			
			if ( shape != null ) {
				g2D.draw( shape );
			}
			path_iterator.next();
		}


		System.out.println( "The general path has " + 
							AWTUtil.getSegmentCount( general_path ) +
							" segments " );

		// Tablet.debug_graphics.setColor( Color.red );
		// (new Polygon( curvature_vertices )).paint( Tablet.debug_graphics );
	}
	
	
    /**
    *
    * Return the vertices obtained by inserting the input vertex to 
	* the appropriate slot in the input vertices.
    *
    **/
    public static Vertex[]
    insertVertex( Vertex vertices[], Vertex vertex )
    {
		int insertion_place = 0;
		
		Vertex result[] = new Vertex[vertices.length+1];
		
		for ( int i=0; i<vertices.length; i++ ) {
			if ( vertices[i].index > vertex.index ) {
				insertion_place = i;
				break;
			}
		}
		
		if ( insertion_place == 0 ) {
			if ( ( vertices.length == 0 ) || 
				 ( vertices[0].index > vertex.index ) ) {
				insertion_place = 0;  // Redundant, I know...
			} else {
				insertion_place = vertices.length - 1;
			}
		}
		
		for ( int i=0; i<insertion_place; i++ ) {
			result[i] = vertices[i];
		}
		result[insertion_place] = vertex;
		for ( int i=insertion_place+1; i<result.length; i++ ) {
			result[i] = vertices[i-1];
		}
		
		return result;		
	}

    /**
    *
    * Return the control points to be used for drawing a curve 
	* from v1 to v2.
    *
    **/
    public static Point[]
    getControlPoints( Vertex v1, Vertex v2, StrokeData data )
    {
		Vertex vertices[]       = data.vertices;
		Point  control_points[] = new Point[2];
		double segment_length   = 
			GeometryUtil.segmentLength( vertices, v1.index, v2.index )/3;
		
		Vertex first  = ( v1.index < v2.index ) ? v1 : v2;
		Vertex second = ( v1.index < v2.index ) ? v2 : v1;
		
		int offset = 3;
		int i      = offset;
		int j      = offset;
		
		if ( !( first.index + offset < vertices.length ) ) {
			i = vertices.length - first.index - 1;
		}

		if ( second.index - offset < 0 ) {
			j = 0;
		}
		
		control_points[0]   = new Point();
		control_points[1]   = new Point();
		control_points[0].x = vertices[first.index +i].x - first.x;
		control_points[0].y = vertices[first.index +i].y - first.y;
		control_points[1].x = vertices[second.index-j].x - second.x;
		control_points[1].y = vertices[second.index-j].y - second.y;
		
		control_points[0].scale( segment_length/control_points[0].magnitude() );
		control_points[1].scale( segment_length/control_points[1].magnitude() );
		
		control_points[0].translate( (double)first.x,  (double)first.y  );
		control_points[1].translate( (double)second.x, (double)second.y );
		
		Point result[] = new Point[2];
		
		if ( v1.index >= v2.index ) {
			result[0] = control_points[1];
			result[1] = control_points[0];
		} else {
			result = control_points;
		}

		return result;
	}
}

/**
  *
  * $Log: Blackboard.java,v $
  * Revision 1.1  2000/06/29 02:09:33  mtsezgin
  * This is the blackboard.
  *
  **/
