/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 * 
 * http://www.osedu.org/licenses/ECL-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package tufts.vue;

import java.util.List;
import static tufts.Util.*;

/**

 * This is for events issued from within an LWComponent object
 * hierarchy.  Usually they are property change events, although in
 * some cases they are used for more general purpose signaling.

 * <p>In addition to the functionality of a
 * java.beans.PropertyChangeEvent, this event also tells you who the
 * change happened to (not always the same as the source: e.g.,
 * hierarchy events are usually sourced from the the parent), and in the
 * case of multiple components changing, can give you an entire list
 * of components who were part of a single change (especially useful
 * for hierarchy events, where issuing individual events would be
 * messy given that the event delivery hierarchy would be changing as
 * each subsequent event is issued).  LWCevent also differs from a
 * PropertyChangeEvent in that it does not include the NEW value,
 * as that can be obtained by asking the changed component to
 * tell us the current value, and because more often than not,
 * the listener already has code to pull that out anyway: it just
 * wants to know it's time to go ahead and do it's thing because
 * something important to it has changed.  As it is, it only contains
 * the oldValue field for the UndoManager.  (And LWComponents are not
 * supposed to fire an LWCEvent notification unless a value has
 * actually changed.)

 * <p>LWCevent is also used for special events placed into the
 * hierachy, such as as "user action completed" event, which can be
 * useful for signaling model listeners that if you've been waiting to
 * update yourself (usually for performance reasons), now is the time
 * to catch up.  E.g., the MapPanner doesn't bother to attempt
 * repainting until it sees this event.  In VUE, the UndoManager
 * singles these events for us, as it already has to have been
 * explicitly told when a user action has been completed in order to
 * know when to stop and collect a new aggregate undo action.
 
 * <p>It is also used in special cases to essentially issue a "repaint"
 * event to the GUI, although these are stop-gap cases that ultimately
 * would be better handled as a recognized property change.

 * @version $Revision: 1.42 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $  
 
 */


// Consider subclassing from PropertyChangeEvent.
// Consider LWCEvent as a keyed event with a source only,
// and adding subclasses for the property change events?

public class LWCEvent
{
    /** This is either a proper LWComponent Key object, or a String */
    public final Object key;
    
    /** This is either an actual old value, or an Undoable that is capable of restoring the old value */
    final Object oldValue;

    /** What initiated this event -- usually the same as component */
    public final Object source;
    
    /** a LWCevent can either hold a single component or an array of components: one of them is always null */
    private List<LWComponent> components = null;
    public final LWComponent component;
    
    // todo: we still using both src & component?
    public LWCEvent(Object source, LWComponent c, Object key, Object oldValue)
    {
        this.source = source;
        this.component = c;
        this.key = key;
        this.oldValue = oldValue;
    }

    public LWCEvent(Object source, LWComponent c, Object key) {
        this (source, c, key, NO_OLD_VALUE);
    }

    public LWCEvent(Object source, List<LWComponent> components, Object key)
    {
        this.source = source;
        this.components = components;
        this.component = null;
        this.key = key;
        this.oldValue = NO_OLD_VALUE;
    }
    
    public Object getSource()
    {
        return this.source;
    }

    /** @return true if this event originated within the model itself (e.g., not something like a proxy repaint event) */
    public boolean isModelSourced() {

        // todo: this won't be accurate for group events where component is null and
        // components is set.  We'll return false, tho that's okay for now as this will
        // only increase the handle priority of the event, so it's an okay fallback
        // position.  Eventually, we should set component to the parent LWComponent
        // experiencing the add/remove of a collection of children, so we can accurately
        // determine if this was a model-sourced event (as oppsed to, say, an Action
        // sourced event).
        
        return source == component;
    }

    public LWComponent getComponent()
    {
        if (component == null && components != null && components.size() > 0) {
            if (DEBUG.Enabled && components.size() > 1) {
                tufts.Util.printStackTrace(this + "RETURNING FIRST ONLY WHEN IT CONTAINS "
                                           + components.size() + "\n" + components + "\n");
            }
            return (LWComponent) components.get(0);
        } else
            return component;
    }

    public LWComponent onlyComponent() {
        if (component == null && components != null)
            return null;
        else
            return component;
    }
    
    
    public List<LWComponent> getComponents() {
        return this.components;
    }

    /** @return the name of the key for this LWCEvent */
    public String getName() {
        if (this.key instanceof LWComponent.Key)
            return ((LWComponent.Key)key).name;
        else
            return (String) this.key;
    }

    /** If the key is a proper Key, return it, otherwise, return null */
    public LWComponent.Key getKey() {
        if (this.key instanceof LWComponent.Key)
            return (LWComponent.Key) key;
        else
            return null;
    }

    /** @return either the LWKey or the String key */
    public Object getCode() {
        return key;
    }
    

    /**
     * Returns an old value if one was given to us.  As null is a valid
     * old value, it's distinguished from having no old value set
     * by the the value LWCEvent.NO_OLD_VALUE.  Or you can
     * check for the presence of an old value by calling hasOldValue().
     * If our stored oldValue is actually an Undoable, this will unpack
     * the old value from the Undoable and return it (if one was provided).
     */
    public Object getOldValue() {
        if (oldValue instanceof Undoable)
            return ((Undoable)oldValue).old;
        else
            return this.oldValue;
    }
    /** @return true if there is an old value to this event */
    public boolean hasOldValue() {
        return this.oldValue != NO_OLD_VALUE;
    }

    public boolean isUndoable() {
        // this is really only used in LWMap to determine if an event should
        // trigger a change in the edited status of a map.  We need to
        // check HierarchyChanging explicitly, as these are change events
        // w/out old-values -- the "old" values are determined specially
        // in the UndoManager itself.  This fixes VUE-1654.
        return hasOldValue() || key == LWKey.HierarchyChanging;
    }
    
    public String toString() {
        //return "LWCEvent[" + paramString() + "]";
        //return "[" + paramString() + "]";
        return String.format(TERM_PURPLE + "%08X[%s%s]%s", hashCode(), paramString(), TERM_PURPLE, TERM_CLEAR);
        
    }
    
    public String paramString() 
    {
        final StringBuffer buf = new StringBuffer(//TERM_PURPLE +
                                                  String.format("%-20s ", key)
                                                  + source);
        //+ TERM_CLEAR + " " + source);
        
        if (component != null && component != source) {
            buf.append(" c=" + component);
            //basic information.. if more information wants to be stringfied, need to code this part
        } else if (components != null) {
            buf.append(" list(" + components.size() + ": ");

            if (components.size() == 1) {
                buf.append(components.get(0));
            } else {
                java.util.Iterator<LWComponent> iter = components.iterator();
                while (iter.hasNext()) {
                    LWComponent c = iter.next();
                    buf.append(c.getUniqueComponentTypeLabel());
                    if (iter.hasNext())
                        buf.append(", ");
                }
            }
            buf.append(')');
        }
        if (oldValue != null && oldValue != NO_OLD_VALUE)
            buf.append(" (" + oldValue + ")");
              
        return buf.toString();
    }


    /** for null masking */
    public static final String NO_OLD_VALUE = "no_old_value";
}

