package org.multiverse.utils;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A customizable implementation of the {@link java.util.concurrent.ThreadFactory}. The new
 * java.util.concurrency library provides a {@link java.util.concurrent.ThreadFactory} interface, which is a great
 * thing, but strangely enough it doesn't provide an customizable implementation.
 * <p/>
 * If the maximum priority of the ThreadGroup is changed after this StandardThreadFactory is
 * constructed, then this will be ignored by the StandardThreadFactory. So it could be that a
 * StandardThreadFactory has a higher priority than the ThreadGroup allowed. What will happen at
 * construction?
 *
 * @author Peter Veentjer.
 */
@SuppressWarnings({"ClassWithTooManyConstructors"})
public final class StandardThreadFactory implements ThreadFactory {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);

    private static String createThreadGroupName() {
        return Integer.toString(poolNumber.getAndIncrement());
    }

    private final ThreadGroup threadGroup;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    private final boolean daemon;
    private volatile int priority;

    /**
     * Constructs a new StandardThreadFactory with a Thread.NORM_PRIORITY as priority and a newly
     * created ThreadGroup. The created Threads are not daemons.
     */
    public StandardThreadFactory() {
        this(Thread.NORM_PRIORITY, createThreadGroupName());
    }

    /**
     * Constructs a new StandardThreadFactory with a Thread.NORM_PRIORITY as priority and with a
     * newly created ThreadGroup with the given groupName. The created threads are not daemons.
     *
     * @param groupName the name of the ThreadGroup (is allowed to be null).
     */
    public StandardThreadFactory(String groupName) {
        this(Thread.NORM_PRIORITY, groupName);
    }

    /**
     * Constructs a new StandardThreadFactory with the given priority. The created threads are
     * not daemons.
     *
     * @param priority the priority of th threads.
     * @throws IllegalArgumentException if the priority is not valid.
     */
    public StandardThreadFactory(int priority) {
        this(priority, createThreadGroupName());
    }

    /**
     * Constructs a new StandardThreadFactory with the given priority and with a newly created
     * ThreadGroup with the given groupName.  The created threads are not daemons.
     *
     * @param priority  the priority of the threads this StandardThreadFactory is going to createReference.
     * @param groupName the name of the ThreadGroup (is allowed to be null).
     * @throws IllegalArgumentException if priority is not a valid value.
     */
    public StandardThreadFactory(int priority, String groupName) {
        this(priority, new ThreadGroup(Thread.currentThread().getThreadGroup(), groupName), false);
    }

    /**
     * Constructs a new StandardThreadFactory with the given priority and are part of the give
     * ThreadGroup. The created threads are not daemons.
     *
     * @param priority    the priority of the created threads.
     * @param threadGroup the ThreadGroup the created Threads are part of.
     * @throws NullPointerException     if threadGroup is null
     * @throws IllegalArgumentException if the priority is not valid value.
     */
    public StandardThreadFactory(int priority, ThreadGroup threadGroup) {
        this(priority, threadGroup, false);
    }

    /**
     * Creates a new StandardThreadFactory with the given priority and if the threads are daemons
     *
     * @param priority the priority of the thread.
     * @param daemon   if the thread is a daemon.
     */
    public StandardThreadFactory(int priority, boolean daemon) {
        this(priority, new ThreadGroup(
                Thread.currentThread().getThreadGroup(),
                createThreadGroupName()),
                daemon);
    }

    /**
     * Constructs a new StandardThreadFactory with the given priority and ThreadGroup.
     *
     * @param priority    the priority of the threads this StandardThreadFactory is going to createReference.
     * @param threadGroup the ThreadGroup the thread is part of
     * @param daemon      if the thread should be a daemon.
     * @throws IllegalArgumentException if the priority is not valid.
     * @throws NullPointerException     if threadGroup is null.
     */
    public StandardThreadFactory(int priority, ThreadGroup threadGroup, boolean daemon) {
        if (threadGroup == null) {
            throw new NullPointerException();
        }

        this.threadGroup = threadGroup;
        ensureValidPriority(priority);
        this.priority = priority;

        this.daemon = daemon;
        this.namePrefix = threadGroup.getName() + "-thread#";
    }

    private void ensureValidPriority(int priority) {
        if (priority < Thread.MIN_PRIORITY) {
            throw new IllegalArgumentException("priority can`t be smaller than: " +
                    Thread.MIN_PRIORITY + ", priority was: " + priority);
        }

        if (priority > Thread.MAX_PRIORITY) {
            throw new IllegalArgumentException("priority can`t be greater than: " +
                    Thread.MAX_PRIORITY + ", priority was: " + priority);
        }

        if (priority > threadGroup.getMaxPriority()) {
            throw new IllegalArgumentException(
                    "priority can`t be greater than threadGroup.maxPriority: " +
                            threadGroup.getMaxPriority() + ", priority was: " + priority);
        }
    }

    /**
     * Returns true if this StandardThreadFactory is producing daemon threads, false
     * otherwise.
     *
     * @return true if this StandardThreadFactory is producing daemon threads, false
     *         otherwise.
     */
    public boolean isProducingDaemons() {
        return daemon;
    }

    /**
     * Returns the ThreadGroup of the created Threads.
     *
     * @return the ThreadGroup of the created Threads.
     */
    public ThreadGroup getThreadGroup() {
        return threadGroup;
    }

    /**
     * Returns the priority of created Threads. This is a value ranging from
     * Thread.MIN_PRIORITY to Thread.MAX_PRIORITY.
     *
     * @return the priority of created Threads.
     */
    public int getPriority() {
        return priority;
    }

    /**
     * Sets the priority of the threads. This will only effect newly created Threads. A value must
     * be getAndSet ranging from Thread.MIN_PRIORITY and Thread.MAX_PRIORITY.
     * <p/>
     * This call is not completely threadsafe, the following scenario could happen:
     * <ol>
     * <li>thread1 call setPriority and newTransaction the checking part of this method and the check passes</li>
     * <li>thread2 calls the ThreadGroup directly and lowers the priority</li>
     * <li>thread1 sets the priority on this StandardThreadFactory</li>
     * </ol>
     * The consequence is that the priority of this StandardThreadFactory is higher than the maximum
     * priority of the ThreadGroup and this means that thread creation could fail because threads are
     * created with a too high priority. This race problem is very hard to prevent because the check/getAndSet
     * can't be done atomically because the ThreadGroup is exposed.
     *
     * @param priority the new priority.
     * @throws IllegalArgumentException if priority is smaller than {@link Thread#MIN_PRIORITY} or
     *                                  larger than {@link Thread#MAX_PRIORITY} or larger than the
     *                                  maximum priority of the ThreadGroup.
     */
    public void setPriority(int priority) {
        ensureValidPriority(priority);
        this.priority = priority;
    }

    @Override
    public Thread newThread(Runnable runnable) {
        if (runnable == null) throw new NullPointerException();

        String threadName = namePrefix + threadNumber.getAndIncrement();
        Thread thread = new Thread(threadGroup, runnable, threadName);
        thread.setDaemon(daemon);
        thread.setPriority(priority);
        return thread;
    }
}
