/**
  *
  * See the end of the file for the log of changes.
  *
  * $Author: mtsezgin $
  * $Date: 2000/06/02 21:11:16 $
  * $Revision: 1.16 $
  * $Headers$
  * $Id: Tablet.java,v 1.16 2000/06/02 21:11:16 mtsezgin Exp $
  * $Name:  $
  * $Locker:  $
  * $Source: /mit/mtsezgin/CVS/Repository/REC/Tablet.java,v $
  *
  **/


import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Frame;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Image;
import java.awt.Toolkit;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

/**
  *
  * In this class last_index refers to the index of the last
  * point read in the points array (ie. it can directly be used
  * to access the last point entered. last_index is actually one 
  * less than the number of calid points in the points array.
  * Due to derivation, speed array's length is equal to last_index.
  * Also acceleration's legnth is (last_index-1). Time array has 
  * the same length as points. It stores the time that was supplied
  * to addPoint( Point point, long time ) method. In order to 
  * make proper use of cached data, additions to points and time
  * array should be done via the addPoint( Point point, long time ) 
  * method.
  *
  **/

class      Tablet
extends    Frame
implements ActionListener, 
           KeyListener,
           MouseMotionListener, 
           MouseListener
{
    public int        last_index;
    public Point      points[];
    public Point      output_polygon[];
    public long       time[];
    public double     speed[];          // Time derivative of position
    public double     acceleration[];   // Time derivative of velocity
    public double     d[];              // Direction
    public double     dd_dt[];          // Time derivative of directions
    public PolarPoint polar_points[];   // Points for polar representation
    public double     scale               = 1.0;
    public double     a_scale             = 3000;
    public double     v_scale             = 60;
    public double     t_scale             = .1;
    public double     theta_scale         = 20;
    public double     combine_treshold    = 100;
    public double     d_theta_scale       = 1000;
    public double     sd_scale            = .00005;
    public double     step_angle          = Math.PI/18;
    public double     orientation_angle   = Math.PI/180; // Was /24
    public double     turn_angle_treshold = Math.PI/8;   // Was /6
    public Point      lower_left;
    public Point      upper_right;
    public Point      center;
    public Dimension  radius;
    public Point      line[];

    public boolean  polar_points_is_valid = true;
    public boolean  v_is_valid            = true;
    public boolean  a_is_valid            = true;
    public boolean  d_is_valid            = true;
    public boolean  dd_dt_is_valid        = true;
    public boolean  display_lsq_vectors   = false;
    public boolean  show_parsed_objects   = false;
    public boolean  in_recognition_mode   = true;
    
    public Frame    v_window;
    public Frame    a_window;
    public Frame    d_window;        // Direction window
    public Frame    dd_dt_window;    // Window for direction's derivative
    public MenuBar  menubar;
    public Menu     file;
    public MenuItem quit_menu;

    public Menu     edit;
    public MenuItem undo_menu;
    
    public Menu     windows;
    public MenuItem v_menu;
    public MenuItem a_menu;
    public Menu     pen_menu;
    public Menu     direction_menu;
    public MenuItem d_menu;
    public MenuItem dd_dt_menu;
    public MenuItem controls_menu;
    public MenuItem statistics_menu;
    
    public Menu     options;
    public MenuItem vectors_menu;
    public MenuItem display_menu;
    
    public static  boolean          debug;    
    public static  boolean          debug2;    
    public static  Graphics         x_window_graphics;
    public static  Graphics         debug_graphics;
    public static  StatisticsModule bar_statistics_module;
    public static  StatisticsModule sliding_window_statistics_module;

    public static  double window_width = Math.PI/36;
    
    public Graphics v_window_graphics;
    public Graphics a_window_graphics;
    public Graphics d_window_graphics; 
    public Graphics dd_dt_window_graphics;
    
    // Objects that we need for double buffering.
    private Dimension off_dimension = new Dimension( 500, 500 );

    public static long    sequence_delay = 1500;
    public final  boolean FILTER_ON      = false; // Prompt for filtering 
                                                  // if set to true

    private ObjectManager  object_manager;
    private ControlModule  control_module;
    
    
    
    /**
    *
    * The constructor.
    *
    **/
    public Tablet( int max_data_points, 
                   double s, 
                   double avg_scale, 
                   String cursor_image_name )
    {
        super( "VisiPad 1.0" );
        
        sd_scale = s;
        scale    = avg_scale;

        object_manager    = new ObjectManager( this );
        control_module      = new ControlModule( this );
        
        bar_statistics_module = new 
            StatisticsModule( object_manager );
        sliding_window_statistics_module = new
            StatisticsModule( object_manager );
            
        control_module.setLocation( 500, 500 );
        bar_statistics_module.setLocation( 500, 100 );
        sliding_window_statistics_module.setLocation( 500, 500 );
            

        addKeyListener( object_manager );
        addMouseListener( object_manager );
        addMouseMotionListener( object_manager );
        
        points = new Point[max_data_points];
        time   = new long[max_data_points];
        for ( int i=0; i<points.length; i++ ) {
            points[i] = new Point();
        }
        
        menubar         = new MenuBar();
        file            = new Menu( "File" );
        quit_menu       = new MenuItem( "Quit" );
        edit            = new Menu( "Edit" );
        undo_menu       = new MenuItem( "Undo" );
        windows         = new Menu( "Windows" );
        pen_menu        = new Menu( "MOTION" );
        v_menu          = new MenuItem( "Speed" );
        a_menu          = new MenuItem( "Acceleration" );
        direction_menu  = new Menu( "DIRECTION" );
        d_menu          = new MenuItem( "  \u0398" );
        dd_dt_menu      = new MenuItem( "d\u0398/dt" );
        controls_menu   = new MenuItem( "Controls" );
        statistics_menu = new MenuItem( "Statistics" );
        v_window        = new Frame( "Speed vs Time" );
        a_window        = new Frame( "Acceleration vs Time" );
        d_window        = new Frame( "Direction vs Time (In radians)" );
        dd_dt_window    = new Frame( "dd/dt vs time" );
        options         = new Menu( "Options" );
        vectors_menu    = new MenuItem( "Display LSQ Vectors" );
        display_menu    = new MenuItem( "Show parsed objects" );

        v_window_graphics     = v_window.getGraphics();
        a_window_graphics     = a_window.getGraphics();
        d_window_graphics     = d_window.getGraphics();
        dd_dt_window_graphics = dd_dt_window.getGraphics();
        
        v_window.setSize( 400, 400 );
        a_window.setSize( 400, 400 );
        d_window.setSize( 400, 400 );
        dd_dt_window.setSize( 400, 400 );
        bar_statistics_module.setSize( 800, 400 );
        sliding_window_statistics_module.setSize( 800, 400 );
        
        setMenuBar( menubar );
        file.add( quit_menu );
        
        edit.add( undo_menu );
        
        windows.add( pen_menu );
        windows.addSeparator();
        windows.add( direction_menu );
        windows.addSeparator();
        windows.add( controls_menu );
        windows.addSeparator();
        windows.add( statistics_menu );
        
        options.add( vectors_menu );
        options.add( display_menu );
        
        pen_menu.add( v_menu );
        pen_menu.addSeparator();
        pen_menu.add( a_menu );
        
        direction_menu.add( d_menu );
        direction_menu.addSeparator();
        direction_menu.add( dd_dt_menu );
        
        menubar.add( file    );
        menubar.add( edit    );
        menubar.add( windows );
        menubar.add( options );

        addKeyListener( this );
		addMouseListener( this );
		setRecognitionMode( true );		        
		addMouseMotionListener( this );
        v_menu.addActionListener( this );
        a_menu.addActionListener( this );
        d_menu.addActionListener( this );
        dd_dt_menu.addActionListener( this );
        controls_menu.addActionListener( this );
        statistics_menu.addActionListener( this );
        quit_menu.addActionListener( this );
        undo_menu.addActionListener( this );
        vectors_menu.addActionListener( this );
        display_menu.addActionListener( this );

        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Image   image   = toolkit.getImage( cursor_image_name );
        Cursor  cursor  = toolkit.createCustomCursor( 
            image,
            new Point( 0, 0 ),
            "pencil" );
        setCursor( cursor );
    }


        
    /**
    *
    * Set debugging mode
    *
    **/
    public void
    setDebugging( boolean d )
    {
        debug = d;
    }
        
    /**
    *
    * Overloaded for double buffering.
    *
    **/
    public void
    paint( Graphics g )
    {
        update( g );
    }
    
    /**
    *
    * Handle key stroke events
    *
    **/
    public void
    keyPressed( KeyEvent k )
    {
    }
     
    /**
    *
    * Handle key stroke events
    *
    **/
    public void
    keyReleased( KeyEvent k )
    {    
    }
     
    /**
    *
    * Handle key stroke events
    *
    **/
    public void
    keyTyped( KeyEvent k )
    {
        char key = k.getKeyChar();
        
        if ( key == 'c' ) {
            repaint();
            return;
        }
        
        if ( key == 'd' ) {
            debug2 = !debug2;
            return;
        }
        
        if ( key == 'r' ) {
            object_manager.reset();
            clearScreenStrong();
            return;
        }
        
        if ( key == 't' ) {
            show_parsed_objects = !show_parsed_objects;
            object_manager.show_parsed_objects = show_parsed_objects;
            clearScreen();
            object_manager.paint( getGraphics() );
            System.out.println( "Show parsed objects set " + 
                                 show_parsed_objects );
        }
        
        if ( key == 'h' ) {
            debug_graphics.setColor( Color.red.darker().darker() );
            for ( int i=0; i<1; i++ ) {
                Line l1 = new Line( (int)(Math.random()*300),
                                    (int)(Math.random()*300),
                                    (int)(Math.random()*300),
                                    (int)(Math.random()*300) );
                Line l2 = new Line( (int)(Math.random()*300),
                                    (int)(Math.random()*300),
                                    (int)(Math.random()*300),
                                    (int)(Math.random()*300) );
                l1.paint( debug_graphics );
                l2.paint( debug_graphics );
                try {
                    Point p = l1.getIntersection( l2 );
                    p.paint( debug_graphics );
                } catch ( Exception e ) {
                    // Lines don't intersect.
                }
            }
            return;
        }
        
        if ( key == 'u' ) {
            Rectangle r = new Rectangle( 300, 350, 40, 80, 30 );
            for ( int i=0; i<18; i++ ) {
                debug_graphics.setColor( new Color( i ) );
                r.rotateAbout( new Point( 300, 350 ), Math.PI/9 );
                r.paint( debug_graphics );
            }
            return;
        }
        
        if ( key == 'l' ) {
            Rectangle r = new Rectangle( 300, 350, 40, 80, 30 );
            for ( int i=0; i<18; i++ ) {
                debug_graphics.setColor( new Color( i*10 ) );
                r.scaleAboutCenter( 1.1 );
                r.paint( debug_graphics );
            }
            return;
        }
        
        if ( key == 'i' ) {
            Rectangle r = new Rectangle( 300, 350, 40, 80, 30 );
            for ( int i=0; i<18; i++ ) {
                debug_graphics.setColor( new Color( i ) );
                r.rotateAbout( new Point( 320, 360 ), Math.PI/9 );
                r.paint( debug_graphics );
            }
            return;
        }
        
        if ( key == 'o' ) {
            Rectangle r = new Rectangle( 300, 350, 100, 160, 0 );
            debug_graphics.setColor( Color.red );
            r.paint( debug_graphics );
            r.rotateAbout( new Point( 340, 350 ), 0.0 );
            debug_graphics.setColor( Color.green );
            r.paint( debug_graphics );
            for ( int i=0; i<4; i++ ) {
                debug_graphics.setColor( new Color( i ) );
                r.rotateAbout( new Point( 340, 350 ), Math.PI/6 );
                r.paint( debug_graphics );
            }
            return;
        }
        
        if ( key == 'w' ) {
            bar_statistics_module.setVisible( 
                !bar_statistics_module.isVisible() );
                
            sliding_window_statistics_module.setVisible( 
                !sliding_window_statistics_module.isVisible() );
                
            System.out.println( "Statistics window." );
            return;
        }
        
        if ( key == 'z' ) {
            object_manager.undo();
            return;
        }
        
        if ( key == 'q' ) {
            System.exit( 0 );
            return;
        }
    }
     
    /**
    *
    * Handle action events
    *
    **/
    public void
    actionPerformed( ActionEvent e ) 
    {
        String command = e.getActionCommand();
        
        if ( command == "Quit" ) {
            System.exit( 0 );
        }
        
        if ( command == "Undo" ) {
            object_manager.undo();
        }
        
        if ( command == "Speed" ) {
            v_window.setVisible( !v_window.isVisible() );
            v_window_graphics = v_window.getGraphics();
            System.out.println( "Speed window." );
        } 
            
        if ( command == "Acceleration" ) {
            a_window.setVisible( !a_window.isVisible() );
            a_window_graphics = a_window.getGraphics();
            System.out.println( "Acceleration window." );
        }

        if ( command == "  \u0398" ) {
            d_window.setVisible( !d_window.isVisible() );
            d_window_graphics = d_window.getGraphics();
            System.out.println( "Direction window." );
        }

        if ( command == "d\u0398/dt" ) {
            dd_dt_window.setVisible( !dd_dt_window.isVisible() );
            dd_dt_window_graphics = dd_dt_window.getGraphics();
            System.out.println( "dd/dt window." );
        }
        
        if ( command == "Controls" ) {
            control_module.setVisible( 
                !control_module.isVisible() );
            System.out.println( "Controls module." );
        }
        
        if ( command == "Statistics" ) {
            bar_statistics_module.setVisible( 
                !bar_statistics_module.isVisible() );
                
            sliding_window_statistics_module.setVisible( 
                !sliding_window_statistics_module.isVisible() );
                
            System.out.println( "Statistics window." );
        }
        
        if ( command == "Display LSQ Vectors" ) {
            display_lsq_vectors = !display_lsq_vectors;
            System.out.println( "Display LSQ Vectors set " + 
                                 display_lsq_vectors );
        }
        
        if ( command == "Show parsed objects" ) {
            show_parsed_objects = !show_parsed_objects;
            object_manager.show_parsed_objects = show_parsed_objects;
            clearScreen();
            object_manager.paint( getGraphics() );
            System.out.println( "Show parsed objects set " + 
                                 show_parsed_objects );
        }
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseClicked( MouseEvent e ) 
    {
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseEntered( MouseEvent e ) 
    {
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseExited( MouseEvent e ) 
    {
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mousePressed( MouseEvent e ) 
    {
        reset();
		if ( in_recognition_mode ) {
	        addPoint( new Point(e.getPoint()), e.getWhen() );
		}
    }
    
    /**
    *
    * Reset the arrays
    *
    **/
    public void
    reset() 
    {
        last_index     = -1;
        output_polygon = null;
        lower_left     = null;
        upper_right    = null;
        center         = null;
        radius         = null;
        invalidateCaches();
    }
    
    /**
    *
    * Add a new point to points, and the corresponding time info 
    * to time. This method is solely for the purpose of making 
    * caching easier through abstraction. All changes to points[]
    * and time[] is to be done through this method.
    *
    **/
    public void
    addPoint( Point p, long t ) 
    {
		System.out.println( " Adding point " + last_index );
        last_index++;
        points[last_index] = p;
        time[last_index]   = t;
        invalidateCaches();
    }


    /**
    *
    * Disable or enable recognition process by turning mouse event 
	* listening off or on. Set flag to be true if enabling is desired.
    * Set flag to be false if disabling is desired.
	*
    **/
    public void
    setRecognitionMode( boolean flag )
    {
		in_recognition_mode = flag;
    }


    /**
    *
    * Invalidates caches 
    *
    **/
    public void
    invalidateCaches() 
    {
        v_is_valid            = false;
        a_is_valid            = false;
        d_is_valid            = false;
        dd_dt_is_valid        = false;
        polar_points_is_valid = false;
    }


    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseReleased( MouseEvent e ) 
    {
        boolean line_result;
        boolean circle_result;
        boolean polygon_result;
        
        double  circle_LSQ  = 0.0;
        double  polygon_LSQ = 0.0;
        
        Polygon original_points = getDataPoints();
        
        System.out.println( "\nRead " + last_index + " points in " +
                            ( e.getWhen() - time[0] ) + " milliseconds" );
        System.out.println( "This is " + ((1000*last_index) /
                            ( e.getWhen() - time[0] )) +
                            " points per second" );

        
        if ( (last_index >= points.length) || 
			 (last_index < 1)              || 
			 (!in_recognition_mode) )
            return;
            
        line_result    = test_line();
        if ( line_result ) {
            GeometricObject recognized_object = new Line( line[0], line[1] );
            recognized_object.setDataPoints( original_points );
            plot_output_line();
            recognized_object.setTimeStamp( e.getWhen() );
            object_manager.addObject( recognized_object ); 
            update( getGraphics() );
            reset();
            if (!debug) {
                object_manager.paint( getGraphics() );
            }

            return;
        }
        polygon_result = test_polygon();
        circle_result  = test_circle();
        
        
        combineEndPoints();
        simplifyPolygon();
        normalizePolygon();
        combineEndPoints();

        circle_LSQ  = leastSquaresForCircle();
        polygon_LSQ = leastSquaresForPolygon();
        
        System.out.println( "LSQE for circle : "  + circle_LSQ  );
        System.out.println( "LSQE for polygon : " + polygon_LSQ );

        if (circle_LSQ > polygon_LSQ ) {
            polygon_result = true;
            circle_result  = false;            
        } else {
            polygon_result = false;
            circle_result  = true;
        }
        
        if ( circle_result ) {
            GeometricObject recognized_object = new 
                Ellipse( center.x-radius.width, 
                         center.y-radius.height,
                         radius.width*2,
                         radius.height*2 );                
            recognized_object.setDataPoints( original_points );
            object_manager.addObject( recognized_object ); 
            recognized_object.setTimeStamp( e.getWhen() );
            plot_output_circle();
        }
        
        if ( polygon_result ) {
            GeometricObject recognized_object = new Polygon( output_polygon );
            recognized_object.setDataPoints( original_points );
            recognized_object.setTimeStamp( e.getWhen() );
            object_manager.addObject( recognized_object ); 
            plot_output_polygon();
        }

        if ( !debug ) {
            update( getGraphics() );
        }
        reset();
    }
    
    /**
    *
    * Handle mouse motion events
    *
    **/
    public void
    mouseDragged( MouseEvent e ) 
    {
        if ( ( last_index >= points.length ) || (!in_recognition_mode) )
            return;
        addPoint( new Point(e.getPoint()), e.getWhen() );
        if ( debug ) {
            System.out.print( "mouseDragged : ");
            System.out.println( last_index + "\t" +
                                (e.getWhen() - time[0])/1000.0 + "\t" + 
                                 points[last_index].x          + "\t" +
                                 points[last_index].y );
        }
        x_window_graphics  = this.getGraphics();
        if ( last_index == 0 ) {
            return;
        }
        x_window_graphics.setColor( Color.blue );
        x_window_graphics.drawLine( points[last_index-1].x,
                                    points[last_index-1].y,
                                    points[last_index].x,
                                    points[last_index].y );
    }

    /**
    *
    * Clear screen
    *
    **/
    public void
    clearScreen()
    {
        System.out.println( "Clearing the tablet" );
        if ( x_window_graphics == null ) {
            x_window_graphics = getGraphics();
            debug_graphics    = x_window_graphics;
            if ( x_window_graphics == null ) {
                return;
            } 
        } 
        
        if ( debug2 ) {
            return;
        }
        Color old_color = x_window_graphics.getColor();
        x_window_graphics.setColor( Color.white );
        x_window_graphics.fillRect( 0, 0, 2000, 2000 );
        x_window_graphics.setColor( old_color );
    }

    /**
    *
    * Clear screen strong (works in debug mode).
    *
    **/
    public void
    clearScreenStrong()
    {
        System.out.println( "Clearing the tablet" );
        if ( x_window_graphics == null ) {
            x_window_graphics = getGraphics();
            debug_graphics    = x_window_graphics;
            if ( x_window_graphics == null ) {
                return;
            } 
        } 
        
        Color old_color = x_window_graphics.getColor();
        x_window_graphics.setColor( Color.white );
        x_window_graphics.fillRect( 0, 0, 2000, 2000 );
        x_window_graphics.setColor( old_color );
    }
    
    /**
    *
    * Handle mouse motion events
    *
    **/
    public void
    mouseMoved( MouseEvent e ) 
    {
    }
    
    
    /**
    *
    * Handle double buffering and do the real drawing.
    *
    **/
    public void 
    update( Graphics g )
    {
        clearScreen();
        System.out.println("Updating");
        if ( (last_index > 5) && (last_index<points.length) ) {
            updateVelocity();
            updateAcceleration();
            update_d();
            update_dd_dt();
        }
        object_manager.paint(g);
    }

    /**
    *
    * Update the main graph.
    *
    **/
    public void
    updateMain()
    {
        x_window_graphics = this.getGraphics();

        if ( x_window_graphics == null )
            return;
        if ( !debug ) {
            clearScreen();
        }    
        
        // The real drawing code starts here.
        x_window_graphics.setColor(Color.blue);
        
        for ( int i=0; i<last_index-1; i++ ) {            
            x_window_graphics.drawLine(  points[i].x,
                            points[i].y,
                            points[i+1].x,
                            points[i+1].y );
        }
        
        if ( last_index == 1 ) {
            x_window_graphics.drawLine(  points[0].x,
                            points[0].y,
                            points[1].x,
                            points[1].y );
        }
    }
    
    /**
    *
    * Update the velocity graph.
    *
    **/
    public void
    updateVelocity()
    {
        int    x;
        int    y;
        int    x_old;
        int    y_old;
        int    window_height = v_window.getSize().height;
        double average       = 0.0;
        
        if (v_window_graphics == null)
            return;
            
        deriveSpeed();
        v_window_graphics.setColor( Color.white );
        v_window_graphics.fillRect( 0, 
                                    0, 
                                    off_dimension.width, 
                                    off_dimension.width );
        v_window_graphics.setColor( Color.black );
        v_window_graphics.drawLine( 0,   (int)window_height/2, 
                                    500, (int)window_height/2  );
        v_window_graphics.setColor( Color.red );
        
        x_old = 10;
        y_old = (int)(window_height/2 - speed[0]*v_scale );
        for ( int i=1; i<speed.length; i++ ) {
        
            x = (int)( 10 + (time[i]   - time[0])*t_scale );
            y = (int)( window_height/2 - speed[i]*v_scale);

            v_window_graphics.drawLine( x_old, y_old, x, y );

            x_old = x;
            y_old = y;
        }
        
        average = averageSpeed();
        v_window_graphics.drawLine( 0,   
                                    (int)(window_height/2 - 
                                          average*v_scale*scale),
                                    500, 
                                    (int)(window_height/2 - 
                                          average*v_scale*scale) );
    }

    /**
    *
    * Update the acceleration graph.
    *
    **/
    public void
    updateAcceleration()
    {
        int x;
        int y;
        int x_old;
        int y_old;
        
        double window_height = a_window.getSize().height;
        
        if ( a_window_graphics == null )
            return;
            
        deriveAcceleration();
        a_window_graphics.setColor( Color.white );
        a_window_graphics.fillRect( 0, 
                                    0, 
                                    off_dimension.width, 
                                    off_dimension.width );
        a_window_graphics.setColor( Color.black );
        a_window_graphics.drawLine( 0,   (int)window_height/2, 
                                    500, (int)window_height/2  );
        a_window_graphics.setColor( Color.green );
                                    
        x_old = 10;
        y_old = (int)(window_height/2 - acceleration[0]*a_scale);
        for ( int i=1; i<acceleration.length; i++ ) {
            x = (int)( 10 + (time[i] - time[0])*t_scale );
            y = (int)( window_height/2 - acceleration[i]*a_scale);
            
            a_window_graphics.drawLine( x_old, y_old, x, y );
            x_old = x;
            y_old = y;
        }
    }

    
    /**
    *
    * Update the direction graph.
    *
    **/
    public void
    update_d()
    {
        int x;
        int y;
        int x_old;
        int y_old;
        
        double window_height = d_window.getSize().height;
        
        if ( d_window_graphics == null )
            return;

        derive_d();

        d_window_graphics.setColor( Color.white );
        d_window_graphics.fillRect( 0, 
                                    0, 
                                    off_dimension.width, 
                                    off_dimension.width );
        d_window_graphics.setColor( Color.black );
        d_window_graphics.drawLine( 0,   (int)window_height/2, 
                                    500, (int)window_height/2  );
        d_window_graphics.setColor( Color.green );
                                    
        x_old = 10;
        y_old = (int)(window_height/2 - d[0]*theta_scale);
        for ( int i=1; i<d.length; i++ ) {
            x = (int)( 10 + (time[i] - time[0])*t_scale );
            y = (int)( window_height/2 - d[i]*theta_scale);
            d_window_graphics.drawLine( x_old, y_old, x, y );
            d_window_graphics.drawLine( 200,
                                        200, 
                                        200 + (int)(100*Math.cos(d[i])), 
                                        200 + (int)(100*Math.sin(d[i])));
            x_old = x;
            y_old = y;
        }
    }    


    /**
    *
    * Update change in direction
    *
    **/
    public void
    update_dd_dt()
    {
        int x;
        int y;
        int x_old;
        int y_old;
        
        double window_height = dd_dt_window.getSize().height;

        if ( dd_dt_window_graphics == null )
            return;
            
        derive_dd_dt();
        
        dd_dt_window_graphics.setColor( Color.white );
        dd_dt_window_graphics.fillRect( 0, 
                                        0, 
                                        off_dimension.width, 
                                        off_dimension.width );
        dd_dt_window_graphics.setColor( Color.black );
        dd_dt_window_graphics.drawLine( 0,   (int)window_height/2, 
                                        500, (int)window_height/2  );
        dd_dt_window_graphics.setColor( Color.blue );
                                    
        x_old = 10;
        y_old = (int)(window_height/2 - dd_dt[0]*d_theta_scale);
        for ( int i=1; i<dd_dt.length; i++ ) {
            x = (int)( 10 + (time[i] - time[0])*t_scale );
            y = (int)( window_height/2 - dd_dt[i]*d_theta_scale );
            dd_dt_window_graphics.drawLine( x_old, y_old, x, y );
            dd_dt_window_graphics.drawLine( 
                                        200,
                                        200, 
                                        200 + (int)(100*Math.cos(dd_dt[i])),
                                        200 + (int)(100*Math.sin(dd_dt[i])));
            x_old = x;
            y_old = y;
        }
    }    
    
    
    /**
    *
    * Try to get rid of some of the noise.
    *
    **/
    public void
    filter()
    {
        Point tmp[] = new Point[last_index];
        tmp[0] = points[0];
        for ( int i=1; i<last_index-2; i++ ) {
            tmp[i]    = new Point();
            tmp[i].x  = ( points[i-1].x + points[i].x + points[i+1].x )/3;
            tmp[i].y  = ( points[i-1].y + points[i].y + points[i+1].y )/3;
            
            x_window_graphics = this.getGraphics();
            if ( x_window_graphics != null ) {
                x_window_graphics.setColor( Color.green );
                x_window_graphics.drawLine( tmp[i-1].x,
                                            tmp[i-1].y,
                                            tmp[i].x,
                                            tmp[i].y );
            }
        }
        
        for ( int i=1; i<last_index-2; i++ ) {
            points[i] = tmp[i];
        }
    }
    
    
    /**
     *
     * Derive speed, and fill in the speed array.
     * This method should cache data in order to save computation.
     *
     **/
    public void
    deriveSpeed()
    {
        if ( v_is_valid )     // Don't do anything if the 
            return;           // cached value is valid
    
        double  dx;
        double  dy;
        double  dt;
        
        speed = new double[last_index]; 
        for ( int i=0; i<last_index; i++ ) { 
            dx = points[i+1].x - points[i].x;
            dy = points[i+1].y - points[i].y;
            dt = time[i+1] - time[i];
            speed[i] = Math.sqrt( dx*dx + dy*dy )/dt;
        }

        v_is_valid = true;
    }
    
    
    /**
     *
     * Put the valid points in the points array in a Polygon and
     * return it.
     *
     **/
    public Polygon
    getDataPoints()
    {
        Polygon result = new Polygon();
                
        for ( int i=0; i<last_index+1; i++ ) {
            result.addPoint( points[i].x, points[i].y );
        }
        
        return result;
    }
    
    
    /**
     *
     * Derive the polar coordinates of the points with respect to
     * the center. Also determine the center, upper_right and lower 
     * right points (for surrounding rectangle).
     *
     **/
    public void
    derivePolarCoordinates()
    {
        if ( polar_points_is_valid )     // Don't do anything if the 
            return;                        // cached value is valid
        
        int x;
        int y;
        
        deriveBoundingBox();
        polar_points = new PolarPoint[last_index+1];     
        
        

        for ( int i=0; i<polar_points.length; i++ ) {
            x = ( points[i].x - center.x );
            y = ( points[i].y - center.y );
            polar_points[i] = new PolarPoint( Math.sqrt( x*x + y*y ),
                                              Math.atan2( y, x ) );
        }
        
        for ( int i=1; i<polar_points.length; i++ ) {
            if ( Math.abs( polar_points[i].theta - 
                           polar_points[i-1].theta ) > Math.PI ) {
                for ( int j=-10; j<11; j++ ) {
                    if ( Math.abs( polar_points[i].theta - 
                                   polar_points[i-1].theta   + 
                                   j*2*Math.PI) < Math.PI ) {
                        polar_points[i].theta += j*2*Math.PI;
                        break;
                    }
                }
            }
        }
        
    if ( debug ) {
        for ( int i=0; i<polar_points.length; i++ ) {
        System.out.println( i + " " + polar_points[i] );
        }
    }

        polar_points_is_valid = true;
    }
  
    
    /**
     *
     * Derive the bounding box for thee points.
     * Determine the center of the points, upper_right and 
     * lower right points (for surrounding rectangle).
     *
     **/
    public void
    deriveBoundingBox()
    {
        double x = 0.0;
        double y = 0.0;
        double n = last_index;
        
        lower_left  = new Point( points[0] );
        upper_right = new Point( points[0] );
        
        for ( int i=0; i<last_index; i++ ) {
            if (points[i].x < lower_left.x)
                lower_left.x = points[i].x;
                
            if (points[i].y < lower_left.y)
                lower_left.y = points[i].y;
                
            if (points[i].x > upper_right.x)
                upper_right.x = points[i].x;
                
            if (points[i].y > upper_right.y)
                upper_right.y = points[i].y;
            x += ((double)points[i].x)/n;
            y += ((double)points[i].y)/n;
        }
        
        radius = new Dimension( (upper_right.x - lower_left.x)/2,
                                (upper_right.y - lower_left.y)/2 );
        
        center = new Point( (upper_right.x + lower_left.x)/2,
                            (upper_right.y + lower_left.y)/2 );
    }
    
    
    /**
     *
     * Derive acceleration, and fill in the acceleration array.
     *
     **/
    public void
    deriveAcceleration()
    {
        if ( a_is_valid )     // Don't do anything if the 
            return;           // cached value is valid

        double dt;
        
        deriveSpeed();
        acceleration = new double[speed.length-1];     
        
        for ( int i=0; i<acceleration.length; i++ ) {
            dt              = ( time[i+2] - time[i] )/2;
            acceleration[i] = (speed[i]-speed[i+1])/dt;
        }
        a_is_valid = true;
    }
    
    
    /**
     *
     * Derive direction, and fill in the d array.
     *
     **/
    public void
    derive_d()
    {
        if ( d_is_valid )     // Don't do anything if the 
            return;           // cached value is valid
        
        double dx;
        double dy;
        double theta;
                                    
        d = new double[last_index];
        for ( int i=0; i<d.length; i++ ) {
            dx = points[i+1].x - points[i].x;
            dy = points[i+1].y - points[i].y;
            
            theta = Math.atan2( dy, dx );
            d[i]  = theta;
        }
        for ( int i=1; i<d.length; i++ ) {
            if ( Math.abs( d[i] - d[i-1] ) > Math.PI ) {
                for ( int j=-10; j<11; j++ ) {
                    if ( Math.abs( d[i] - d[i-1] + j*2*Math.PI) < Math.PI ) {
                        d[i] += j*2*Math.PI;
                        break;
                    }
                }
            }
        }

        d_is_valid = true;
    }
    
    
    /**
     *
     * Derive time derivative of direction, 
     * and fill in the dd_dt array.
     *
     **/
    public void
    derive_dd_dt()
    {
        if ( dd_dt_is_valid )     // Don't do anything if the 
            return;               // cached value is valid
        
        double dt;
        double distance;
        double delta_theta;

        derive_d();                               
        dd_dt = new double[d.length-1];
        
        for ( int i=0; i<dd_dt.length; i++ ) {
            delta_theta = d[i+1] - d[i];
            dt          = ( time[i+2] - time[i])/2;
            dd_dt[i]    = delta_theta/dt;
        }
        
        dd_dt_is_valid = true;
    }
    
    /**
     *
     * Average speed
     *
     **/
    public double
    averageSpeed()
    {
        double  dx;
        double  dy;
        double  dt;
        double  average = 0.0;
    
        for ( int i=0; i<last_index-1; i++ ) {
            dx = points[i+1].x - points[i].x;
            dy = points[i+1].y - points[i].y;
            average += Math.sqrt( dx*dx + dy*dy );
        }
        average /= (time[last_index] - time[0]);
    
        return average;
    }
    
    /**
    * 
    * See if the input was meant to be a circle.
    * 
    **/
    public boolean
    test_circle()
    {
        double mean    = 0.0;
        double std_dev = 0.0;
                
        derive_dd_dt();
        for ( int i=0; i<dd_dt.length; i++ ) {
            mean += dd_dt[i];
        }
        mean /= dd_dt.length;
        
        for ( int i=0; i<dd_dt.length; i++ ) {
            std_dev += (dd_dt[i]-mean)*(dd_dt[i]-mean);
        }
        std_dev /= (dd_dt.length-1);
        
        System.out.println( "Mean    : " + mean );
        System.out.println( "Std dev : " + std_dev );
        
        if ( debug ) {
            for ( int i=0; i<dd_dt.length; i++ ) {
            System.out.println( "dd_dt[" + i + "]" + "\t" + dd_dt[i] );
        }
    }
        
        if ( std_dev < sd_scale ) {
            return true;
        } else {
            return false;
        }
    }

    /**
    *
    * Plot the recognized circle if one was recognized.
    *
    **/
    public void
    plot_output_circle() 
    {
        derivePolarCoordinates();
        
        if ( debug ) {
            System.out.println( "RADIUS = " + radius );
        }
        x_window_graphics = this.getGraphics();
        
        if ( null == x_window_graphics )
            return;
            
        x_window_graphics.setColor( Color.cyan );
        GraphicsUtil.drawThickOval( 1,
                                     x_window_graphics,
                                     lower_left.x,
                                     lower_left.y,
                                    (upper_right.x - lower_left.x),
                                    (upper_right.y - lower_left.y) );
    }


    /**
    *
    * See if the input is a polygon.
    *
    **/
    public boolean
    test_polygon()
    {
        int     edges = 0;
        double  dx;
        double  dy;
        double  dt;
        double  average = 0.0;
        
        deriveSpeed();
        average = averageSpeed();
                
        if ( speed[0] > average*scale ) {              // This is
            speed[0] = average*scale/2;                // not a good
        }                                              // solution
        if ( speed[speed.length-1] > average*scale ) { // fix it      
            speed[speed.length-1] = average*scale/2;   // properly
        }

        boolean below = (speed[0] < average*scale);

        for ( int i=0; i<speed.length; i++ ) { 
            if ( ( speed[i] > average*scale ) && ( below == true ) ) {
                below = false;
                edges++;
            }
            if ( ( speed[i] < average*scale ) && ( below == false ) ) {
                below = true;
                edges++;
            }
        }
        
        edges /= 2;
        System.out.println( edges + " edges detected." );

        if ( edges < 2 )
            return false;
            
        // Now lets find the the minimums of the v graph
        
        int    mins      = 0;
        int    indices[] = new int[edges+1];
        double min       = average*scale;
        below            = (speed[0] < average*scale);
        
        for ( int i=0; i<speed.length; i++ ) { 
            if ( ( speed[i] > average*scale ) && ( below == true ) ) {
                mins++;
                min   = average*scale;
                below = false;
            }
            if ( speed[i] < average*scale ) {
                if ( speed[i] < min ) {
                    min           = speed[i];
                    indices[mins] = i;
                }
                below = true;
            }
        }

        output_polygon = new Point[edges+1];
        
        for ( int i=0; i<=edges; i++ ) {
            output_polygon[i] = new Point( points[ indices[i] ] ); 
        }
        
        if ( debug ) {                                                           
            System.out.println( "Final output polygon <<<<<<<" ); 
            for ( int i=0; i<output_polygon.length; i++ ) {
                System.out.println( i + " " + output_polygon[i] );
            }
            System.out.println( "Final output polygon >>>>>>>" ); 
        }
        
        return true;        
    }
    
    /**
    *
    * See if the input is a line.
    *
    **/
    public boolean
    test_line()
    {
        double  dx;
        double  dy;
        double  length   = 0.0;
        double  distance = 0.0;
        boolean result;
        
        for ( int i=0; i<last_index-1; i++ ) {
            dx = points[i+1].x - points[i].x;
            dy = points[i+1].y - points[i].y;

            length += Math.sqrt( dx*dx + dy*dy );
        }
        
        distance = Math.sqrt( (points[0].x - points[last_index].x) *
                              (points[0].x - points[last_index].x) +
                              (points[0].y - points[last_index].y) *
                              (points[0].y - points[last_index].y) );
        result = length < distance*1.1;                      
        
        if ( result ) {
            System.out.println("That was definitely a line");
            line    = new Point[2];
            line[0] = new Point( points[0] );
            line[1] = new Point( points[last_index] );            
        } else {
            System.out.println("Doesn't look like a line");
        }
        
        return result;
    }
    
    /**
    * 
    * Modify the polygon, so that the lines appear as the user wanted
    * them to be (ie, get rid of the small rotations and shifts.
    * (For example usually lines which look horizontal were meant
    * to be perfectly horizontal.
    *
    **/
    public void
    normalizePolygon()
    {
        double theta          = 0.0;
        double old_theta      = 0.0;
        int    tmp_last_index = last_index;
        
        if ( (output_polygon == null) || (output_polygon.length < 2) ) {
            System.err.println("Error: normalizePolygon no polygon detected.");
            return;
        }
        
        int    indices[]   = getPolygonIndices();
        Point tmp_points[] = new Point[points.length];
        
        for ( int i=0; i<points.length; i++ ) {
            tmp_points[i] = new Point( points[i] );
        }
    
        points = new Point[output_polygon.length];
        for ( int i=0; i<output_polygon.length; i++ ) {
            points[i] = new Point( output_polygon[i] );
        }        
        last_index = points.length-1;
        invalidateCaches();
        derive_d();
        derive_dd_dt();

        if ( debug ) {
            System.out.println( "THE INPUT" );
            for ( int i=0; i<output_polygon.length; i++ ) {
                System.out.println( output_polygon[i] );
            }
        }

        double distance = 0.0;        
        for ( int i=0; i<d.length; i++ ) {
            for ( int j=-40; j<40; j++ ) {
                 if ( (d[i]-step_angle*j <  orientation_angle) &&
                      (d[i]-step_angle*j > -orientation_angle) ) {
                    distance = Math.sqrt( (output_polygon[ i ].x  - 
                                           output_polygon[i+1].x) *
                                          (output_polygon[ i ].x  -
                                           output_polygon[i+1].x) +
                                          (output_polygon[ i ].y  -
                                           output_polygon[i+1].y) *
                                          (output_polygon[ i ].y  -
                                           output_polygon[i+1].y) );
                                           
                                           
                    
                     output_polygon[i+1].x = (int)
                        (output_polygon[i].x+Math.cos(step_angle*j)*distance);
                     output_polygon[i+1].y = (int)
                        (output_polygon[i].y+Math.sin(step_angle*j)*distance);
                     if ( debug )
                     System.out.println("DOING THE IF " + i + " " + j  + 
                                       " dx " + (int)(output_polygon[i+1].x-tmp_points[indices[i+1]].x) +
                                       " dy " + (int)(output_polygon[i+1].y-tmp_points[indices[i+1]].y) );
                     tmp_points[indices[i+1]].x =  output_polygon[i+1].x;
                     tmp_points[indices[i+1]].y =  output_polygon[i+1].y;
                     System.out.println("Changed to " + output_polygon[i+1] );
                      invalidateCaches();
                      derive_d();
                      derive_dd_dt();
                    break;
                 }
            }
        }       
        if ( debug ) {
            System.out.println( "THE OUTPUT" );
            for ( int i=0; i<output_polygon.length; i++ ) {
                System.out.println( output_polygon[i] );
            }
        }
         
        points = new Point[tmp_points.length];
        for ( int i=0; i<points.length; i++ ) {
            points[i] = new Point( tmp_points[i] );
        }
        
        last_index = tmp_last_index;
        invalidateCaches();
        derive_d();
        derive_dd_dt();
        deriveSpeed();
        deriveAcceleration();
        derivePolarCoordinates();
    }
   
   
    /**
    * 
    * Combines the ends of the polygon if they are sufficiently close.
    * 
    **/
    public void
    combineEndPoints()
    {
        if ( (output_polygon == null) || (output_polygon.length < 2) ) {
            System.err.println("Error: combineEndPoints no polygon detected.");
            return;
        }
        
        int l = output_polygon.length-1;            
        
        if ( ( (output_polygon[l].x - output_polygon[0].x) *
               (output_polygon[l].x - output_polygon[0].x) +
               (output_polygon[l].y - output_polygon[0].y) *
               (output_polygon[l].y - output_polygon[0].y) ) < 
               combine_treshold ) {
            
            output_polygon[0].x  = output_polygon[l].x;  
            output_polygon[0].y  = output_polygon[l].y;  
            points[last_index].x = output_polygon[l].x;
            points[last_index].y = output_polygon[l].y;
            points[0].x          = output_polygon[l].x;
            points[0].y          = output_polygon[l].y;
            System.out.println( "COMBINED ENDS OF THE POLYGON" );
        }
        invalidateCaches();
        derive_d();
        derive_dd_dt();
        deriveSpeed();
        deriveAcceleration();
        derivePolarCoordinates();
    }

    /**
    * 
    * Simplify the polygon to get rid of consecutive lines which are
    * sufficiently parallel.
    *
    **/
    public void
    simplifyPolygon()
    {
        int    tmp_last_index = last_index;
        double theta     = 0.0;
        double old_theta = 0.0;
        
        if ( (output_polygon == null) || (output_polygon.length < 2) ) {
            System.err.println("Error: simplifyPolygon no polygon detected.");
            return;
        }
        
        Point tmp_points[] = new Point[points.length];
        
        for ( int i=0; i<points.length; i++ ) {
            tmp_points[i] = new Point( points[i] );
        }
        

        points = new Point[output_polygon.length];
        for ( int i=0; i<output_polygon.length; i++ ) {
            points[i] = new Point( output_polygon[i] );
        }        
        last_index = points.length-1;
        invalidateCaches();
        derive_d();
        derive_dd_dt();
        
        boolean   include[] = new boolean[d.length+1];
        
        if ( debug2 ) {
            System.out.println( "Starting simplification" );
            System.out.println( "# of points = " + points.length );
            System.out.println( "d.length = "    + d.length );
        }

        include[0] = true;
        old_theta = d[0];
        int deleted_points = 0;
        for ( int i=1; i<d.length; i++ ) {
            if ( Math.abs( old_theta - d[i] ) < turn_angle_treshold ) {
                if ( debug2 ) {
                    System.out.println( "Deleting point " + i );
                }
                deleted_points++;
                include[i] = false;
            } else {
                include[i] = true;
            }
            old_theta = d[i];
        }
        include[include.length-1] = true;
        System.out.println( "Simplification over" );
        System.out.println( "Deleted points = " +  deleted_points );

        Point new_polygon[] = new Point[include.length - deleted_points];
        System.out.println( "Created new polygon with " + 
                             new_polygon.length + " points");
        int index = 0;
        for ( int i=0; i<include.length; i++ ) {
            if ( include[i] ) {
                if ( debug2 ) {
                    System.out.println( "Included point " + i +
                                        " " + output_polygon[i] );
                }
                new_polygon[index] = new Point( output_polygon[i] );
                index++;
            } else {
                if ( debug2 ) {
                    System.out.println( "Excluded point " + i );
                }
            }
        }
        
        output_polygon = new Point[new_polygon.length];
        for ( int i=0; i<output_polygon.length; i++ ) {
            if ( debug2 ) {
                System.out.println( "Copying " + new_polygon[i] );
            }
            output_polygon[i] = new Point( new_polygon[i] );
        }
        
        points = new Point[tmp_points.length];
        for ( int i=0; i<points.length; i++ ) {
            points[i] = new Point( tmp_points[i] );
        }
        
        last_index = tmp_last_index;
        invalidateCaches();
        derive_d();
        derive_dd_dt();
        deriveSpeed();
        deriveAcceleration();
        derivePolarCoordinates();
    }
    
    /**
    *
    * Plot the recognized line if one was recognized.
    *
    **/
    public void
    plot_output_line()
    {
        x_window_graphics = this.getGraphics();
        x_window_graphics.setColor( Color.green );
        GraphicsUtil.drawThickLine( 1,
                                     x_window_graphics,
                                     points[0].x,
                                     points[0].y,
                                     points[last_index].x,
                                     points[last_index].y );
    }    
    
    /**
    *
    * Plot the recognized polygon if one was recognized.
    *
    **/
    public void
    plot_output_polygon() 
    {
        int l = output_polygon.length-1;            
             
        Color colors[] = new Color[4];
        colors[0] = Color.red;
        colors[1] = Color.green;
        colors[2] = Color.black;
        colors[3] = Color.cyan;

        x_window_graphics = this.getGraphics();
        x_window_graphics.setColor( Color.green );
        for ( int i=0; i<output_polygon.length-1; i++ ) {
            x_window_graphics.setColor( colors[i%4] );
            GraphicsUtil.drawThickLine( 1,
                                         x_window_graphics,
                                         output_polygon[i].x,
                                         output_polygon[i].y,
                                         output_polygon[i+1].x,
                                         output_polygon[i+1].y );
        }
    }
    
    
    /**
    *
    * calculate the lsq error.
    *
    **/
    public int[]
    getPolygonIndices()
    {
        if ( (output_polygon == null) || (output_polygon.length<2) ) {
            System.err.println("Error: getPolgonIndices no polygon detected.");
        }
        
        if ( 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 ( debug ) {
                    System.out.println( "# " + i + " " + index +
                                        " " + output_polygon[index] + 
                                        " " + points[indices[index]] );
                }
                index++;
            }
        }
        
        if ( 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;
    }
    
    
    /**
    *
    * calculate the lsq error.
    *
    **/
    public double
    leastSquaresForPolygon()
    {
        int    index    = 0;
        double ls_error = 0.0;
        
        if ( (output_polygon == null) || (output_polygon.length < 2) ) {
            System.err.println("no polygon detected for LSQ");
            return Double.MAX_VALUE;
        }
            
        Point p1 = new Point( output_polygon[index] );
        Point p2 = new Point( output_polygon[index+1] );
        Point p  = new Point( points[0] );
        
        int indices[] = getPolygonIndices();

        int segments = 0;

        for ( int i=0; i<indices.length-1; i++ ) {
            p1.x = output_polygon[i].x;
            p1.y = output_polygon[i].y;
            p2.x = output_polygon[i+1].x;
            p2.y = output_polygon[i+1].y;
            
            for ( int j=indices[i]; j<indices[i+1]; j++ ) {
                p.x = points[index].x;
                p.y = points[index].y;
                ls_error += GeometryUtil.ptSegDistSq( p1, p2, p );
                index++;
                segments++;
            }
        }
        System.out.println( "LSQ Polygon: last_index = " + last_index );
        System.out.println( "LSQ Polygon: segments = "   + segments );


        return (ls_error/last_index);
    }
    
    /**
    *
    * calculate the lsq error.
    *
    **/
    public double
    leastSquaresForCircle() 
    {
        derivePolarCoordinates();
        double rotation_angle = 0.0;
        Point  p              = new Point();

        
        double a = radius.width;
        double b = radius.height;
        if ( a < b ) {
            double tmp = a;
            a = b;
            b = tmp;
            rotation_angle = Math.PI/2;
        }
        
        // Now a > b
        double c     = Math.sqrt( a*a-b*b );
        double e     = Math.sqrt( 1 - (b*b)/(a*a) );; // Eccentricity
        double r     = 0;
        double theta = 0;
                       
        System.out.println( "a " + a );
        System.out.println( "b " + b );
        System.out.println( "c " + c );
        System.out.println( "e " + e );
        
        x_window_graphics = this.getGraphics();
        double ls_error   = 0.0;                         
        if ( x_window_graphics != null ) {
            x_window_graphics.setColor( Color.green );
        
            for ( int i=0; i<polar_points.length; i++ ) {
                theta = polar_points[i].theta + rotation_angle;
                r = a*Math.sqrt( (1-e*e) /
                                 (1-e*e*Math.cos(theta)*Math.cos(theta)) );
                p.x = center.x + (int)(r*Math.cos(theta - rotation_angle));
                p.y = center.y + (int)(r*Math.sin(theta - rotation_angle));
                
                if ( display_lsq_vectors ) {
                    sleep( 10000 );
                    x_window_graphics.drawLine( points[i].x, points[i].y, 
                                                p.x, p.y );
                }
                
                ls_error += ( ( points[i].x - p.x )*( points[i].x - p.x ) +
                              ( points[i].y - p.y )*( points[i].y - p.y ) );
            }
        }
        
        x_window_graphics.setColor( Color.blue );
        x_window_graphics.fillOval( center.x, center.y, 4, 4 );
        x_window_graphics.drawRect( center.x-radius.width, 
                                    center.y-radius.height,
                                    radius.width*2,
                                    radius.height*2 );
        
        System.out.println( "d.length            = " + d.length );
        System.out.println( "polar_points.length = " + polar_points.length );
        
        return (ls_error/polar_points.length);
    }
    

    /**
    *
    * Do useless work for some number of iterations
    * specified by the argument.
    *
    **/
    public void
    sleep( int iterations ) 
    {
        java.util.Vector vector = new java.util.Vector();

        double sum = 0.0;
        for ( int i=0; i<iterations; i++ ) {
            sum +=i;
            vector.addElement( new Integer(i) );
        }
    }
}

/**
  *
  * $Log: Tablet.java,v $
  * Revision 1.16  2000/06/02 21:11:16  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.15  2000/05/26 20:43:35  mtsezgin
  *
  * Modified to make use of the ControlsModule that lets the user play
  * with parameters used in the recognition process.
  *
  * Revision 1.13  2000/05/22 02:42:34  mtsezgin
  *
  * The current version enables polygons to be sketched in pieces.
  *
  * Revision 1.12  2000/05/07 17:27:58  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.11  2000/04/28 04:45:05  mtsezgin
  *
  * Now each GeometricObject keeps the mouse input that was previously
  * discarded. User can switch between seeing the recognized mode and
  * the raw mode. setDataPoints( Polygon points ) and getDataPoints()
  * are added to GeometricObject, and all the implementors are modified
  * accordingly.
  *
  * Revision 1.10  2000/04/20 04:29:51  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.8  2000/04/17 07:02:32  mtsezgin
  *
  * Finally made the Rectangle really rotatable.
  *
  * Revision 1.7  2000/04/13 06:24:09  mtsezgin
  *
  * The current version of the program recognized Crosses, and Shades.
  * Implementors of Terminal and their descendants were modified to
  * implement the changes in GeometricObject.
  *
  * Revision 1.6  2000/04/12 04:00:17  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.5  2000/04/11 00:41:48  mtsezgin
  *
  * Now the whole package succesfully parses a motor.
  *
  * Revision 1.4  2000/04/06 19:16:24  mtsezgin
  *
  * Modified all the classes to use my Point class which extends java.awt.Point
  * instead of directly using java.awt.Point
  *
  * Revision 1.3  2000/04/01 20:34:04  mtsezgin
  *
  * Renamed Oval.java to Ellipse.java
  *
  * Revision 1.2  2000/04/01 04:11:32  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.1.1.1  2000/04/01 03:07:07  mtsezgin
  * Imported sources
  *
  * Revision 1.3  2000/03/31 22:41:05  mtsezgin
  *
  * Started Log tracking.
  *
  * Revision 1.2  2000/03/31 22:30:31  mtsezgin
  *
  *
  * Starting the log management.
  *
  *
  **/
