/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache 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.apache.org/licenses/LICENSE-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 org.netbeans.spi.actions;

import org.openide.util.*;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import javax.swing.Action;
import javax.swing.ImageIcon;

/**
 * An action which operates in the global <i>selection context</i> (a
 * Lookup such as the return value of Utilities.actionsGlobalContext()).
 * Context actions are sensitive to the presence or absence of a particular
 * Java type in a Lookup.  The presence or absence of objects of that type
 * can changed based on things like what the user has selected, or what
 * logical window contains focus, or both of those things.
 * <p/>
 * Context actions implement <code>org.openide.util.ContextAwareAction</code>,
 * which has the method <code>createContextAwareInstance()</code> to create
 * an instance of the action for a specific context (for example, a popup
 * menu - the selection might change while the popup is on-screen, but what
 * the action in the popup menu operates on should not).
 * <p/>
 * Context actions make it possible to have a global action which doesn't
 * have to know anything in particular about which object going to operate on;
 * it just knows the type of that object.  When invoked, it acts on whatever
 * is selected at that moment in time.
 * <p/>
 * This class and its subclasses are a replacement for most uses of
 * NodeAction and CookieAction from the Nodes API.
 * In the NetBeans IDE, to declaratively install a global ContextAction, simply write
 * a static method that contains the above code, and reference it in your
 * layer file as the <code>methodvalue</code> for your <code>.instance</code>
 * or similar file.
 * <p/>
 * <h4>Using ContextAction in place of NodeAction or CookieAction</h4>
 * Replacing CookieAction with ContextAction is fairly straightforward.
 * <p/>
 * The most straightforward replacement for <code>CookieAction</code> is
 * <code>LookupProviderAction</code>.  It handles one layer of indirection -
 * that is, a <code>CookieAction</code> first needs to have at least one <code>Node</code>
 * selected;  a <code>Node</code> is itself a <code>Lookup.Provider</code>,
 * and so the <code>CookieAction</code>
 * is sensitive to some object type in the lookup of one or more selected
 * <code>Node</code>s.  So a <code>CookieAction</code> is an action sensitive
 * to an object (the cookie) in the lookup of an object in the selection (the node).
 * <p/>
 * <code>LookupProviderAction</code> accomplishes the same thing, without
 * requiring that the second <code>Lookup.Provider</code> be a <code>Node</code>.
 * <p>
 * CookieAction supported a number of different <i>mode</i>s
 * which determined its enablement, based on how many objects were in the
 * selection and if there were any lookups in the selection that did <i>not</i>
 * contain an instance of the type it was sensitive to.  In <code>LookupProviderAction</code>,
 * this distinction is split into its two constituents:  To disallow enablement
 * if there is some Lookup.Provider that does <i>not</i> contain the type you
 * care about, pass true to the superclass constructor for the <code>all</code>
 * parameter.  To control for the number of selected items, override
 * <code>checkQuantity(int)</code>.
 * <p/>
 * It is also possible with <code>ContextAction</code>s to do deeper indirection.
 * E.g., say the selection will contain a <code>Node</code>.  <code>Node</code>
 * implements <code>Lookup.Provider</code>.  You are interested in <code>Node</code>s
 * which have a <code>Project</code> in their lookup, and <code>Project</code>
 * is also a <code>Lookup.Provider</code> too.  What you really want to do is
 * write an action that is enabled
 * <ul><li>When the selection contains a <code>Node</code>
 *   <ul><li>which contains a <code>Project</code> in its lookup</code>
 *      <ul><li>which contains a <code>Foo</code> in its lookup</code>
 *      </ul</li>
 *   </ul></li>
 * </ul></li>
 * The only actually interesting logic here is what you will do with a
 * <code>Foo</code> if you can get hold of one.  Handling this is easy:
 * <pre>
 * ContextAction<Foo> theRealAction = new MyContextAction(); //this is what you wrote
 * Action<Node> theGlobalAction = createIndirectAction(Node.class,
 *   createIndirectAction(Project.class, theRealAction));
 * </pre>
 * If you had a CookieAction which was sensitive to more than one cookie type,
 * that scenario is not supported directly by ContextAction.  But what you
 * do instead will likely be simpler and more testable than your original
 * code, and will do the same thing:
 * <ul>
 * <li>Write one ContextAction subclass for each of the types you want to
 * support</li>
 * <li>Create your global action by calling 
 * <code>ContextAction.merge (actionOne, actionTwo, actionThree);
 * </li>
 * </ul>
 * <h4>A Note about Equality</h4>
 * ContextAction and any subclasses implement equals() and hashCode() such that
 * if the <em>type</em> of the passed object is the same, then the objects are treated
 * as identical.  This enables multi-selection to work.  This means that any
 * instance of a subclass of ContextAction is effectively a singleton and any
 * other instance of the same class may be substituted for it.
 * <p/>
 * If you want to create a single subclass of ContextAction which is passed
 * different parameters or create instances of the same subtype which
 * have different behaviors, you
 * are probably choosing the wrong parent class for your action.  The point
 * of ContextAction is that the entirety of the &quot;context&quot; is the
 * content of the Lookup that is passed to it.
 * <p/>
 * To, for example, change the display name depending on number of objects
 * selected, simply override <code>change()</code> to do that.
 *
 * @see org.openide.util.ContextAwareAction
 * @see org.openide.util.Lookup
 * @see org.openide.util.Utilities#actionsGlobalContext
 * @author Tim Boudreau
 */
public abstract class ContextAction<T> extends NbAction {
    final Class<T> type;
    private final StubListener stubListener = new StubListener();
    //A context aware instance which we use internally to trigger
    //enabled changes as long as there is at least one property change
    //listener attached to us.
    //By having the same thing we return from createContextAwareInstance handle
    //all internal state, we make it easy to make a survives-focus-change
    //subclass just by overriding createStub() to make a stub which retains
    //the last usable collection of objects

    //The action stub is the thing that really does the heavy lifting for
    //all ContextActions.
    ActionStub<T> stub;
    static boolean unitTest;
    
    /**
     * Create a new ContextAction which will operate on instances of
     * type <code>type</code>
     * @param type The type this action needs in its context in order to be
     * invoked
     */
    protected ContextAction(Class<T> type) {
        this (type, null, null);
    }

    /**
     * Create a new ContextAction which will operate on a type <code>type</code>,
     * with the specified display name and icon.
     *
     * @param type The type this action needs in its context in order to be
     * invoked
     * @param displayName A localized display name
     * @param icon An image to use as the action's icon
     */
    protected ContextAction(Class<T> type, String displayName, Image icon) {
        this.type = type;
        Parameters.notNull("type", type);
        if (displayName != null) {
            putValue (Action.NAME, displayName);
        }
        if (icon != null) {
            putValue (Action.SMALL_ICON, new ImageIcon (icon));
        }
        putValue ("noIconInMenu", true);
    }

    /**
     * Whether or not the action is enabled.  By default, determines if there
     * are any instances of type <code>type</code> in the selection context
     * lookup, and if there are, returns true.  To refine this behavior further,
     * override <code>enabled (java.util.Collection)</code>.
     * @return whether or not the action should be enabled
     */
    @Override
    public final boolean isEnabled() {
        return _isEnabled();
    }

    boolean _isEnabled() { //For override in MergeAction
        ActionStub<T> stubAction = getStub();
        Collection <? extends T> targets = stubAction.collection();
        boolean result = checkQuantity(targets) && stubAction.isEnabled();
        return result;
    }

    boolean checkQuantity(Collection<? extends T> targets) {
        return checkQuantity (targets.size());
    }

    /**
     * Determine if this action should be enabled, based on the number
     * of objects of type <code>type</code> available in the selection.
     * Some actions will want to be enabled only if there is a single
     * object of a given type, or some specific number (for example,
     * an action which diffs two files should only be enabled if exactly
     * two files are selected).
     * <p/>
     * This method is called prior to the (potentially)
     * more expensive check of <code>enabled(Collection&lt;T&gt;)</code>.  If it
     * returns false, no further tests are done;  the action is disabled.
     *
     * @param numberOfObjects The number of objects of type <code>T</code>
     * in the selection
     * @return True if the action should be enabled.
     */
    protected boolean checkQuantity (int numberOfObjects) {
        return numberOfObjects > 0;
    }

    /**
     * Determine if this action should be enabled.  This method will only be
     * called if the size of the collection is > 0.  The default implementation
     * returns <code>true</code>.  If you need to do some further
     * test on the collection of objects to determine if the action should
     * really be enabled or not, override this method.
     *
     * @param targets A collection of objects of type <code>type</code>
     * @return Whether or not the action should be enabled.
     */
    protected boolean isEnabled(Collection<? extends T> targets) {
        return true;
    }

    /**
     * Override to actually do whatever this action does.  This method is
     * passed the collection of all objects of type <code>&lt;T&gt;
     * present in the selection context (the lookup).
     * @param targets The objects of type <code>type</code>, which
     * this action will use to do whatever it does
     */
    protected abstract void actionPerformed(Collection<? extends T> targets);

    /**
     * Fetches the collection of objects this action will act on and passes
     * them to <code>actionPerformed(Collection<? extends T>).
     * @param ignored The action event.  Ignored.
     */
    public final void actionPerformed(ActionEvent ignored) {
        getStub().actionPerformed(null);
    }

    /**
     * Create an instance of this action over a particular context.  This is
     * used to handle cases such as popup menus, where a popup menu is created
     * against whatever the selection is at the time of its creation;  if the
     * selection changes <i>while</i> the popup is onscreen, we do not want
     * the popup to operate on the new selection; it should operate on the thing
     * the menu was created for.  So for a popup menu, an instance of this
     * action is created over a snapshot-lookup - a snapshot
     * of the context at the moment it is created.
     * @param actionContext The context this action instance should operate on.
     * @return An action specific to the passed Lookup
     */
    @Override
    protected final NbAction internalCreateContextAwareInstance(Lookup actionContext) {
        return createStub (actionContext);
    }

    /**
     * Equals in ContextAction and its subclasses will return true if the
     * passed object is of the same <em>type</em> as the passed object.  This
     * means that two instances of the same subtype of ContextAction are
     * equal if they are the same subtype (this enables actions to work over
     * multiple selection).
     * <p/>
     * If this is not the behavior you want, it is very likely that
     * ContextAction is not the correct superclass for your action.
     *
     * @param o The foreign object
     * @return whether or not this object is functionally equivalent to the
     * passed object
     */
    @Override
    public boolean equals (Object o) {
        return o != null && o.getClass() == getClass();
    }

    /**
     * Returns getClass().hashCode();
     * @return The hash code of this type.
     */
    @Override
    public int hashCode() {
        return getClass().getName().hashCode();
    }

    ActionStub<T> createStub(Lookup actionContext) {
        return new ActionStub<T>(actionContext, this);
    }

    private ActionStub<T> createInternalStub () {
        //Don't synchronize, just ensure we are only called from sync methods
        assert Thread.holdsLock(lock());
        ActionStub<T> result = createStub (Utilities.actionsGlobalContext());
        return result;
    }

    ActionStub<T> getStub() {
        synchronized (lock()) {
            if (stub == null && attached()) {
                stub = createInternalStub();
                stub.addPropertyChangeListener(stubListener);
            }
            return stub == null ? createInternalStub() : stub;
        }
    }

    @Override
    public String toString() {
        return super.toString() + "[name=" + getValue(NAME) + " type=" + type.getName() + "]";
    }

    /**
     * Called when the set of available objects of type <code>type</code>
     * changes.  Override if you incorporate some aspect of the selection
     * in the display name, and need to change it when the collection changes.
     * <p/>
     * Note that this method may be called with an empty collection.
     * @param collection The collection of objects currently in the selection
     * @param instance The action instance which encountered the change.  <i>
     * Note that <code>instance</code> may or may not == this.</i>  It might
     * be an action returned by createContextAwareInstance() for display in
     * a popup menu, which is responding to a change in the context it
     * operates on.  If you need to change the display name, call
     * <code>putValue(NAME, newName)</code> on <code>instance</code>,
     * not on <code>this</code>.  Do not assume <code>instance</code> is
     * an instance of your <code>ContextAction</code> subclass - it may or
     * it may not be.
     */
    protected void change (Collection <? extends T> collection, Action instance) {
        //do nothing
    }

    @Override
    void internalAddNotify() {
        stub = getStub();
        stub.resultChanged(null);
        super.internalAddNotify();
    }

    @Override
    void internalRemoveNotify() {
        try {
            super.internalRemoveNotify();
        } finally {
            stub.removePropertyChangeListener(stubListener);
            stub = null;
        }
    }

    /**
     * Recompute the enabled state of this action.  Call this method if your
     * action depends on some external value to compute its enabled state,
     * when that value changes.
     */
    protected final void refresh() {
        getStub().resultChanged(null);
    }

    //for unit tests
    Collection<? extends T> stubCollection() {
        synchronized (lock()) {
            return stub == null ? null : stub.collection();
        }
    }

    private class StubListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent evt) {
            firePropertyChange (evt.getPropertyName(),
                    evt.getOldValue(), evt.getNewValue());
        }
    }

    /**
     * Create an action which looks for a particular type <code>T</code> in
     * the selection, where T itself is a subclass <code>Lookup.Provider</code>
     * (such as a Node or Project in the NetBeans IDE), and delegates to
     * another action which is sensitive to some object in that lookup.
     * <p/>
     * Such actions can be chained to any depth of lookup providers;
     * you only need to write a ContextAction that is sensitive to the thing
     * you are actually interested in at the end of the chain.
     * So, if you want to write an action which is sensitive to, say, a
     * ClassPathProvider that belongs to the Project found in the selected
     * Node in the global selection, you would do as follows:
     * <pre>
     * ContextAction<ClassPathProvider> theRealAction = new MyContextAction();
     * Action<Node> theGlobalAction = createIndirectAction(Node.class,
     *   createIndirectAction(Project.class, theRealAction));
     * </pre>
     * The returned action will pick up its properties (name, icon, enabled
     * state, etc.) from the action passed in to this one, and it will re-fire
     * property changes from that one.
     * <p>
     * If you need more specific control of enablement logic, either override
     * <code>enabled()</code> in your <code>ContextAction</code>, or use
     * one of the subclasses such as <code>Single</code> or
     * <code>SurviveSelectionChange</code> which provide
     * specific enablement behavior.
     *
     * @param <T> The Lookup.Provider subclass (for example,
     * <code>Node</code>, <code>DataObject</code> or <code>Project</code>)
     * @param lkpProviderType An object which implements Lookup.Provider which
     * will be in the lookup.
     * @param theRealAction The action to invoke if all the conditions are met
     * @param allLookupsMustBeUsable if true, then if any of the Lookup.Providers
     * cannot provide an object of the type <code>theRealAction</code> cares
     * about, do not enable the action.  Otherwise the action will be enabled
     * if any objects <code>theRealAction</code> is interested in are present.
     * If you want to write an action that works on multi-selection, but only
     * if the action can work against all of the selected objects, pass true.
     * If the action should be enabled if only some of the objects are interesting
     * to <code>theRealAction</code>, pass false.
     * @return A ContextAction
     */
    public static <T extends Lookup.Provider, R> ContextAction<T>
            createIndirectAction(Class<T> lkpProviderType, ContextAction<R> theRealAction, boolean allLookupsMustBeUsable) {
        return new IndirectAction<T,R> (lkpProviderType, theRealAction,
                allLookupsMustBeUsable);
    }

    /**
     * Same as <code>createIndirectAction (lkpProviderType, theRealAction,
     * <i>true</i>)</code>.
     * @param <T> The Lookup.Provider subclass (for example,
     * <code>Node</code>, <code>DataObject</code> or <code>Project</code>)
     * @param lkpProviderType An object which implements Lookup.Provider which
     * will be in the lookup.
     * @param theRealAction The action to invoke if all the conditions are met
     * @return A contextAction
     */
    public static <T extends Lookup.Provider, R> ContextAction<T>
            createIndirectAction(Class<T> lkpProviderType, ContextAction<R> theRealAction) {
        return createIndirectAction (lkpProviderType, theRealAction, true);
    }
}
