/*
 * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.java.accessibility.util;

import java.util.*;
import java.beans.*;
import java.awt.*;
import java.awt.event.*;
import javax.accessibility.*;

/**
 * <P>{@code AccessibilityEventMonitor} implements a PropertyChange listener
 * on every UI object that implements interface {@code Accessible} in the Java
 * Virtual Machine.  The events captured by these listeners are made available
 * through listeners supported by {@code AccessibilityEventMonitor}.
 * With this, all the individual events on each of the UI object
 * instances are funneled into one set of PropertyChange listeners.
 * <p>This class depends upon {@link EventQueueMonitor}, which provides the base
 * level support for capturing the top-level containers as they are created.
 *
 */

@jdk.Exported
public class AccessibilityEventMonitor {

    // listeners
    /**
     * The current list of registered {@link java.beans.PropertyChangeListener
     * PropertyChangeListener} classes.
     *
     * @see #addPropertyChangeListener
     * @see #removePropertyChangeListener
     */
    static protected final AccessibilityListenerList listenerList =
        new AccessibilityListenerList();


    /**
     * The actual listener that is installed on the component instances.
     * This listener calls the other registered listeners when an event
     * occurs.  By doing things this way, the actual number of listeners
     * installed on a component instance is drastically reduced.
     */
    static protected final AccessibilityEventListener accessibilityListener =
        new AccessibilityEventListener();

    /**
     * Adds the specified listener to receive all PropertyChange events on
     * each UI object instance in the Java Virtual Machine as they occur.
     * <P>Note: This listener is automatically added to all component
     * instances created after this method is called.  In addition, it
     * is only added to UI object instances that support this listener type.
     *
     * @param l the listener to add
     *
     * @see #removePropertyChangeListener
     */
    static public void addPropertyChangeListener(PropertyChangeListener l) {
        if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
            accessibilityListener.installListeners();
        }
        listenerList.add(PropertyChangeListener.class, l);
    }

    /**
     * Removes the specified listener so it no longer receives PropertyChange
     * events when they occur.
     * @see #addPropertyChangeListener
     * @param l the listener to remove
     */
    static public void removePropertyChangeListener(PropertyChangeListener l) {
        listenerList.remove(PropertyChangeListener.class, l);
        if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
            accessibilityListener.removeListeners();
        }
    }


    /**
     * AccessibilityEventListener is the class that does all the work for
     * AccessibilityEventMonitor.  It is not intended for use by any other
     * class except AccessibilityEventMonitor.
     *
     */

    static class AccessibilityEventListener implements TopLevelWindowListener,
                PropertyChangeListener {

        /**
         * Create a new instance of this class and install it on each component
         * instance in the virtual machine that supports any of the currently
         * registered listeners in AccessibilityEventMonitor.  Also registers
         * itself as a TopLevelWindowListener with EventQueueMonitor so it can
         * automatically add new listeners to new components.
         * @see EventQueueMonitor
         * @see AccessibilityEventMonitor
         */
        public AccessibilityEventListener() {
            EventQueueMonitor.addTopLevelWindowListener(this);
        }

        /**
         * Installs PropertyChange listeners on all Accessible objects based
         * upon the current topLevelWindows cached by EventQueueMonitor.
         * @see EventQueueMonitor
         * @see AWTEventMonitor
         */
        protected void installListeners() {
            Window topLevelWindows[] = EventQueueMonitor.getTopLevelWindows();
            if (topLevelWindows != null) {
                for (int i = 0; i < topLevelWindows.length; i++) {
                    if (topLevelWindows[i] instanceof Accessible) {
                        installListeners((Accessible) topLevelWindows[i]);
                    }
                }
            }
        }

        /**
         * Installs PropertyChange listeners to the Accessible object, and it's
         * children (so long as the object isn't of TRANSIENT state).
         * @param a the Accessible object to add listeners to
         */
        protected void installListeners(Accessible a) {
            installListeners(a.getAccessibleContext());
        }

        /**
         * Installs PropertyChange listeners to the AccessibleContext object,
         * and it's * children (so long as the object isn't of TRANSIENT state).
         * @param a the Accessible object to add listeners to
         */
        private void installListeners(AccessibleContext ac) {

            if (ac != null) {
                AccessibleStateSet states = ac.getAccessibleStateSet();
                if (!states.contains(AccessibleState.TRANSIENT)) {
                    ac.addPropertyChangeListener(this);
                    /*
                     * Don't add listeners to transient children. Components
                     * with transient children should return an AccessibleStateSet
                     * containing AccessibleState.MANAGES_DESCENDANTS. Components
                     * may not explicitly return the MANAGES_DESCENDANTS state.
                     * In this case, don't add listeners to the children of
                     * lists, tables and trees.
                     */
                    AccessibleStateSet set = ac.getAccessibleStateSet();
                    if (set.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
                        return;
                    }
                    AccessibleRole role = ac.getAccessibleRole();
                    if (role == AccessibleRole.LIST ||
                        role == AccessibleRole.TREE) {
                        return;
                    }
                    if (role == AccessibleRole.TABLE) {
                        // handle Oracle tables containing tables
                        Accessible child = ac.getAccessibleChild(0);
                        if (child != null) {
                            AccessibleContext ac2 = child.getAccessibleContext();
                            if (ac2 != null) {
                                role = ac2.getAccessibleRole();
                                if (role != null && role != AccessibleRole.TABLE) {
                                    return;
                                }
                            }
                        }
                    }
                    int count = ac.getAccessibleChildrenCount();
                    for (int i = 0; i < count; i++) {
                        Accessible child = ac.getAccessibleChild(i);
                        if (child != null) {
                            installListeners(child);
                        }
                    }
                }
            }
        }

        /**
         * Removes PropertyChange listeners on all Accessible objects based
         * upon the topLevelWindows cached by EventQueueMonitor.
         * @param eventID the event ID
         * @see EventID
         */
        protected void removeListeners() {
            Window topLevelWindows[] = EventQueueMonitor.getTopLevelWindows();
            if (topLevelWindows != null) {
                for (int i = 0; i < topLevelWindows.length; i++) {
                    if (topLevelWindows[i] instanceof Accessible) {
                        removeListeners((Accessible) topLevelWindows[i]);
                    }
                }
            }
        }

        /**
         * Removes PropertyChange listeners for the given Accessible object,
         * it's children (so long as the object isn't of TRANSIENT state).
         * @param a the Accessible object to remove listeners from
         */
        protected void removeListeners(Accessible a) {
            removeListeners(a.getAccessibleContext());
        }

        /**
         * Removes PropertyChange listeners for the given AccessibleContext
         * object, it's children (so long as the object isn't of TRANSIENT
         * state).
         * @param a the Accessible object to remove listeners from
         */
        private void removeListeners(AccessibleContext ac) {


            if (ac != null) {
                // Listeners are not added to transient components.
                AccessibleStateSet states = ac.getAccessibleStateSet();
                if (!states.contains(AccessibleState.TRANSIENT)) {
                    ac.removePropertyChangeListener(this);
                    /*
                     * Listeners are not added to transient children. Components
                     * with transient children should return an AccessibleStateSet
                     * containing AccessibleState.MANAGES_DESCENDANTS. Components
                     * may not explicitly return the MANAGES_DESCENDANTS state.
                     * In this case, don't remove listeners from the children of
                     * lists, tables and trees.
                     */
                    if (states.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
                        return;
                    }
                    AccessibleRole role = ac.getAccessibleRole();
                    if (role == AccessibleRole.LIST ||
                        role == AccessibleRole.TABLE ||
                        role == AccessibleRole.TREE) {
                        return;
                    }
                    int count = ac.getAccessibleChildrenCount();
                    for (int i = 0; i < count; i++) {
                        Accessible child = ac.getAccessibleChild(i);
                        if (child != null) {
                            removeListeners(child);
                        }
                    }
                }
            }
        }

        /********************************************************************/
        /*                                                                  */
        /* Listener Interface Methods                                       */
        /*                                                                  */
        /********************************************************************/

        /* TopLevelWindow Methods ***************************************/

        /**
         * Called when top level window is created.
         * @see EventQueueMonitor
         * @see EventQueueMonitor#addTopLevelWindowListener
         */
        public void topLevelWindowCreated(Window w) {
            if (w instanceof Accessible) {
                installListeners((Accessible) w);
            }
        }

        /**
         * Called when top level window is destroyed.
         * @see EventQueueMonitor
         * @see EventQueueMonitor#addTopLevelWindowListener
         */
        public void topLevelWindowDestroyed(Window w) {
            if (w instanceof Accessible) {
                removeListeners((Accessible) w);
            }
        }


        /* PropertyChangeListener Methods **************************************/

        public void propertyChange(PropertyChangeEvent e) {
            // propogate the event
            Object[] listeners =
                    AccessibilityEventMonitor.listenerList.getListenerList();
            for (int i = listeners.length-2; i>=0; i-=2) {
                if (listeners[i]==PropertyChangeListener.class) {
                    ((PropertyChangeListener)listeners[i+1]).propertyChange(e);
                }
            }

            // handle childbirth/death
            String name = e.getPropertyName();
            if (name.compareTo(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) == 0) {
                Object oldValue = e.getOldValue();
                Object newValue = e.getNewValue();

                if ((oldValue == null) ^ (newValue == null)) { // one null, not both
                    if (oldValue != null) {
                        // this Accessible is a child that's going away
                        if (oldValue instanceof Accessible) {
                            Accessible a = (Accessible) oldValue;
                            removeListeners(a.getAccessibleContext());
                        } else if (oldValue instanceof AccessibleContext) {
                            removeListeners((AccessibleContext) oldValue);
                        }
                    } else if (newValue != null) {
                        // this Accessible is a child was just born
                        if (newValue instanceof Accessible) {
                            Accessible a = (Accessible) newValue;
                            installListeners(a.getAccessibleContext());
                        } else if (newValue instanceof AccessibleContext) {
                            installListeners((AccessibleContext) newValue);
                        }
                    }
                } else {
                    System.out.println("ERROR in usage of PropertyChangeEvents for: " + e.toString());
                }
            }
        }
    }
}

/*
 * workaround for no public AccessibleState constructor
 */
class _AccessibleState extends AccessibleState {
    /**
     * Indicates this object is responsible for managing its
     * subcomponents.  This is typically used for trees and tables
     * that have a large number of subcomponents and where the
     * objects are created only when needed and otherwise remain virtual.
     * The application should not manage the subcomponents directly.
     */
    public static final _AccessibleState MANAGES_DESCENDANTS
        = new _AccessibleState ("managesDescendants");

    /**
     * Creates a new AccessibleState using the given locale independent key.
     * This should not be a public method.  Instead, it is used to create
     * the constants in this file to make it a strongly typed enumeration.
     * Subclasses of this class should enforce similar policy.
     * <p>
     * The key String should be a locale independent key for the state.
     * It is not intended to be used as the actual String to display
     * to the user.  To get the localized string, use toDisplayString.
     *
     * @param key the locale independent name of the state.
     * @see AccessibleBundle#toDisplayString
     */
    protected _AccessibleState(String key) {
        super(key);
    }
}
