/**
 * Copyright 2015 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */

package com.jogamp.common.util;

/**
 * Interface exposing {@link java.lang.Thread#interrupt()} source,
 * intended for {@link java.lang.Thread} specializations.
 * @since 2.3.2
 */
public interface InterruptSource {
    /**
     * Returns the source of the last {@link #interrupt()} call.
     * @param clear if true, issues {@link #clearInterruptSource()}
     */
    Throwable getInterruptSource(final boolean clear);

    /**
     * Returns the count of {@link java.lang.Thread#interrupt()} calls.
     * @param clear if true, issues {@link #clearInterruptSource()}
     */
    int getInterruptCounter(final boolean clear);

    /**
     * Clears source and count of {@link java.lang.Thread#interrupt()} calls, if any.
     */
    void clearInterruptSource();

    public static class Util {
        /**
         * Casts given {@link java.lang.Thread} to {@link InterruptSource}
         * if applicable, otherwise returns {@code null}.
         */
        public static InterruptSource get(final java.lang.Thread t) {
            if(t instanceof InterruptSource) {
                return (InterruptSource)t;
            } else {
                return null;
            }
        }
        /**
         * Casts current {@link java.lang.Thread} to {@link InterruptSource}
         * if applicable, otherwise returns {@code null}.
         */
        public static InterruptSource currentThread() {
            return get(java.lang.Thread.currentThread());
        }
    }

    /**
     * {@link java.lang.Thread} specialization implementing {@link InterruptSource}
     * to track {@link java.lang.Thread#interrupt()} calls.
     * @since 2.3.2
     */
    public static class Thread extends java.lang.Thread implements InterruptSource {
        volatile Throwable interruptSource = null;
        volatile int interruptCounter = 0;
        final Object sync = new Object();

        /**
         * See {@link Thread#Thread(} for details.
         */
        public Thread() {
            super();
        }
        /**
         * See {@link Thread#Thread(Runnable)} for details.
         * @param target explicit {@link Runnable}, may be {@code null}
         */
        public Thread(final Runnable target) {
            super(target);
        }
        /**
         * See {@link Thread#Thread(ThreadGroup, Runnable)} for details.
         * @param tg explicit {@link ThreadGroup}, may be {@code null}
         * @param target explicit {@link Runnable}, may be {@code null}
         */
        public Thread(final ThreadGroup tg, final Runnable target) {
            super(tg, target);
        }
        /**
         * See {@link Thread#Thread(ThreadGroup, Runnable, String)} for details.
         * @param tg explicit {@link ThreadGroup}, may be {@code null}
         * @param target explicit {@link Runnable}, may be {@code null}
         * @param name explicit name of thread, must not be {@code null}
         */
        public Thread(final ThreadGroup tg, final Runnable target, final String name) {
            super(tg, target, name);
        }

        /**
         * Depending on whether {@code name} is null, either
         * {@link #Thread(ThreadGroup, Runnable, String)} or
         * {@link #Thread(ThreadGroup, Runnable)} is being utilized.
         * @param tg explicit {@link ThreadGroup}, may be {@code null}
         * @param target explicit {@link Runnable}, may be {@code null}
         * @param name explicit name of thread, may be {@code null}
         */
        public static Thread create(final ThreadGroup tg, final Runnable target, final String name) {
            return null != name ? new Thread(tg, target, name) : new Thread(tg, target);
        }

        @Override
        public final Throwable getInterruptSource(final boolean clear) {
            synchronized(sync) {
                final Throwable r = interruptSource;
                if( clear ) {
                    clearInterruptSource();
                }
                return r;
            }
        }
        @Override
        public final int getInterruptCounter(final boolean clear) {
            synchronized(sync) {
                final int r = interruptCounter;
                if( clear ) {
                    clearInterruptSource();
                }
                return r;
            }
        }
        @Override
        public final void clearInterruptSource() {
            synchronized(sync) {
                interruptCounter = 0;
                interruptSource = null;
            }
        }
        @Override
        public final void interrupt() {
            synchronized(sync) {
                interruptCounter++;
                interruptSource = new Throwable(getName()+".interrupt() #"+interruptCounter);
            }
            super.interrupt();
        }
    }
}
