// TargetChain.java
// By Ned Etcode
// Copyright 1995, 1996 Netscape Communications Corp. All rights reserved.


package netscape.application;
import netscape.util.*;

/** Object subclass used to locate a Target within an application that is
  * capable of performing a specific command.  When asked to perform a command,
  * the TargetChain asks each of its potential Targets, in turn, whether or
  * not they implement the ExtendedTarget interface, and if so, calls their
  * <b>canPerformCommand()</b> method to determine whether or not they can
  * perform the specified command.  The TargetChain forwards the command on
  * to the first object that can perform the command.  The TargetChain
  * returned by the static <b>applicationChain()</b> method contains the
  * following potential Targets:
  * <OL><LI>Each of the Targets added using <b>addTarget()</b> with
  * <b>atFront</b> == <b>true</b>
  * <LI>The current focusedView  <LI>The main Window's owner
  * <LI>The main Window <LI>The "first" RootView  <LI>The Application
  * <LI>Each of the Targets added using <b>addTarget()</b> with <b>atFront</b>
  * == false </OL>
  * The TargetChain is useful in situations where the Target that should
  * receive a command is context sensitive.  For example, a Menu item
  * representing the "cut" command should send its command to the current
  * selection.  If an application has multiple TextFields, the TextField that
  * should receive the command will depend upon which TextField the user has
  * decided to edit.  By setting the Menu item's Target to the application's
  * TargetChain, the application can configure the Menu item's Target once,
  * with the assurance that the TargetChain will locate and forward the command
  * to the appropriate object.
  * @see Target
  */

public class TargetChain implements ExtendedTarget {
    private Vector preTargets = new Vector();
    private Vector postTargets = new Vector();
    private static TargetChain applicationChain;

    private TargetChain() {
        super();
    }

    /** Returns the Application global TargetChain.
        */
    public static TargetChain applicationChain() {
        if (applicationChain == null) {
            applicationChain = new TargetChain();
        }
        return applicationChain;
    }

    /** Adds <b>target</b> to the TargetChain. If <b>atFront</b> is
      * <b>true</b>,the TargetChain will query <b>target</b> before any of the
      * ExtendedTargets currently in its list. Otherwise, the TargetChain will
      * query <b>target</b> only after all current ExtendedTargets.
      */
    public synchronized void addTarget(ExtendedTarget target,
                                       boolean atFront) {
        if (atFront) {
            preTargets.insertElementAt(target, 0);
        } else {
            postTargets.addElement(target);
        }
    }

    /** Removes <b>target</b> from the TargetChain (only if <b>target</b> was
      * added using <b>addTarget()</b>).
      * @see #addTarget
      */
    public synchronized void removeTarget(ExtendedTarget target) {
        preTargets.removeElement(target);
        postTargets.removeElement(target);
    }

    /** Returns the first Target that performs <B>command</B>.
      */
    public synchronized Target targetForCommand(String command) {
        int             i, size;
        Application     app;
        View            focusedView;
        RootView  rootView;
        Window          documentWindow, mainWindow;
        WindowOwner     documentOwner, mainOwner;

        size = preTargets.size();
        for (i = 0; i < size; i++) {
            ExtendedTarget target = (ExtendedTarget) preTargets.elementAt(i);

            if (target.canPerformCommand(command)) {
                return target;
            }
        }

        app = Application.application();
        rootView = app.firstRootView();
        if (rootView != null) {
            focusedView = rootView.focusedView();
            if ((focusedView instanceof ExtendedTarget) &&
                (((ExtendedTarget)focusedView).canPerformCommand(command))) {
                return (Target)focusedView;
            }

            documentWindow = rootView.documentWindow();
            documentWindow = null;      // ALERT!!
            if (documentWindow != null) {
                documentOwner = documentWindow.owner();
                if ((documentOwner != null) &&
                    (documentOwner instanceof ExtendedTarget) &&
                    ((ExtendedTarget)documentOwner).canPerformCommand(
                                                                command)) {
                    return (Target)documentOwner;
                }
                if ((documentWindow instanceof ExtendedTarget) &&
                    (((ExtendedTarget)documentWindow).canPerformCommand(
                        command))) {
                    return (Target)documentWindow;
                }
            }

            mainWindow = rootView.mainWindow();
            if ((mainWindow != null) && (mainWindow != documentWindow)) {
                mainOwner = mainWindow.owner();
                if ((mainOwner != null) &&
                    (mainOwner instanceof ExtendedTarget) &&
                    (((ExtendedTarget)mainOwner).canPerformCommand(command))) {
                    return (Target)mainOwner;
                }
                if ((mainWindow instanceof ExtendedTarget) &&
                    ((ExtendedTarget)mainWindow).canPerformCommand(command)) {
                    return (Target)mainWindow;
                }
            }

            if ((rootView instanceof ExtendedTarget) &&
                ((ExtendedTarget)rootView).canPerformCommand(command)) {
                return (Target)rootView;
            }
        }

        if ((app instanceof ExtendedTarget) &&
            (((ExtendedTarget)app).canPerformCommand(command))) {
            return (Target)app;
        }

        size = postTargets.size();
        for (i = 0; i < size; i++) {
            ExtendedTarget target = (ExtendedTarget) postTargets.elementAt(i);

            if (target.canPerformCommand(command)) {
                return target;
            }
        }

        return null;
    }

    /** Returns <b>true</b> if any of the TargetChain's Targets can perform
      * <B>command</B>.
      */
    public boolean canPerformCommand(String command) {
        return (targetForCommand(command) != null);
    }

    /** Forwards the performCommand message to the first Target in the chain
      * that can perform <B>command</B>.
      */
    public void performCommand(String command, Object data) {
        Target target = targetForCommand(command);

        if (target != null) {
            target.performCommand(command, data);
        }
    }
}
