////////////////////////////////////////////////////////////////////////////////
//																			  //
// 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/08 03:18:32 $   
  * $Revision: 1.14 $
  * $Headers$
  * $Id: ObjectManager.java,v 1.14 2000/06/08 03:18:32 mtsezgin Exp $     
  * $Name:  $   
  * $Locker:  $
  * $Source: /mit/mtsezgin/CVS/Repository/REC/ObjectManager.java,v $
  *  
  **/


import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Graphics;

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;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import java.util.Enumeration;
import java.util.Vector;

/**
  *
  * This class manages the objects recognized by the Tablet.
  * Tablet adds the recognized using the addObject method.
  * The objects are kept in a Vector.
  *
  **/

class      ObjectManager
implements MouseMotionListener,
           MouseListener,
           KeyListener
{
    ParseSupervisor     parse_supervisor;
    SpatialParser       spatial_parser;
    Object              selected_objects[];
    Vector              objects;
    Filter              filter;
    TabletDataProcessor tablet;
    Point               last_position;

    String          current_file_name      = "Untitled";
    String          current_file_directory = null;

	public static final int RECOGNITION_MODE = 0;
	public static final int SELECTION_MODE   = 1;


	public int mode = RECOGNITION_MODE;

    /**
    *
    * This is true if the parsed stuff is to be painted.
    * Otherwise the original data points are displayed.
    *
    **/    
    boolean show_parsed_objects = false;


    /**
    *
    * The constructor.
    *
    **/    
    ObjectManager( TabletDataProcessor tablet )
    {
        this.tablet      = tablet;
        spatial_parser   = new SpatialParser();
        parse_supervisor = new ParseSupervisor( this );
        objects          = new Vector( 10, 0 );
        filter           = new Filter( this );
    }


    /**
    *
    * Adds an object to the objects vector after filtering.
    *
    **/
    public void
    addObject( Object o )
    {
		if ( mode == RECOGNITION_MODE ) {
	        Vector filtered_output = filter.filter( o );
    	    for ( int i=0; i<filtered_output.size(); i++ ) {
        	    addObjectInternal( filtered_output.elementAt(i) );
        	}
        	paint( Tablet.debug_graphics );
		}
		
		if ( mode == SELECTION_MODE ) {
			if ( !( o instanceof Line ) ) {
				selectInside( ((GeometricObject)o) );
			}
		}
    }


    /**
    *
    * Adds given objects to the objects vector after filtering.
    *
    **/
    public void
    addObjects( Vector input_objects )
    {
        for ( int i=0; i<input_objects.size(); i++ ) {
            addObjectInternal( input_objects.elementAt(i) );
        }
    }


    /**
    *
    * Selects all the objects inside the given object.
    *
    **/
    public void
    selectInside( GeometricObject object )
    {
		Vector objects_inside = new Vector();
		
		for ( int i=0; i<objects.size(); i++ ) {
			if ( objects.elementAt( i ) instanceof GeometricObject ) {
				if ( ((GeometricObject)object).containsGeometricObject( 
					((GeometricObject)objects.elementAt( i ) ) ) ) {
					objects_inside.addElement( objects.elementAt( i ) );
				}
			}
		}
		
		selectObjects( objects_inside );
		
		System.out.println( "selectInside called" );
    }


    /**
    *
    * Removes the argument object from the objects Vector.
    *
    **/
    public boolean
    removeObject( Object object )
    {
        return objects.removeElement( object );
    }


    /**
    *
    * Adds an object to the objects vector.
    *
    **/
    private void
    addObjectInternal( Object o )
    {
        Paintable p = (Paintable)o;
        System.out.println( "addObject " + ((Paintable)o).getType() );
        
        if ( p instanceof Polygon ) {
            if ( Rectangle.isRectangle( (Polygon)o ) ) {
                Rectangle rectangle = ((Polygon)o).getRectangularBounds();
                rectangle.setDataPoints( ((Polygon)o).getDataPoints() );
                addObject( rectangle ); 
                return;
            }
            if ( Line.isLine( ((Polygon)o) ) ) {
                addObject( ((Polygon)o).getEdge(0)  ); 
                return;
            }
            if ( Spring.isSpring( ((Polygon)o) ) ) {
                objects.addElement( new Spring( ((Polygon)o) ) );
                return;
            }
        }
        
        if ( tryCombining( p ) ) {
            System.out.println( "Combined two Polygons" );
            return;
        }
        
        normalizeObject( ((Object)p) );
        
        objects.addElement( o );
        System.out.println( "Added " + o );
    }


    /**
    *
    * Look at the statistical information and modify the polygons
    * to look like as they were intended to be.
    *
    **/
    private void
    normalizeObject( Object o )
    {
        if ( Tablet.debug ) {
            System.out.println( "Normalize Object" );
        }
        if ( o instanceof Polygon ) {
            Polygon polygon     = ((Polygon)o);
            double  angle_set[] = polygon.getMajorAngles();
            if ( Tablet.debug ) {
                for ( int i=0; i<angle_set.length; i++ ) {
                    System.out.println( "Angle " + i + " = " + 
                                         angle_set[i]*180/Math.PI );
                }
            }
            
            angle_set = GeometryUtil.roundAngles( angle_set, Math.PI/18 );

            double tmp_angle_set[] = new double[angle_set.length*3];
            for ( int i=0; i<angle_set.length; i++ ) {
                tmp_angle_set[i                   ] = angle_set[i];
                tmp_angle_set[i+angle_set.length  ] = angle_set[i] + Math.PI;
                tmp_angle_set[i+angle_set.length*2] = angle_set[i] - Math.PI;
            }
            angle_set = tmp_angle_set;

            if ( Tablet.debug ) {
                for ( int i=0; i<angle_set.length; i++ ) {
                    System.out.println( "Angle " + i + " = " + 
                                         angle_set[i]*180/Math.PI );
                }
            }
            
            polygon.normalize( angle_set );
        }
    }


    /**
    *
    * Try combining the input object with whatever is present in 
    * the objects vector.
    *
    **/
    private boolean
    tryCombining( Object o )
    {
        Paintable p = (Paintable)o;
        
        if ( ( p instanceof Polygon ) || ( p instanceof Line ) ) {
            for ( int i=0; i<objects.size(); i++ ) {
                if ( objects.elementAt( i ) instanceof Polygon ) {
                    if ( ((Polygon)objects.elementAt(i)).tryCombining(
                        (Object)p, 20) ) {
                        normalizeObject( objects.elementAt(i) );
                        return true;
                    }
                }
            }
        }
        
        return false;
    }
    
    
    /**
    *
    * Draw the objects
    *
    **/
    public void
    paint( Graphics g )
    {
        Paintable object;
        clear();
        g.setColor( Color.black );
        
        for ( Enumeration e = objects.elements(); e.hasMoreElements(); ) {
            object = ((Paintable)e.nextElement());
            object.setGraphicsContext( g );
            if ( show_parsed_objects ) {
                object.paint( g );
            } else {
                object.paintOriginal( g );
            }
        }
        
        if ( selected_objects != null ) {
            g.setColor( Color.green );
            for ( int i=0; i<selected_objects.length; i++ ) {
				if ( selected_objects[i] instanceof Paintable ) {
	                if ( show_parsed_objects ) {
                    	((Paintable)selected_objects[i]).paint( g );
                	} else {
                    	((Paintable)selected_objects[i]).paintOriginal( g );
					}
                }
            }
        }
    }
    

    /**
    *
    * Clear the screen
    *
    **/    
    public void
    clear()
    {
		tablet.clearScreen();
    }
    
    
    /**
    *
    * Removes all the objects.
    *
    **/    
    public void
    reset()
    {
        objects.removeAllElements();
    }
    
    
    /**
    *
    * A very primitive undo
    *
    **/    
    public void
    undo()
    {
        objects.remove( objects.size() - 1 );
        paint( Tablet.debug_graphics );
    }
    
    
    /**
    *
    * Returns a vector of all the geometric objects "sufficiently 
    * close to the point passes, determined by the search radius r.
    * For the moment, the only Terminals are searched.
    *
    **/    
    public Vector
    getTranslatableNeighborObjects( Point p, int r )
    {
        Object       object;
        Translatable translatable;
        Vector       translatable_neighbors = new Vector();
        
        for ( Enumeration e = objects.elements(); e.hasMoreElements(); ) {
            object = e.nextElement();
            if ( object instanceof Translatable ) {
                   translatable = ((Translatable)object);
                if ( translatable.pointIsOn( p, r ) ) {
                    translatable_neighbors.addElement( translatable );
                }
            }
        }
        
        return translatable_neighbors;
    }
    
    
    /**
    *
    * Returns a vector of all the geometric objects "sufficiently 
    * close to the point passes, determined by the search radius r.
    * For the moment, the only Terminals are searched. This method
    * looks at the original data points.
    *
    **/    
    public Vector
    getTranslatableNeighborObjectsOriginal( Point p, int r )
    {
        Object       object;
        Translatable translatable;
        Vector       translatable_neighbors = new Vector();
        
        for ( Enumeration e = objects.elements(); e.hasMoreElements(); ) {
            object = e.nextElement();
            if ( object instanceof Translatable ) {
                   translatable = ((Translatable)object);
                if ( translatable.pointIsOnOriginal( p, r ) ) {
                    translatable_neighbors.addElement( translatable );
                }
            }
        }
        
        return translatable_neighbors;
    }
    
    
    /**
    *
    * Returns a vector of all the geometric objects "sufficiently 
    * close to the point passes, determined by the search radius r.
    * For the moment, the only Terminals are searched.
    *
    **/    
    public Vector
    getNeighborObjects( Point p, int r )
    {
        Object          object;
        GeometricObject geometric_object;
        Vector          neighbors = new Vector();
        
        for ( Enumeration e = objects.elements(); e.hasMoreElements(); ) {
            object = e.nextElement();
            if ( object instanceof GeometricObject ) {
                   geometric_object = ((GeometricObject)object);
                if ( geometric_object.pointIsOn( p, r ) ) {
                    neighbors.addElement( geometric_object );
                }
            }
        }
        
        return neighbors;
    }
    
    
    /**
    *
    * Returns a vector of all the geometric objects "sufficiently 
    * close to the point passes, determined by the search radius r.
    * For the moment, the only Terminals are searched. This method
    * looks at the original data points.
    *
    **/    
    public Vector
    getNeighborObjectsOriginal( Point p, int r )
    {
        Object          object;
        GeometricObject geometric_object;
        Vector          neighbors = new Vector();
        
        for ( Enumeration e = objects.elements(); e.hasMoreElements(); ) {
            object = e.nextElement();
            if ( object instanceof GeometricObject ) {
                   geometric_object = ((GeometricObject)object);
                if ( geometric_object.pointIsOnOriginal( p, r ) ) {
                    neighbors.addElement( geometric_object );
                }
            }
        }
        
        return neighbors;
    }

        
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseClicked( MouseEvent e ) 
    {
        int    index;
        Object object;
        
        Vector neighbors;
        Point  point = new Point( e.getPoint() );

		
        if ( e.getClickCount() == 1 ) {
			switch ( mode ) 
			{
				case SELECTION_MODE :
					 changeModeTo( RECOGNITION_MODE );
				 	 break;
				case RECOGNITION_MODE :
					 changeModeTo( SELECTION_MODE );
					 break;
				default : 
					 System.err.println( "No matching mode.." );
					 break;
			}
        }

        if ( show_parsed_objects ) {
            neighbors = getTranslatableNeighborObjects( point, 5 );
        } else {
            neighbors = getTranslatableNeighborObjectsOriginal( point, 5 );
        }
        
        
        System.out.print( "Clicked on ");
        if ( e.getClickCount() == 2 ) {
            objects.removeAll( neighbors );
            paint( Tablet.debug_graphics );
            return;
        }

        for ( int i=0; i<neighbors.size(); i++ ) {
            
            object = neighbors.elementAt(i);
            System.out.println( object );
			
            if ( object instanceof Polygon ) {
                Polygon polygon     = ((Polygon)object);
                double  angles[]    = polygon.getAbsolutePositiveAngles();    
                double  window_size = Tablet.window_width;
                Tablet.bar_statistics_module.plotBarGraph( angles, 
                                                           0, 
                                                           Math.PI+window_size, 
                                                           window_size );

                double wrapped_angles[] = new double[angles.length*2];

                int j = 0;
                for ( j = 0;             j<angles.length;         j++ )
                    wrapped_angles[j] = angles[j];
                for ( j = angles.length; j<wrapped_angles.length; j++ )
                    wrapped_angles[j] = angles[j%angles.length] + Math.PI;

                System.out.print( "window_size " + window_size );
                Tablet.sliding_window_statistics_module.plotSlidingWindowGraph( 
                    angles, 
                    0,
                    Math.PI, 
                    Math.PI/180,
                    window_size );
            }
        }
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseEntered( MouseEvent e ) 
    {
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseExited( MouseEvent e ) 
    {
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mousePressed( MouseEvent e ) 
    {
        Point  point     = new Point( e.getPoint() );
        Vector neighbors;
        
        last_position = new Point( e.getPoint() );
    }
    
    /**
    *
    * Handle mouse events
    *
    **/
    public void
    mouseReleased( MouseEvent e ) 
    {
        if ( ( selected_objects != null    ) && 
             ( selected_objects.length > 0 ) ) {
			changeModeTo( SELECTION_MODE );
        }
        deselectObjects();
        paint( Tablet.debug_graphics );        
    }    
    
    /**
    *
    * 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 == 'p' ) {
            parse();
            return;
        }

        if ( key == 's' ) {
            LoadStoreModule.storeInFile( "objects", objects );
            return;
        }
        
        if ( key == 'l' ) {
            addObjects( LoadStoreModule.loadFromFile( "objects" ) );
            return;
        }
    }
    
         
    /**
    *
    * Mouse events
    *
    **/
    public void
    mouseDragged( MouseEvent e )
    {
        Point point = new Point( e.getPoint() );
        
        if ( selected_objects != null ) {
			tablet.setRecognitionMode( false );
            for ( int i=0; i<selected_objects.length; i++ ) {
				if ( selected_objects[i] instanceof Translatable ) {
	                ((Translatable)selected_objects[i]).translate( 
						(point.x-last_position.x), 
    	                (point.y-last_position.y) );
				}
            }
            paint( Tablet.debug_graphics );
            last_position = point;
        }
    }


    /**
    *
    * Mouse events
    *
    **/
    public void
    mouseMoved( MouseEvent e )
    {
    }     
    
    
    /**
    *
    * Mouse events
    *
    **/
    public void
    changeModeTo( int mode )
	{
		this.mode = mode;
		if ( mode == RECOGNITION_MODE ) {
	        tablet.setRecognitionMode( true );
	        tablet.setStatusMessage( "In recognition mode." );
		}
		if ( mode == SELECTION_MODE ) {
	        tablet.setRecognitionMode( true );
	        tablet.setStatusMessage( "In selection mode. Select and drag..." );
		}
	}
	
	
    /**
    *
    * Deselect the objects 
    *
    **/
    public void
    deselectObjects()
    {
        selected_objects = null;
    }
    
         
    /**
    *
    * Select the objects in the argument vector.
    *
    **/
    public void
    selectObjects( Translatable[] translatables )
    {
        selected_objects = translatables;
		changeModeTo( SELECTION_MODE );
    }
    
         
    /**
    *
    * Select the objects in the argument vector.
    *
    **/
    public void
    selectObjects( Vector translatables )
    {
		selected_objects = new Translatable[translatables.size()];
        for ( int i=0; i<selected_objects.length; i++ ) {
			selected_objects[i] = (Translatable) translatables.elementAt( i );
		}
		changeModeTo( SELECTION_MODE );
    }
    
         
    /**
    *
    * Call the parse method in spatial_parser
    *
    **/
    public void
    parse()
    {
        spatial_parser.parse( this );
        paint( Tablet.debug_graphics );
    }
    
         
    /**
    *
    * Launch a FileDialog and do a load
    *
    **/
    public void
    doOpen()
    {
        System.out.println( "Dialog launched" );
        FileDialog file_dialog = new FileDialog( tablet, 
                                                   "Open saved design",
                                                   FileDialog.LOAD );
        file_dialog.setFilenameFilter( new MechanicalDesignFilenameFilter() );
        file_dialog.show();
        reset();
        
        if ( ( file_dialog.getFile() == "" ) || 
             ( file_dialog.getFile() == 
             MechanicalDesignFilenameFilter.MECHANICAL_DESIGN_FILE_SIGNATURE )){
            return; 
        }
        
        System.out.println( "Loading from " + 
                            file_dialog.getDirectory() + 
                            file_dialog.getFile() );
        addObjects( 
            LoadStoreModule.loadFromFile( 
                file_dialog.getDirectory() + 
                file_dialog.getFile() ) );

        current_file_name      = file_dialog.getFile();
        current_file_directory = file_dialog.getDirectory();
    }
    
         
    /**
    *
    * Launch a FileDialog and do a save as
    *
    **/
    public void
    doSaveAs()
    {
        FileDialog file_dialog = new FileDialog( tablet, 
                                                    "Save design as",
                                                    FileDialog.SAVE );

        file_dialog.setFilenameFilter( new MechanicalDesignFilenameFilter() );
        file_dialog.setFile( 
            MechanicalDesignFilenameFilter.MECHANICAL_DESIGN_FILE_SIGNATURE );
        file_dialog.show();
        
        if ( ( file_dialog.getFile() == "" ) || 
             ( file_dialog.getFile() == 
             MechanicalDesignFilenameFilter.MECHANICAL_DESIGN_FILE_SIGNATURE )){
            return; 
        }

        System.out.println( "Saving in " + 
                            file_dialog.getDirectory() + 
                            file_dialog.getFile() );
                            
        LoadStoreModule.storeInFile(
            file_dialog.getDirectory() + 
            file_dialog.getFile(),
            objects );
            
        current_file_name      = file_dialog.getFile();
        current_file_directory = file_dialog.getDirectory();
    }
    
         
    /**
    *
    * Launch a FileDialog and do a save
    *
    **/
    public void
    doSave()
    {
        if ( current_file_name == "Untitled" ) {
            doSaveAs();
        } else {
            System.out.println( "Saving in " + 
                                current_file_directory +
                                current_file_name );
                                
            LoadStoreModule.storeInFile(
                current_file_directory +
                current_file_name,
                objects );
        }
    }
}


/** 
  * 
  * $Log: ObjectManager.java,v $
  * Revision 1.14  2000/06/08 03:18:32  mtsezgin
  *
  * Added addObjects method for adding a Vector of objects.
  *
  * Revision 1.13  2000/06/03 01:52:13  mtsezgin
  *
  * Removed GeometryUtil.java and delegated the functionality to ObjectManager.
  *
  * Revision 1.12  2000/06/02 21:11:15  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.11  2000/05/26 20:46:44  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.9  2000/05/22 02:42:34  mtsezgin
  *
  * The current version enables polygons to be sketched in pieces.
  *
  * Revision 1.8  2000/05/04 01:36:27  mtsezgin
  *
  * Fixed minor bugs.
  * The current version successfuly parses Motor, Cross, Nail and Ground.
  * In addition the ParseSupervisor is introduced here.
  *
  * Revision 1.7  2000/04/28 04:45:04  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.6  2000/04/25 22:18:09  mtsezgin
  *
  * Fixed some bugs in addObject.
  *
  * Revision 1.5  2000/04/20 03:59:53  mtsezgin
  * *** empty log message ***
  *
  * Revision 1.4  2000/04/13 06:24:08  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.3  2000/04/11 00:41:47  mtsezgin
  *
  * Now the whole package succesfully parses a motor.
  *
  * Revision 1.2  2000/04/06 19:16:23  mtsezgin
  *
  * Modified all the classes to use my Point class which extends java.awt.Point
  * instead of directly using java.awt.Point
  *
  * Revision 1.1.1.1  2000/04/01 03:07:07  mtsezgin
  * Imported sources
  *
  * Revision 1.2  2000/03/31 22:41:04  mtsezgin
  *
  * Started Log tracking.
  *
  *  
  **/
