/*
 * 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 java.lang;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import libcore.util.CollectionUtils;

/**
 * {@code ThreadGroup} is a means of organizing threads into a hierarchical structure.
 * This class is obsolete. See <i>Effective Java</i> Item 73, "Avoid thread groups" for details.
 * @see Thread
 */
public class ThreadGroup implements Thread.UncaughtExceptionHandler {

    // Name of this ThreadGroup
    // VM needs this field name for debugging.
    private String name;

    // Maximum priority for Threads inside this ThreadGroup
    private int maxPriority = Thread.MAX_PRIORITY;

    // The ThreadGroup to which this ThreadGroup belongs
    // VM needs this field name for debugging.
    final ThreadGroup parent;

    /**
     * Weak references to the threads in this group.
     * Access is guarded by synchronizing on this field.
     */
    private final List<WeakReference<Thread>> threadRefs = new ArrayList<WeakReference<Thread>>(5);

    /**
     * View of the threads.
     * Access is guarded by synchronizing on threadRefs.
     */
    private final Iterable<Thread> threads = CollectionUtils.dereferenceIterable(threadRefs, true);

    /**
     * Thread groups. Access is guarded by synchronizing on this field.
     */
    private final List<ThreadGroup> groups = new ArrayList<ThreadGroup>(3);

    // Whether this ThreadGroup is a daemon ThreadGroup or not
    private boolean isDaemon;

    // Whether this ThreadGroup has already been destroyed or not
    private boolean isDestroyed;

    /* the VM uses these directly; do not rename */
    static final ThreadGroup mSystem = new ThreadGroup();
    static final ThreadGroup mMain = new ThreadGroup(mSystem, "main");

    /**
     * Constructs a new {@code ThreadGroup} with the given name. The new {@code ThreadGroup}
     * will be child of the {@code ThreadGroup} to which the calling thread belongs.
     *
     * @param name the name
     * @see Thread#currentThread
     */
    public ThreadGroup(String name) {
        this(Thread.currentThread().getThreadGroup(), name);
    }

    /**
     * Constructs a new {@code ThreadGroup} with the given name, as a child of the
     * given {@code ThreadGroup}.
     *
     * @param parent the parent
     * @param name the name
     * @throws NullPointerException if {@code parent == null}
     * @throws IllegalThreadStateException if {@code parent} has been
     *         destroyed already
     */
    public ThreadGroup(ThreadGroup parent, String name) {
        if (parent == null) {
            throw new NullPointerException("parent == null");
        }
        this.name = name;
        this.parent = parent;
        if (parent != null) {
            parent.add(this);
            this.setMaxPriority(parent.getMaxPriority());
            if (parent.isDaemon()) {
                this.setDaemon(true);
            }
        }
    }

    /**
     * Initialize the special "system" ThreadGroup. Was "main" in Harmony,
     * but we have an additional group above that in Android.
     */
    private ThreadGroup() {
        this.name = "system";
        this.parent = null;
    }

    /**
     * Returns the number of running {@code Thread}s which are children of this thread group,
     * directly or indirectly.
     *
     * @return the number of children
     */
    public int activeCount() {
        int count = 0;
        synchronized (threadRefs) {
            for (Thread thread : threads) {
                if (thread.isAlive()) {
                    count++;
                }
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                count += group.activeCount();
            }
        }
        return count;
    }

    /**
     * Returns the number of {@code ThreadGroup}s which are children of this group,
     * directly or indirectly.
     *
     * @return the number of children
     */
    public int activeGroupCount() {
        int count = 0;
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                // One for this group & the subgroups
                count += 1 + group.activeGroupCount();
            }
        }
        return count;
    }

    /**
     * Adds a {@code ThreadGroup} to this thread group.
     *
     * @param g ThreadGroup to add
     * @throws IllegalThreadStateException if this group has been destroyed already
     */
    private void add(ThreadGroup g) throws IllegalThreadStateException {
        synchronized (groups) {
            if (isDestroyed) {
                throw new IllegalThreadStateException();
            }
            groups.add(g);
        }
    }

    /**
     * Does nothing. The definition of this method depends on the deprecated
     * method {@link #suspend()}. The exact behavior of this call was never
     * specified.
     *
     * @param b Used to control low memory implicit suspension
     * @return {@code true} (always)
     *
     * @deprecated Required deprecated method {@link #suspend()}.
     */
    @Deprecated
    public boolean allowThreadSuspension(boolean b) {
        // Does not apply to this VM, no-op
        return true;
    }

    /**
     * Does nothing.
     */
    public final void checkAccess() {
    }

    /**
     * Destroys this thread group and recursively all its subgroups. It is only legal
     * to destroy a {@code ThreadGroup} that has no threads in it. Any daemon
     * {@code ThreadGroup} is destroyed automatically when it becomes empty (no threads
     * or thread groups in it).
     *
     * @throws IllegalThreadStateException if this thread group or any of its
     *         subgroups has been destroyed already or if it still contains
     *         threads.
     */
    public final void destroy() {
        synchronized (threadRefs) {
            synchronized (groups) {
                if (isDestroyed) {
                    throw new IllegalThreadStateException(
                            "Thread group was already destroyed: "
                            + (this.name != null ? this.name : "n/a"));
                }
                if (threads.iterator().hasNext()) {
                    throw new IllegalThreadStateException(
                            "Thread group still contains threads: "
                            + (this.name != null ? this.name : "n/a"));
                }
                // Call recursively for subgroups
                while (!groups.isEmpty()) {
                    // We always get the first element - remember, when the
                    // child dies it removes itself from our collection. See
                    // below.
                    groups.get(0).destroy();
                }

                if (parent != null) {
                    parent.remove(this);
                }

                // Now that the ThreadGroup is really destroyed it can be tagged as so
                this.isDestroyed = true;
            }
        }
    }

    /*
     * Auxiliary method that destroys this thread group and recursively all its
     * subgroups if this is a daemon ThreadGroup.
     *
     * @see #destroy
     * @see #setDaemon
     * @see #isDaemon
     */
    private void destroyIfEmptyDaemon() {
        // Has to be non-destroyed daemon to make sense
        synchronized (threadRefs) {
            if (isDaemon && !isDestroyed && !threads.iterator().hasNext()) {
                synchronized (groups) {
                    if (groups.isEmpty()) {
                        destroy();
                    }
                }
            }
        }
    }

    /**
     * Iterates over all active threads in this group (and its sub-groups) and
     * stores the threads in the given array. Returns when the array is full or
     * no more threads remain, whichever happens first.
     *
     * <p>Note that this method will silently ignore any threads that don't fit in the
     * supplied array.
     *
     * @param threads the array into which the {@code Thread}s will be copied
     * @return the number of {@code Thread}s that were copied
     */
    public int enumerate(Thread[] threads) {
        return enumerate(threads, true);
    }

    /**
     * Iterates over all active threads in this group (and, optionally, its
     * sub-groups) and stores the threads in the given array. Returns when the
     * array is full or no more threads remain, whichever happens first.
     *
     * <p>Note that this method will silently ignore any threads that don't fit in the
     * supplied array.
     *
     * @param threads the array into which the {@code Thread}s will be copied
     * @param recurse indicates whether {@code Thread}s in subgroups should be
     *        recursively copied as well
     * @return the number of {@code Thread}s that were copied
     */
    public int enumerate(Thread[] threads, boolean recurse) {
        return enumerateGeneric(threads, recurse, 0, true);
    }

    /**
     * Iterates over all thread groups in this group (and its sub-groups) and
     * and stores the groups in the given array. Returns when the array is full
     * or no more groups remain, whichever happens first.
     *
     * <p>Note that this method will silently ignore any thread groups that don't fit in the
     * supplied array.
     *
     * @param groups the array into which the {@code ThreadGroup}s will be copied
     * @return the number of {@code ThreadGroup}s that were copied
     */
    public int enumerate(ThreadGroup[] groups) {
        return enumerate(groups, true);
    }

    /**
     * Iterates over all thread groups in this group (and, optionally, its
     * sub-groups) and stores the groups in the given array. Returns when
     * the array is full or no more groups remain, whichever happens first.
     *
     * <p>Note that this method will silently ignore any thread groups that don't fit in the
     * supplied array.
     *
     * @param groups the array into which the {@code ThreadGroup}s will be copied
     * @param recurse indicates whether {@code ThreadGroup}s in subgroups should be
     *        recursively copied as well or not
     * @return the number of {@code ThreadGroup}s that were copied
     */
    public int enumerate(ThreadGroup[] groups, boolean recurse) {
        return enumerateGeneric(groups, recurse, 0, false);
    }

    /**
     * Copies into <param>enumeration</param> starting at
     * <param>enumerationIndex</param> all Threads or ThreadGroups in the
     * receiver. If <param>recurse</param> is true, recursively enumerate the
     * elements in subgroups.
     *
     * If the array passed as parameter is too small no exception is thrown -
     * the extra elements are simply not copied.
     *
     * @param enumeration array into which the elements will be copied
     * @param recurse Indicates whether subgroups should be enumerated or not
     * @param enumerationIndex Indicates in which position of the enumeration
     *        array we are
     * @param enumeratingThreads Indicates whether we are enumerating Threads or
     *        ThreadGroups
     * @return How many elements were enumerated/copied over
     */
    private int enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex,
            boolean enumeratingThreads) {
        if (enumeratingThreads) {
            synchronized (threadRefs) {
                // walk the references directly so we can iterate in reverse order
                for (int i = threadRefs.size() - 1; i >= 0; --i) {
                    Thread thread = threadRefs.get(i).get();
                    if (thread != null && thread.isAlive()) {
                        if (enumerationIndex >= enumeration.length) {
                            return enumerationIndex;
                        }
                        enumeration[enumerationIndex++] = thread;
                    }
                }
            }
        } else {
            synchronized (groups) {
                for (int i = groups.size() - 1; i >= 0; --i) {
                    if (enumerationIndex >= enumeration.length) {
                        return enumerationIndex;
                    }
                    enumeration[enumerationIndex++] = groups.get(i);
                }
            }
        }

        if (recurse) {
            synchronized (groups) {
                for (ThreadGroup group : groups) {
                    if (enumerationIndex >= enumeration.length) {
                        return enumerationIndex;
                    }
                    enumerationIndex = group.enumerateGeneric(enumeration, recurse,
                            enumerationIndex, enumeratingThreads);
                }
            }
        }
        return enumerationIndex;
    }

    /**
     * Returns the maximum allowed priority for a {@code Thread} in this thread group.
     *
     * @return the maximum priority
     *
     * @see #setMaxPriority
     */
    public final int getMaxPriority() {
        return maxPriority;
    }

    /**
     * Returns the name of this thread group.
     *
     * @return the group's name
     */
    public final String getName() {
        return name;
    }

    /**
     * Returns this thread group's parent {@code ThreadGroup}. It can be null if this
     * is the the root ThreadGroup.
     *
     * @return the parent
     */
    public final ThreadGroup getParent() {
        return parent;
    }

    /**
     * Interrupts every {@code Thread} in this group and recursively in all its
     * subgroups.
     *
     * @see Thread#interrupt
     */
    public final void interrupt() {
        synchronized (threadRefs) {
            for (Thread thread : threads) {
                thread.interrupt();
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                group.interrupt();
            }
        }
    }

    /**
     * Checks whether this thread group is a daemon {@code ThreadGroup}.
     *
     * @return true if this thread group is a daemon {@code ThreadGroup}
     *
     * @see #setDaemon
     * @see #destroy
     */
    public final boolean isDaemon() {
        return isDaemon;
    }

    /**
     * Checks whether this thread group has already been destroyed.
     *
     * @return true if this thread group has already been destroyed
     * @see #destroy
     */
    public synchronized boolean isDestroyed() {
        return isDestroyed;
    }

    /**
     * Outputs to {@code System.out} a text representation of the
     * hierarchy of {@code Thread}s and {@code ThreadGroup}s in this thread group (and recursively).
     * Proper indentation is used to show the nesting of groups inside groups
     * and threads inside groups.
     */
    public void list() {
        // We start in a fresh line
        System.out.println();
        list(0);
    }

    /*
     * Outputs to {@code System.out}a text representation of the
     * hierarchy of Threads and ThreadGroups in this thread group (and recursively).
     * The indentation will be four spaces per level of nesting.
     *
     * @param levels How many levels of nesting, so that proper indentation can
     * be output.
     */
    private void list(int levels) {
        indent(levels);
        System.out.println(this.toString());

        ++levels;
        synchronized (threadRefs) {
            for (Thread thread : threads) {
                indent(levels);
                System.out.println(thread);
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                group.list(levels);
            }
        }
    }

    private void indent(int levels) {
        for (int i = 0; i < levels; i++) {
            System.out.print("    "); // 4 spaces for each level
        }
    }

    /**
     * Checks whether this thread group is a direct or indirect parent group of a
     * given {@code ThreadGroup}.
     *
     * @param g the potential child {@code ThreadGroup}
     * @return true if this thread group is parent of {@code g}
     */
    public final boolean parentOf(ThreadGroup g) {
        while (g != null) {
            if (this == g) {
                return true;
            }
            g = g.parent;
        }
        return false;
    }

    /**
     * Removes an immediate subgroup.
     *
     * @param g ThreadGroup to remove
     *
     * @see #add(Thread)
     * @see #add(ThreadGroup)
     */
    private void remove(ThreadGroup g) {
        synchronized (groups) {
            for (Iterator<ThreadGroup> i = groups.iterator(); i.hasNext(); ) {
                ThreadGroup threadGroup = i.next();
                if (threadGroup.equals(g)) {
                    i.remove();
                    break;
                }
            }
        }
        destroyIfEmptyDaemon();
    }

    /**
     * Resumes every thread in this group and recursively in all its
     * subgroups.
     *
     * @see Thread#resume
     * @see #suspend
     *
     * @deprecated Requires deprecated method {@link Thread#resume()}.
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    public final void resume() {
        synchronized (threadRefs) {
            for (Thread thread : threads) {
                thread.resume();
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                group.resume();
            }
        }
    }

    /**
     * Sets whether this is a daemon {@code ThreadGroup} or not. Daemon
     * thread groups are automatically destroyed when they become empty.
     *
     * @param isDaemon the new value
     * @see #isDaemon
     * @see #destroy
     */
    public final void setDaemon(boolean isDaemon) {
        this.isDaemon = isDaemon;
    }

    /**
     * Configures the maximum allowed priority for a {@code Thread} in this group and
     * recursively in all its subgroups.
     *
     * <p>A caller can never increase the maximum priority of a thread group.
     * Such an attempt will not result in an exception, it will
     * simply leave the thread group with its current maximum priority.
     *
     * @param newMax the new maximum priority to be set
     *
     * @throws IllegalArgumentException if the new priority is greater than
     *         Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY
     *
     * @see #getMaxPriority
     */
    public final void setMaxPriority(int newMax) {
        if (newMax <= this.maxPriority) {
            if (newMax < Thread.MIN_PRIORITY) {
                newMax = Thread.MIN_PRIORITY;
            }

            int parentPriority = parent == null ? newMax : parent.getMaxPriority();
            this.maxPriority = parentPriority <= newMax ? parentPriority : newMax;
            synchronized (groups) {
                for (ThreadGroup group : groups) {
                    group.setMaxPriority(newMax);
                }
            }
        }
    }

    /**
     * Stops every thread in this group and recursively in all its subgroups.
     *
     * @see Thread#stop()
     * @see Thread#stop(Throwable)
     * @see ThreadDeath
     *
     * @deprecated Requires deprecated method {@link Thread#stop()}.
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    public final void stop() {
        if (stopHelper()) {
            Thread.currentThread().stop();
        }
    }

    @SuppressWarnings("deprecation")
    private boolean stopHelper() {
        boolean stopCurrent = false;
        synchronized (threadRefs) {
            Thread current = Thread.currentThread();
            for (Thread thread : threads) {
                if (thread == current) {
                    stopCurrent = true;
                } else {
                    thread.stop();
                }
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                stopCurrent |= group.stopHelper();
            }
        }
        return stopCurrent;
    }

    /**
     * Suspends every thread in this group and recursively in all its
     * subgroups.
     *
     * @see Thread#suspend
     * @see #resume
     *
     * @deprecated Requires deprecated method {@link Thread#suspend()}.
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    public final void suspend() {
        if (suspendHelper()) {
            Thread.currentThread().suspend();
        }
    }

    @SuppressWarnings("deprecation")
    private boolean suspendHelper() {
        boolean suspendCurrent = false;
        synchronized (threadRefs) {
            Thread current = Thread.currentThread();
            for (Thread thread : threads) {
                if (thread == current) {
                    suspendCurrent = true;
                } else {
                    thread.suspend();
                }
            }
        }
        synchronized (groups) {
            for (ThreadGroup group : groups) {
                suspendCurrent |= group.suspendHelper();
            }
        }
        return suspendCurrent;
    }

    @Override
    public String toString() {
        return getClass().getName() + "[name=" + getName()
                + ",maxPriority=" + getMaxPriority() + "]";
    }

    /**
     * Handles uncaught exceptions. Any uncaught exception in any {@code Thread}
     * is forwarded to the thread's {@code ThreadGroup} by invoking this
     * method.
     *
     * <p>New code should use {@link Thread#setUncaughtExceptionHandler} instead of thread groups.
     *
     * @param t the Thread that terminated with an uncaught exception
     * @param e the uncaught exception itself
     */
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else if (Thread.getDefaultUncaughtExceptionHandler() != null) {
            // TODO The spec is unclear regarding this. What do we do?
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            // No parent group, has to be 'system' Thread Group
            e.printStackTrace(System.err);
        }
    }

    /**
     * Called by the Thread constructor.
     */
    final void addThread(Thread thread) throws IllegalThreadStateException {
        synchronized (threadRefs) {
            if (isDestroyed) {
                throw new IllegalThreadStateException();
            }
            threadRefs.add(new WeakReference<Thread>(thread));
        }
    }

    /**
     * Called by the VM when a Thread dies.
     */
    final void removeThread(Thread thread) throws IllegalThreadStateException {
        synchronized (threadRefs) {
            for (Iterator<Thread> i = threads.iterator(); i.hasNext(); ) {
                if (i.next().equals(thread)) {
                    i.remove();
                    break;
                }
            }
        }
        destroyIfEmptyDaemon();
    }
}
