package prefuse;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import prefuse.action.Action;
import prefuse.activity.Activity;
import prefuse.activity.ActivityMap;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tree;
import prefuse.data.Tuple;
import prefuse.data.expression.Expression;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.tuple.CompositeTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleManager;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.Renderer;
import prefuse.render.RendererFactory;
import prefuse.util.PrefuseConfig;
import prefuse.util.PrefuseLib;
import prefuse.util.collections.CompositeIterator;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import prefuse.visual.VisualTree;
import prefuse.visual.VisualTupleSet;
import prefuse.visual.expression.ValidatedPredicate;
import prefuse.visual.expression.VisiblePredicate;
import prefuse.visual.tuple.TableDecoratorItem;
import prefuse.visual.tuple.TableEdgeItem;
import prefuse.visual.tuple.TableNodeItem;

/**
 * <p>Central data structure representing an interactive Visualization.
 * This class is responsible for
 * managing the mappings between source data and onscreen VisualItems,
 * maintaining a list of {@link Display} instances responsible for rendering
 * of and interaction with the contents of this visualization, and
 * providing a collection of named Action instances for performing
 * data processing such as layout, animation, and size, shape, and color
 * assignment.</p>
 * 
 * <p>The primary responsibility of the Visualization class is the creation
 * of <em>visual abstractions</em> of input data. Regardless of the data
 * structure (i.e., {@link prefuse.data.Table}, {@link prefuse.data.Graph},
 * or {@link prefuse.data.Tree}), this class takes source data such as that
 * loaded from a file (see {@link prefuse.data.io}) or from a relational
 * database (see {@link prefuse.data.io.sql}) and creates a visual
 * representation of the data. These visual representations of the data are
 * data sets in their own right, providing access to the underlying source
 * data to be visualized while also adding addition data fields specific to a
 * visualization. These fields include spatial location (x, y
 * coordinates and item bounds), color (for stroke, fill, and text), size,
 * shape, and font. For a given input data set of type
 * {@link prefuse.data.Table}, {@link prefuse.data.Graph}, or
 * or {@link prefuse.data.Tree}, a corresponding instance of
 * {@link prefuse.visual.VisualTable}, {@link prefuse.visual.VisualGraph}, or
 * {@link prefuse.visual.VisualTree} is created and stored in the
 * visualization. These data types inherit the data values of the source
 * data (and indeed, manipulate it directly) while additionally providing
 * the aforementioned visual variables unique to that generated
 * visual abstraction. Similarly, all {@link prefuse.data.Tuple},
 * {@link prefuse.data.Node}, or {@link prefuse.data.Edge}
 * instances used to represent an entry in the source data have a
 * corresponding {@link prefuse.visual.VisualItem},
 * {@link prefuse.visual.NodeItem}, or {@link prefuse.visual.EdgeItem}
 * representing the interactive, visual realization of the backing data.</p>
 * 
 * <p>The mapping of source data to a visual abstraction is accomplished
 * using {@link #add(String, TupleSet)} and the other "add" methods. These
 * methods will automatically create the visual abstraction, and store it
 * in this visualization, associating it with a provided <em>data group name
 * </em>. This group name allows for queries to this visualization that
 * consider only VisualItem instances from that particular group. This is
 * quite useful when crafting {@link prefuse.action.Action} instances that
 * process only a particular group of visual data. The Visualization class
 * provides mechanisms for querying any or all groups within the visualization,
 * using one or both of the group name or a filtering
 * {@link prefuse.data.expression.Predicate} to determine the items to
 * include (see {@link #items(Predicate)} for an examples). Source data
 * may be added multiple times to a Visualization under different group
 * names, allowing for multiple representations of the same backing data.</p>
 * 
 * <p>Additionally, the Visualization class supports VisualItem instances
 * that are not directly grounded in backing source data. Examples include
 * {@link prefuse.visual.DecoratorItem} which "decorates" another pre-existing
 * VisualItem with a separate interactive visual object, and
 * {@link prefuse.visual.AggregateItem} which provides an interactive visual
 * representation of an aggregated of other VisualItems. Methods for adding
 * data groups of these kinds include {@link #addDecorators(String, String)}
 * and {@link #addAggregates(String)}.</p>
 * 
 * <p>All of the examples discussed above are examples of <em>primary, or
 * visual, data groups</em> of VisualItems. Visualizations also support
 * <em>secondary, or focus data groups</em> that maintain additional
 * collections of the VisualItems stored in the primary groups. Examples
 * include a set of focus items (such as those that have been clicked
 * by the user), selected items (items selected by a user), or search
 * items (all matches to a search query). The exact semantics of these
 * groups and the mechanisms by which they are populated is determined by
 * application creators, but some defaults are provided. The Visualization
 * class includes some default group names, namely {@link #FOCUS_ITEMS},
 * {@link #SELECTED_ITEMS}, and {@link #SEARCH_ITEMS} for the above 
 * mentioned tasks. By default, both the {@link #FOCUS_ITEMS},
 * {@link #SELECTED_ITEMS} focus groups are included in the Visualization,
 * represented using {@link prefuse.data.tuple.DefaultTupleSet} instances.
 * Also, some of the interactive controls provided by the
 * {@link prefuse.controls} package populate these sets by default. See
 * {@link prefuse.controls.FocusControl} for an example.</p>
 * 
 * <p>Visualizations also maintain references to all the {@link Display}
 * instances providing interactive views of the content of this
 * visualization. {@link Display} instances registers themselves with
 * the visualization either in their constructor or through
 * the {@link Display#setVisualization(Visualization)} method, so they
 * do not otherwise need to be added manually. Displays can be configured
 * to show all or only a subset of the data in the Visualization. A
 * filtering {@link prefuse.data.expression.Predicate} can be used to
 * control what items are drawn by the displaying, including limiting
 * the Display to particular data groups (for example, using a
 * {@link prefuse.visual.expression.InGroupPredicate}). The Visualization's
 * {@link #repaint()} method will trigger a repaint on all Displays
 * associated with the visualization.</p>
 * 
 * <p>Finally, the Visualization class provides a map of named
 * {@link prefuse.action.Action} instances that can be invoked to perform
 * processing on the VisualItems contained in the visualization.
 * Using the {@link #putAction(String, Action)} will add a named Action
 * to the visualization, registering the Action such that a reference
 * to this Visualization will be available within the scope of the
 * Action's {@link prefuse.action.Action#run(double)} method. Processing
 * Actions can later be invoked by name using the {@link #run(String)}
 * method and other similar methods. This functionality not only
 * provides a convenient means of organizing a Visualization-specific
 * collection of processing Actions, it also allows for a layer of indirection
 * between an Action and its name. This allows Actions to be dynamically
 * swapped at runtime. For example, an application may make a call to
 * invoke an Action named "layout", but the actual layout processing maybe
 * be dynamically swapped by changing the Action that corresponds to that
 * name. For more information on processing Actions, see the
 * {@link prefuse.action} packages and the top-level
 * {@link prefuse.action.Action} class.</p>
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class Visualization {
    
    /** Data group name for indicating all groups */
    public static final String ALL_ITEMS 
        = PrefuseConfig.get("visualization.allItems");
    /** Default data group name for focus items */
    public static final String FOCUS_ITEMS
        = PrefuseConfig.get("visualization.focusItems");
    /** Default data group name for selected items */
    public static final String SELECTED_ITEMS
        = PrefuseConfig.get("visualization.selectedItems");
    /** Default data group name for search result items */
    public static final String SEARCH_ITEMS
        = PrefuseConfig.get("visualization.searchItems");
    
    // visual abstraction
    // filtered tables and groups
    private Map m_visual;
    private Map m_source;
    private Map m_focus;
    
    // actions
    private ActivityMap m_actions;
    
    // renderers
    private RendererFactory m_renderers;
    
    // displays
    private ArrayList m_displays;
    
    // ------------------------------------------------------------------------
    // Constructor
    
    /**
     * Create a new, empty Visualization. Uses a DefaultRendererFactory.
     */
    public Visualization() {
        m_actions = new ActivityMap();
        m_renderers = new DefaultRendererFactory();
        m_visual = new LinkedHashMap();
        m_source = new HashMap();
        m_focus = new HashMap();
        m_displays = new ArrayList();
        
        addFocusGroup(Visualization.FOCUS_ITEMS,    new DefaultTupleSet());
        addFocusGroup(Visualization.SELECTED_ITEMS, new DefaultTupleSet());
    }
    
    // ------------------------------------------------------------------------
    // Data Methods
    
    /**
     * Add a data set to this visualization, using the given data group name.
     * A visual abstraction of the data will be created and registered with
     * the visualization. An exception will be thrown if the group name is
     * already in use.
     * @param group the data group name for the visualized data
     * @param data the data to visualize
     * @return a visual abstraction of the input data, a VisualTupleSet
     * instance
     */
    public synchronized VisualTupleSet add(String group, TupleSet data) {
        return add(group, data, null);
    }

    /**
     * Add a data set to this visualization, using the given data group name.
     * A visual abstraction of the data will be created and registered with
     * the visualization. An exception will be thrown if the group name is
     * already in use.
     * @param group the data group name for the visualized data
     * @param data the data to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input data set are visualized
     * @return a visual abstraction of the input data, a VisualTupleSet
     * instance
     */
    public synchronized VisualTupleSet add(
            String group, TupleSet data, Predicate filter)
    {
        if ( data instanceof Table ) {
            return addTable(group, (Table)data, filter);
        } else if ( data instanceof Tree ) {
            return addTree(group, (Tree)data, filter);
        } else if ( data instanceof Graph ) {
            return addGraph(group, (Graph)data, filter);
        } else {
            throw new IllegalArgumentException("Unsupported TupleSet type.");
        }
    }
    
    protected void checkGroupExists(String group) {
    	if ( m_visual.containsKey(group) || m_focus.containsKey(group) ) {
    		throw new IllegalArgumentException(
    				"Group name \'"+group+"\' already in use");
    	}
    }
    
    protected void addDataGroup(String group, VisualTupleSet ts, TupleSet src) {
    	checkGroupExists(group);
    	m_visual.put(group, ts);
    	if ( src != null )
    		m_source.put(group, src);
    }
    
    // -- Tables --------------------------------------------------------------
    
    /**
     * Add an empty VisualTable to this visualization, using the given data
     * group name. This adds a group of VisualItems that do not have a
     * backing data set, useful for creating interactive visual objects
     * that do not represent data. An exception will be thrown if the group
     * name is already in use.
     * @param group the data group name for the visualized data
     * @return the added VisualTable
     */
    public synchronized VisualTable addTable(String group) {
    	VisualTable vt = new VisualTable(this, group);
    	addDataGroup(group, vt, null);
        return vt;
    }
    
    /**
     * Add an empty VisualTable to this visualization, using the given data
     * group name and table schema. This adds a group of VisualItems that do
     * not have a backing data set, useful for creating interactive visual
     * objects that do not represent data. An exception will be thrown if the
     * group name is already in use.
     * @param group the data group name for the visualized data
     * @param schema the data schema to use for the VisualTable
     * @return the added VisualTable
     */
    public synchronized VisualTable addTable(String group, Schema schema) {
    	VisualTable vt = new VisualTable(this, group, schema);
        addDataGroup(group, vt, null);
        return vt;
    }
    
    /**
     * Adds a data table to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized data
     * @param table the data table to visualize
     */
    public synchronized VisualTable addTable(String group, Table table) {
        return addTable(group, table, (Predicate)null);
    }
    
    /**
     * Adds a data table to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized data
     * @param table the data table to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input table are visualized
     */
    public synchronized VisualTable addTable(
            String group, Table table, Predicate filter)
    {
    	VisualTable vt = new VisualTable(table, this, group, filter);
    	addDataGroup(group, vt, table);
        return vt;
    }

    /**
     * Adds a data table to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized data
     * @param table the data table to visualize
     * @param schema the data schema to use for the created VisualTable
     */
    public synchronized VisualTable addTable(
            String group, Table table, Schema schema)
    {
        return addTable(group, table, null, schema);
    }
    
    /**
     * Adds a data table to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized data
     * @param table the data table to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input table are visualized
     * @param schema the data schema to use for the created VisualTable
     */
    public synchronized VisualTable addTable(
            String group, Table table, Predicate filter, Schema schema)
    {
        VisualTable vt = new VisualTable(table, this, group, filter, schema);
        addDataGroup(group, vt, table);
        return vt;
    }
    
    /**
     * Add a VisualTable to this visualization, using the table's
     * pre-set group name. An exception will be thrown if the group
     * name is already in use. This method allows you to insert custom
     * implementations of VisualTable into a Visualization. It is intended
     * for advanced users and should <b>NOT</b> be used if you do not know
     * what you are doing. In almost all cases, one of the other add methods
     * is preferred.
     * @param table the pre-built VisualTable to add
     * @return the added VisualTable
     */
    public synchronized VisualTable addTable(VisualTable table) {
    	addDataGroup(table.getGroup(), table, table.getParentTable());
    	table.setVisualization(this);
    	return table;
    }
    
    // -- Graphs and Trees ----------------------------------------------------
    
    /**
     * Adds a graph to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized graph. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param graph the graph to visualize
     */
    public synchronized VisualGraph addGraph(String group, Graph graph) {
        return addGraph(group, graph, null);
    }
    
    /**
     * Adds a graph to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized graph. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param graph the graph to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input graph are visualized
     */
    public synchronized VisualGraph addGraph(
            String group, Graph graph, Predicate filter)
    {
        return addGraph(group, graph, filter, VisualItem.SCHEMA, VisualItem.SCHEMA);
    }
    
    /**
     * Adds a graph to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized graph. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param graph the graph to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input graph are visualized
     * @param nodeSchema the data schema to use for the visual node table
     * @param edgeSchema the data schema to use for the visual edge table
     */
    public synchronized VisualGraph addGraph(String group, Graph graph,
            Predicate filter, Schema nodeSchema, Schema edgeSchema)
    {
    	checkGroupExists(group); // check before adding sub-tables
        String ngroup = PrefuseLib.getGroupName(group, Graph.NODES); 
        String egroup = PrefuseLib.getGroupName(group, Graph.EDGES);

        VisualTable nt, et;
        nt = addTable(ngroup, graph.getNodeTable(), filter, nodeSchema);
        et = addTable(egroup, graph.getEdgeTable(), filter, edgeSchema);
        
        VisualGraph vg = new VisualGraph(nt, et, 
                graph.isDirected(), graph.getNodeKeyField(),
                graph.getEdgeSourceField(), graph.getEdgeTargetField());
        vg.setVisualization(this);
        vg.setGroup(group);
     
        addDataGroup(group, vg, graph);
        
        TupleManager ntm = new TupleManager(nt, vg, TableNodeItem.class);
        TupleManager etm = new TupleManager(et, vg, TableEdgeItem.class);
        nt.setTupleManager(ntm);
        et.setTupleManager(etm);
        vg.setTupleManagers(ntm, etm);
        
        return vg;
    }
    
    /**
     * Adds a tree to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized tree. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param tree the tree to visualize
     */
    public synchronized VisualTree addTree(String group, Tree tree) {
        return addTree(group, tree, null);
    }
    
    /**
     * Adds a tree to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized tree. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param tree the tree to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input graph are visualized
     */
    public synchronized VisualTree addTree(
            String group, Tree tree, Predicate filter)
    {
        return addTree(group, tree, filter, VisualItem.SCHEMA, VisualItem.SCHEMA);
    }
    
    /**
     * Adds a tree to this visualization, using the given data group
     * name. A visual abstraction of the data will be created and registered
     * with the visualization. An exception will be thrown if the group name
     * is already in use.
     * @param group the data group name for the visualized tree. The nodes
     * and edges will be available in the "group.nodes" and "group.edges"
     * subgroups.
     * @param tree the tree to visualize
     * @param filter a filter Predicate determining which data Tuples in the
     * input graph are visualized
     * @param nodeSchema the data schema to use for the visual node table
     * @param edgeSchema the data schema to use for the visual edge table
     */
    public synchronized VisualTree addTree(String group, Tree tree,
            Predicate filter, Schema nodeSchema, Schema edgeSchema)
    {
    	checkGroupExists(group); // check before adding sub-tables
        String ngroup = PrefuseLib.getGroupName(group, Graph.NODES); 
        String egroup = PrefuseLib.getGroupName(group, Graph.EDGES);
        
        VisualTable nt, et;
        nt = addTable(ngroup, tree.getNodeTable(), filter, nodeSchema);
        et = addTable(egroup, tree.getEdgeTable(), filter, edgeSchema);

        VisualTree vt = new VisualTree(nt, et, tree.getNodeKeyField(),
                tree.getEdgeSourceField(), tree.getEdgeTargetField());
        vt.setVisualization(this);
        vt.setGroup(group);
        
        addDataGroup(group, vt, tree);
        
        TupleManager ntm = new TupleManager(nt, vt, TableNodeItem.class);
        TupleManager etm = new TupleManager(et, vt, TableEdgeItem.class);
        nt.setTupleManager(ntm);
        et.setTupleManager(etm);
        vt.setTupleManagers(ntm, etm);
        
        return vt;
    }
    
    // -- Aggregates ----------------------------------------------------------
    
    /**
     * Add a group of aggregates to this visualization. Aggregates are
     * used to visually represent groups of VisualItems.
     * @param group the data group name for the aggregates.
     * @return the generated AggregateTable
     * @see prefuse.visual.AggregateTable
     */
    public synchronized AggregateTable addAggregates(String group) {
        return addAggregates(group, VisualItem.SCHEMA);
    }
    
    /**
     * Add a group of aggregates to this visualization. Aggregates are
     * used to visually represent groups of VisualItems.
     * @param group the data group name for the aggregates.
     * @param schema the data schema to use for the AggregateTable
     * @return the generated AggregateTable
     * @see prefuse.visual.AggregateTable
     */
    public synchronized AggregateTable addAggregates(String group,
                                                     Schema schema)
    {
        AggregateTable vat = new AggregateTable(this, group, schema);
        addDataGroup(group, vat, null);
        return vat;
    }
    
    // -- Derived Tables and Decorators ---------------------------------------
    
    /**
     * Add a derived table, a VisualTable that is cascaded from an
     * existing VisualTable. This is useful for creating VisualItems
     * that inherit a set of visual properties from another group of
     * VisualItems. This might be used, for example, in the creation
     * of small multiples where only a few visual attributes vary
     * across the multiples.
     * @param group the data group to use for the derived table
     * @param source the source data group to derive from
     * @param filter a Predicate filter indicating which tuples of the
     * source group should be inheritable by the new group
     * @param override a data schema indicating which data fields
     * should not be inherited, but managed locally by the derived group
     * @return the derived VisualTable
     */
    public synchronized VisualTable addDerivedTable(
            String group, String source, Predicate filter, Schema override)
    {
        VisualTable src = (VisualTable)getGroup(source);
        VisualTable vt = new VisualTable(src, this, group, filter, override);
     
        addDataGroup(group, vt, getSourceData(source));
        return vt;
    }
    
    /**
     * Add a group of decorators to an existing visual data group. Decorators
     * are VisualItem instances intended to "decorate" another VisualItem,
     * such as providing a label or dedicated interactive control, and are
     * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide
     * access to the decorated item in addition to the standard VisualItem
     * properties. The generated table is created using the
     * {@link #addDerivedTable(String, String, Predicate, Schema)} method,
     * but with no VisualItem properties inherited from the source group.
     * @param group the data group to use for the decorators
     * @param source the source data group to decorate
     * @return the generated VisualTable of DecoratorItem instances
     */
    public synchronized VisualTable addDecorators(String group,String source) {
        return addDecorators(group, source, (Predicate)null);
    }
    
    /**
     * Add a group of decorators to an existing visual data group. Decorators
     * are VisualItem instances intended to "decorate" another VisualItem,
     * such as providing a label or dedicated interactive control, and are
     * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide
     * access to the decorated item in addition to the standard VisualItem
     * properties.
     * @param group the data group to use for the decorators
     * @param source the source data group to decorate
     * @param schema schema indicating which variables should <b>not</b> be
     * inherited from the source data group and instead be managed locally
     * by the generated VisualTable
     * @return the generated VisualTable of DecoratorItem instances
     */
    public synchronized VisualTable addDecorators(
            String group, String source, Schema schema)
    {
        return addDecorators(group, source, null, schema);
    }
    
    /**
     * Add a group of decorators to an existing visual data group. Decorators
     * are VisualItem instances intended to "decorate" another VisualItem,
     * such as providing a label or dedicated interactive control, and are
     * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide
     * access to the decorated item in addition to the standard VisualItem
     * properties.
     * @param group the data group to use for the decorators
     * @param source the source data group to decorate
     * @param filter a Predicate filter indicating which tuples of the
     * source group should be inheritable by the new group
     * @return the generated VisualTable of DecoratorItem instances
     */
    public synchronized VisualTable addDecorators(
            String group, String source, Predicate filter)
    {
        VisualTable t = addDerivedTable(group,source,filter,VisualItem.SCHEMA);
        t.setTupleManager(new TupleManager(t, null, TableDecoratorItem.class));
        return t;
    }
    
    /**
     * Add a group of decorators to an existing visual data group. Decorators
     * are VisualItem instances intended to "decorate" another VisualItem,
     * such as providing a label or dedicated interactive control, and are
     * realizeed as {@link prefuse.visual.DecoratorItem} instances that provide
     * access to the decorated item in addition to the standard VisualItem
     * properties.
     * @param group the data group to use for the decorators
     * @param source the source data group to decorate
     * @param filter a Predicate filter indicating which tuples of the
     * source group should be inheritable by the new group
     * @param schema schema indicating which variables should <b>not</b> be
     * inherited from the source data group and instead be managed locally
     * by the generated VisualTable
     * @return the generated VisualTable of DecoratorItem instances
     */
    public synchronized VisualTable addDecorators(
            String group, String source, Predicate filter, Schema schema)
    {
        VisualTable t = addDerivedTable(group, source, filter, schema);
        t.setTupleManager(new TupleManager(t, null, TableDecoratorItem.class));
        return t;
    }
    
    // -- Data Removal --------------------------------------------------------
    
    /**
     * Removes a data group from this Visualization. If the group is a focus
     * group, the group will simply be removed, and any subsequent attempts to
     * retrieve the group will return null. If the group is a primary group, it
     * will be removed, and any members of the group will also be removed
     * from any registered focus groups.
     * @param group the data group to remove
     * @return true if the group was found and removed, false if the group
     * was not found in this visualization.
     */
    public synchronized boolean removeGroup(String group) {
        // check for focus group first
        TupleSet ts = getFocusGroup(group);
        if ( ts != null ) {
            // invalidate the item to reflect group membership change
            for ( Iterator items = ts.tuples(ValidatedPredicate.TRUE);
                  items.hasNext(); )
            {
                ((VisualItem)items.next()).setValidated(false);
            }
            ts.clear(); // trigger group removal callback
            m_focus.remove(group);
            return true;
        }
        
        // focus group not found, check for primary group
        ts = getVisualGroup(group);
        if ( ts == null ) {
            // exit with false if group not found
            return false;
        }
        // remove group members from focus sets and invalidate them
        TupleSet[] focus = new TupleSet[m_focus.size()];
        m_focus.values().toArray(focus);
        for ( Iterator items = ts.tuples(); items.hasNext(); ) {
            VisualItem item = (VisualItem)items.next();
            for ( int j=0; j<focus.length; ++j ) {
                focus[j].removeTuple(item);
            }
            item.setValidated(false);
        }
        // remove data
        if ( ts instanceof CompositeTupleSet ) {
            CompositeTupleSet cts = (CompositeTupleSet)ts;
            for ( Iterator names = cts.setNames(); names.hasNext(); ) {
                String name = (String)names.next();
                String subgroup = PrefuseLib.getGroupName(group,name); 
                m_visual.remove(subgroup);
                m_source.remove(subgroup);
            }
        }
        m_visual.remove(group);
        m_source.remove(group);
        return true;
    }
    
    /**
     * Reset this visualization, clearing out all visualization tuples. All
     * data sets added using the "addXXX" methods will be removed from the
     * visualization. All registered focus groups added using the 
     * addFocusGroup() methods will be retained, but will be cleared of all
     * tuples.
     */
    public synchronized void reset() {
        // first clear out all the focus groups
        Iterator iter = m_focus.entrySet().iterator();
        while ( iter.hasNext() ) {
            Map.Entry entry = (Map.Entry)iter.next();
            TupleSet ts = (TupleSet)entry.getValue();
            ts.clear();
        }
        // finally clear out all map entries
        m_visual.clear();
        m_source.clear();
    }
    
    // ------------------------------------------------------------------------
    // Groups
    
    /**
     * Get the source data TupleSet backing the given visual data group.
     * @return the backing source data set, or null if there is no such
     * data set
     */
    public TupleSet getSourceData(String group) {
        return (TupleSet)m_source.get(group);
    }
    
    /**
     * Get the source data TupleSet backing the given visual tuple set.
     * @return the backing source data set, or null if there is no such
     * data set
     */
    public TupleSet getSourceData(VisualTupleSet ts) {
        return (TupleSet)m_source.get(ts.getGroup());
    }
    
    /**
     * Get the Tuple from a backing source data set that corresponds most
     * closely to the given VisualItem.
     * @param item the VisualItem for which to retreive the source tuple
     * @return the data source tuple, or null if no such tuple could
     * be found
     */
    public Tuple getSourceTuple(VisualItem item) {
        // get the source group and tuple set, exit if none
        String group = item.getGroup();
        TupleSet source = getSourceData(group);
        if ( source == null ) return null;
        
        // first get the source table and row value
        int row = item.getRow();
        Table t = item.getTable();
        while ( t instanceof VisualTable ) {
            VisualTable vt = (VisualTable)t;
            row = vt.getParentRow(row);
            t   = vt.getParentTable();
        }
        
        // now get the appropriate source tuple
        // graphs maintain their own tuple managers so treat them specially
        String cgroup = PrefuseLib.getChildGroup(group);
        if ( cgroup != null ) {
            String pgroup = PrefuseLib.getParentGroup(group);
            Graph g = (Graph)getSourceData(pgroup);
            if ( t == g.getNodeTable() ) {
                return g.getNode(row);
            } else {
                return g.getEdge(row);
            }
        } else {
            return t.getTuple(row);
        }
    }
    
    /**
     * Get the VisualItem associated with a source data tuple, if it exists.
     * @param group the data group from which to lookup the source tuple,
     * only primary visual groups are valid, focus groups will not work
     * @param t the source data tuple
     * @return the associated VisualItem from the given data group, or
     * null if no such VisualItem exists
     */
    public VisualItem getVisualItem(String group, Tuple t) {
        TupleSet ts = getVisualGroup(group);
        VisualTable vt;
        if ( ts instanceof VisualTable ) {
            vt = (VisualTable)ts;
        } else if ( ts instanceof Graph ) {
            Graph g = (Graph)ts;
            vt = (VisualTable)(t instanceof Node ? g.getNodeTable() 
                                                 : g.getEdgeTable());
        } else {
            return null;
        }
        int pr = t.getRow();
        int cr = vt.getChildRow(pr);
        return cr<0 ? null : vt.getItem(cr);
    }
    
    // ------------------------------------------------------------------------
    
    /**
     * Get the TupleSet associated with the given data group name. 
     * @param group a visual data group name
     * @return the data group TupleSet
     */
    public TupleSet getGroup(String group) {
        TupleSet ts = getVisualGroup(group);
        if ( ts == null )
            ts = getFocusGroup(group);
        return ts;
    }
    
    /**
     * Indicates if a given VisualItem is contained in the given visual
     * data group.
     * @param item the VisualItem instance
     * @param group the data group to check for containment
     * @return true if the VisualItem is in the group, false otherwise
     */
    public boolean isInGroup(VisualItem item, String group) {
        if ( ALL_ITEMS.equals(group) )
            return true;
        if ( item.getGroup() == group )
            return true;
        
        TupleSet tset = getGroup(group);
        return ( tset==null ? false : tset.containsTuple(item) );
    }
    
    /**
     * Add a new secondary, or focus, group to this visualization. By
     * default the added group is an instance of
     * {@link prefuse.data.tuple.DefaultTupleSet}.
     * @param group the name of the focus group to add
     */
    public void addFocusGroup(String group) {
    	checkGroupExists(group);
        m_focus.put(group, new DefaultTupleSet());
    }

    /**
     * Add a new secondary, or focus, group to this visualization.
     * @param group the name of the focus group to add
     * @param tset the TupleSet for the focus group
     */
    public void addFocusGroup(String group, TupleSet tset) {
        checkGroupExists(group);
    	m_focus.put(group, tset);
    }
    
    // ------------------------------------------------------------------------
    // VisualItems
    
    /**
     * Get the size of the given visual data group.
     * @param group the visual data group
     * @return the size (number of tuples) of the group
     */
    public int size(String group) {
        TupleSet tset = getGroup(group);
        return ( tset==null ? 0 : tset.getTupleCount() );
    }
    
    /**
     * Retrieve the visual data group of the given group name. Only primary
     * visual groups will be considered.
     * @param group the visual data group
     * @return the requested data group, or null if not found
     */
    public TupleSet getVisualGroup(String group) {
        return (TupleSet)m_visual.get(group);
    }
    
    /**
     * Retrieve the focus data group of the given group name. Only secondary,
     * or focus, groups will be considered.
     * @param group the focus data group
     * @return the requested data group, or null if not found
     */
    public TupleSet getFocusGroup(String group) {
        return (TupleSet)m_focus.get(group);
    }
    
    /**
     * Invalidate the bounds of all VisualItems in the given group. This
     * will cause the bounds to be recomputed for all items upon the next
     * redraw.
     * @param group the visual data group to invalidate
     */
    public void invalidate(String group) {
        Iterator items = items(ValidatedPredicate.TRUE);
        while ( items.hasNext() ) {
            VisualItem item = (VisualItem)items.next();
            item.setValidated(false);
        }
    }
    
    /**
     * Invalidate the bounds of all VisualItems in this visualization. This
     * will cause the bounds to be recomputed for all items upon the next
     * redraw.
     */
    public void invalidateAll() {
        invalidate(ALL_ITEMS);
    }
    
    /**
     * Get an iterator over all visible items.
     * @return an iterator over all visible items.
     */
    public Iterator visibleItems() {
        return items(VisiblePredicate.TRUE);
    }
    
    /**
     * Get an iterator over all visible items in the specified group.
     * @param group the visual data group name
     * @return an iterator over all visible items in the specified group
     */
    public Iterator visibleItems(String group) {
        return items(group, VisiblePredicate.TRUE);
    }
    
    /**
     * Get an iterator over all items, visible or not.
     * @return an iterator over all items, visible or not.
     */
    public Iterator items() {
        return items((Predicate)null);
    }
    
    /**
     * Get an iterator over all items which match the given
     * Predicate filter.
     * @param filter a Predicate indicating which items should be included
     * in the iteration
     * @return a filtered iterator over VisualItems
     */
    public Iterator items(Predicate filter) {
        int size = m_visual.size();
        if ( size == 0 ) {
            return Collections.EMPTY_LIST.iterator();
        } else if ( size == 1 ) {
            Iterator it = m_visual.keySet().iterator();
            return items((String)it.next(), filter);
        } else {
            CompositeIterator iter = new CompositeIterator(m_visual.size());
            Iterator it = m_visual.keySet().iterator();
            for ( int i=0; it.hasNext(); ) {
                String group = (String)it.next();
                if ( !PrefuseLib.isChildGroup(group) )
                    iter.setIterator(i++, items(group, filter));
            }
            return iter;
        }
    }
    
    /**
     * Get an iterator over all items in the specified group.
     * @param group the visual data group name
     * @return an iterator over all items in the specified group.
     */
    public Iterator items(String group) {
        return items(group, (Predicate)null);
    }
    
    /**
     * Get an iterator over all items in the given group which match the given
     * filter expression.
     * @param group the visual data group to iterate over
     * @param expr an expression string that should parse to a Predicate
     * indicating which items should be included in the iteration. The input
     * string will be parsed using the
     * {@link prefuse.data.expression.parser.ExpressionParser} class. If a
     * parse error occurs, an empty iterator is returned.
     * @return a filtered iterator over VisualItems
     */
    public Iterator items(String group, String expr) {
        Expression e = ExpressionParser.parse(expr);
        if ( !(e instanceof Predicate) || ExpressionParser.getError()!=null )
            return Collections.EMPTY_LIST.iterator();
        return items(group, (Predicate)e);
    }
    
    /**
     * Get an iterator over all items in the given group which match the given
     * Predicate filter.
     * @param group the visual data group to iterate over
     * @param filter a Predicate indicating which items should be included in
     * the iteration.
     * @return a filtered iterator over VisualItems
     */
    public Iterator items(String group, Predicate filter) {
        if ( ALL_ITEMS.equals(group) )
            return items(filter);

        TupleSet t = getGroup(group);
        return ( t==null ? Collections.EMPTY_LIST.iterator() 
                         : t.tuples(filter) );
    }
    
    // ------------------------------------------------------------------------
    // Batch Methods
    
    /**
     * Set a data field value for all items in a given data group matching a
     * given filter predicate.
     * @param group the visual data group name
     * @param p the filter predicate determining which items to modify
     * @param field the data field / column name to set
     * @param val the value to set
     */
    public void setValue(String group, Predicate p, String field, Object val) {
        Iterator items = items(group, p);
        while ( items.hasNext() ) {
            VisualItem item = (VisualItem)items.next();
            item.set(field, val);
        }
    }
    
    /**
     * Sets the visbility status for all items in a given data group matching
     * a given filter predicate.
     * @param group the visual data group name
     * @param p the filter predicate determining which items to modify
     * @param value the visibility value to set
     */
    public void setVisible(String group, Predicate p, boolean value) {
        Iterator items = items(group, p);
        while ( items.hasNext() ) {
            VisualItem item = (VisualItem)items.next();
            item.setVisible(value);
        }
    }

    /**
     * Sets the interactivity status for all items in a given data group
     * matching a given filter predicate.
     * @param group the visual data group name
     * @param p the filter predicate determining which items to modify
     * @param value the interactivity value to set
     */
    public void setInteractive(String group, Predicate p, boolean value) {
        Iterator items = items(group, p);
        while ( items.hasNext() ) {
            VisualItem item = (VisualItem)items.next();
            item.setInteractive(value);
        }
    }
    
    // ------------------------------------------------------------------------
    // Action Methods
    
    /**
     * Add a data processing Action to this Visualization. The Action will be
     * updated to use this Visualization in its data processing.
     * @param name the name of the Action
     * @param action the Action to add
     */
    public Action putAction(String name, Action action) {
        action.setVisualization(this);
        m_actions.put(name, action);
        return action;
    }
    
    /**
     * Get the data processing Action with the given name.
     * @param name the name of the Action
     * @return the requested Action, or null if the name was not found
     */
    public Action getAction(String name) {
        return (Action)m_actions.get(name);
    }
    
    /**
     * Schedule the Action with the given name to run immediately. The running
     * of all Actions is managed by the
     * {@link prefuse.activity.ActivityManager}, which runs in a dedicated
     * thread.
     * @param action the name of the Action to run
     * @return the Action scheduled to run
     */
    public Activity run(String action) {
        return m_actions.run(action);
    }

    /**
     * Schedule the Action with the given name to run after the specified
     * delay. The running of all Actions is managed by the
     * {@link prefuse.activity.ActivityManager}, which runs in a dedicated
     * thread.
     * @param action the name of the Action to run
     * @param delay the amount of time to wait, in milliseconds, before
     * running the Action
     * @return the Action scheduled to run
     */
    public Activity runAfter(String action, long delay) {
        return m_actions.runAt(action, System.currentTimeMillis()+delay);
    }
    
    /**
     * Schedule the Action with the given name to run at the specified
     * time. The running of all Actions is managed by the
     * {@link prefuse.activity.ActivityManager}, which runs in a dedicated
     * thread.
     * @param action the name of the Action to run
     * @param startTime the absolute system time, in milliseconds since the
     * epoch, at which to run the Action.
     * @return the Action scheduled to run
     */
    public Activity runAt(String action, long startTime) {
        return m_actions.runAt(action, startTime);
    }
    
    /**
     * Schedule the Action with the given name to run after another Action
     * finishes running. This relationship will only hold for one round of
     * scheduling. If the "before" Action is run a second time, the "after"
     * action will not be run a second time. The running of all Actions is
     * managed by the {@link prefuse.activity.ActivityManager}, which runs
     * in a dedicated thread.
     * @param before the name of the Action to wait for
     * @param after the name of the Action to run after the first one finishes
     * @return the Action scheduled to run after the first one finishes
     */
    public Activity runAfter(String before, String after) {
        return m_actions.runAfter(before, after);
    }
    
    /**
     * Schedule the Action with the given name to always run after another Action
     * finishes running. The running of all Actions is managed by the
     * {@link prefuse.activity.ActivityManager}, which runs in a dedicated
     * thread.
     * @param before the name of the Action to wait for
     * @param after the name of the Action to run after the first one finishes
     * @return the Action scheduled to always run after the first one finishes
     */
    public Activity alwaysRunAfter(String before, String after) {
        return m_actions.alwaysRunAfter(before, after);
    }
    
    /**
     * Cancel the Action with the given name, if it has been scheduled.
     * @param action the name of the Action to cancel
     * @return the canceled Action
     */
    public Activity cancel(String action) {
        return m_actions.cancel(action);
    }
    
    // ------------------------------------------------------------------------
    // Renderers
    
    /**
     * Set the RendererFactory used by this Visualization. The RendererFactory
     * is responsible for providing the Renderer instances used to draw
     * the VisualItems.
     * @param rf the RendererFactory to use.
     */
    public void setRendererFactory(RendererFactory rf) {
        invalidateAll();
        m_renderers = rf;
    }
    
    /**
     * Get the RendererFactory used by this Visualization.
     * @return this Visualization's RendererFactory
     */
    public RendererFactory getRendererFactory() {
        return m_renderers;
    }
    
    /**
     * Get the renderer for the given item. Consults this visualization's
     * {@link prefuse.render.RendererFactory} and returns the result.
     * @param item the item to retreive the renderer for
     * @return the {@link prefuse.render.Renderer} for drawing the
     * given item
     */
    public Renderer getRenderer(VisualItem item) {
        if ( item.getVisualization() != this ) {
            throw new IllegalArgumentException(
                    "Input item not a member of this visualization.");
        }
        return m_renderers.getRenderer(item);
    }
    
    /**
     * Issue a repaint request, causing all displays associated with this
     * visualization to be repainted.
     */
    public synchronized void repaint() {
        Iterator items = items(ValidatedPredicate.FALSE);
        while ( items.hasNext() ) {
            ((VisualItem)items.next()).validateBounds();
        }
        for ( int i=0; i<m_displays.size(); ++i ) {
            getDisplay(i).repaint();
        }
    }
    
    /**
     * Get the bounding rectangle for all items in the given group.
     * @param group the visual data group
     * @return the bounding box of the items
     */
    public Rectangle2D getBounds(String group) {
        return getBounds(group, new Rectangle2D.Double());
    }
    
    /**
     * Get the bounding rectangle for all items in the given group.
     * @param group the visual data group name
     * @param r a rectangle in which to store the computed bounding box
     * @return the input rectangle r, updated to hold the computed
     * bounding box
     */
    public Rectangle2D getBounds(String group, Rectangle2D r) {
        Iterator iter = visibleItems(group);
        if ( iter.hasNext() ) {
            VisualItem item = (VisualItem)iter.next();
            r.setRect(item.getBounds());
        }
        while ( iter.hasNext() ) {
            VisualItem item = (VisualItem)iter.next();
            Rectangle2D.union(item.getBounds(), r, r);
        }
        return r;
    }
    
    // ------------------------------------------------------------------------
    // Displays
    
    /**
     * Get the number of displays associated with this visualization.
     * @return the number of displays
     */
    public int getDisplayCount() {
        return m_displays.size();
    }
    
    /**
     * Add a display to this visualization. Called automatically by the
     * {@link prefuse.Display#setVisualization(Visualization)} method.
     * @param display the Display to add
     */
    void addDisplay(Display display) {
        m_displays.add(display);
    }
    
    /**
     * Get the display at the given list index. Displays are numbered by the
     * order in which they are added to this visualization.
     * @param idx the list index
     * @return the Display at the given index
     */
    public Display getDisplay(int idx) {
        return (Display)m_displays.get(idx);
    }
    
    /**
     * Remove a display from this visualization.
     * @param display the display to remove
     * @return true if the display was removed, false if it was not found
     */
    boolean removeDisplay(Display display) {
        return m_displays.remove(display);
    }
    
    /**
     * Report damage to associated displays, indicating a region that will need
     * to be redrawn.
     * @param item the item responsible for the damage
     * @param region the damaged region, in item-space coordinates
     */
    public void damageReport(VisualItem item, Rectangle2D region) {
        for ( int i=0; i<m_displays.size(); ++i ) {
            Display d = getDisplay(i);
            if ( d.getPredicate().getBoolean(item) ) {
                d.damageReport(region);
            }
        }
    }
    
} // end of class Visualization
