/*
 * 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 org.apache.cassandra.simulator.systems;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import org.apache.cassandra.concurrent.ExecutorPlus;
import org.apache.cassandra.concurrent.LocalAwareExecutorPlus;
import org.apache.cassandra.concurrent.LocalAwareSequentialExecutorPlus;
import org.apache.cassandra.concurrent.ScheduledExecutorPlus;
import org.apache.cassandra.concurrent.SequentialExecutorPlus;
import org.apache.cassandra.concurrent.SingleThreadExecutorPlus;
import org.apache.cassandra.concurrent.SyncFutureTask;
import org.apache.cassandra.concurrent.TaskFactory;
import org.apache.cassandra.simulator.OrderOn;
import org.apache.cassandra.simulator.systems.InterceptingAwaitable.InterceptingCondition;
import org.apache.cassandra.simulator.systems.NotifyThreadPaused.AwaitPaused;
import org.apache.cassandra.utils.Shared;
import org.apache.cassandra.utils.WithResources;
import org.apache.cassandra.utils.concurrent.Condition;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.NotScheduledFuture;
import org.apache.cassandra.utils.concurrent.RunnableFuture;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;

import static java.util.Collections.newSetFromMap;
import static java.util.Collections.synchronizedMap;
import static java.util.Collections.synchronizedSet;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_SIMULATOR_DEBUG;
import static org.apache.cassandra.simulator.systems.InterceptibleThread.runDeterministic;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_DAEMON;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_TASK;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_TIMEOUT;
import static org.apache.cassandra.simulator.systems.SimulatedExecution.callable;
import static org.apache.cassandra.simulator.systems.SimulatedTime.Global.localToGlobalNanos;
import static org.apache.cassandra.simulator.systems.SimulatedTime.Global.localToRelativeNanos;
import static org.apache.cassandra.simulator.systems.SimulatedTime.Global.relativeToGlobalNanos;
import static org.apache.cassandra.simulator.systems.SimulatedTime.Global.relativeToLocalNanos;
import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;

// An executor whose tasks we can intercept the execution of
@Shared(scope = SIMULATION, inner = INTERFACES)
public interface InterceptingExecutor extends OrderOn
{
    class ForbiddenExecutionException extends RejectedExecutionException
    {
    }

    interface InterceptingTaskFactory extends TaskFactory
    {
    }

    void addPending(Object task);
    void cancelPending(Object task);
    void submitUnmanaged(Runnable task);
    void submitAndAwaitPause(Runnable task, InterceptorOfConsequences interceptor);

    OrderOn orderAppliesAfterScheduling();

    interface InterceptableScheduledFuture<T> extends ScheduledFuture<T>, RunnableFuture<T>
    {
        void onCancel(Runnable onCancel);
    }

    @PerClassLoader
    abstract class AbstractInterceptingExecutor implements InterceptingExecutor, ExecutorPlus
    {
        private static final AtomicIntegerFieldUpdater<AbstractInterceptingExecutor> pendingUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractInterceptingExecutor.class, "pending");

        final OrderAppliesAfterScheduling orderAppliesAfterScheduling = new OrderAppliesAfterScheduling(this);

        final InterceptorOfExecution interceptorOfExecution;
        final InterceptingTaskFactory taskFactory;

        final Set<Object> debugPending = TEST_SIMULATOR_DEBUG.getBoolean() ? synchronizedSet(newSetFromMap(new IdentityHashMap<>())) : null;
        final Condition isTerminated;
        volatile boolean isShutdown;
        volatile int pending;

        protected AbstractInterceptingExecutor(InterceptorOfExecution interceptorOfExecution, InterceptingTaskFactory taskFactory)
        {
            this.interceptorOfExecution = interceptorOfExecution;
            this.isTerminated = new InterceptingCondition();
            this.taskFactory = taskFactory;
        }

        @Override
        public void addPending(Object task)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            pendingUpdater.incrementAndGet(this);
            if (isShutdown)
            {
                if (0 == pendingUpdater.decrementAndGet(this))
                    terminate();
                throw new RejectedExecutionException();
            }

            if (debugPending != null && !debugPending.add(task))
                throw new AssertionError();
        }

        @Override
        public void cancelPending(Object task)
        {
            boolean shutdown = isShutdown;
            if (completePending(task) == 0 && shutdown)
                terminate();
        }

        @Override
        public OrderOn orderAppliesAfterScheduling()
        {
            return orderAppliesAfterScheduling;
        }

        public int completePending(Object task)
        {
            int remaining = pendingUpdater.decrementAndGet(this);
            if (debugPending != null && !debugPending.remove(task))
                throw new AssertionError();
            return remaining;
        }

        <V, T extends RunnableFuture<V>> T addTask(T task)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            return interceptorOfExecution.intercept().addTask(task, this);
        }

        public void maybeExecuteImmediately(Runnable command)
        {
            execute(command);
        }

        @Override
        public void execute(Runnable run)
        {
            addTask(taskFactory.toSubmit(run));
        }

        @Override
        public void execute(WithResources withResources, Runnable run)
        {
            addTask(taskFactory.toSubmit(withResources, run));
        }

        @Override
        public Future<?> submit(Runnable run)
        {
            return addTask(taskFactory.toSubmit(run));
        }

        @Override
        public <T> Future<T> submit(Runnable run, T result)
        {
            return addTask(taskFactory.toSubmit(run, result));
        }

        @Override
        public <T> Future<T> submit(Callable<T> call)
        {
            return addTask(taskFactory.toSubmit(call));
        }

        @Override
        public <T> Future<T> submit(WithResources withResources, Runnable run, T result)
        {
            return addTask(taskFactory.toSubmit(withResources, run, result));
        }

        @Override
        public Future<?> submit(WithResources withResources, Runnable run)
        {
            return addTask(taskFactory.toSubmit(withResources, run));
        }

        @Override
        public <T> Future<T> submit(WithResources withResources, Callable<T> call)
        {
            return addTask(taskFactory.toSubmit(withResources, call));
        }

        abstract void terminate();

        public boolean isShutdown()
        {
            return isShutdown;
        }

        public boolean isTerminated()
        {
            return isTerminated.isSignalled();
        }

        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
        {
            Thread thread = Thread.currentThread();
            if (thread instanceof InterceptibleThread)
            {
                InterceptibleThread interceptibleThread = (InterceptibleThread) thread;
                if (interceptibleThread.isIntercepting())
                {
                    // simpler to use no timeout than to ensure pending tasks all run first in simulation
                    isTerminated.await();
                    return true;
                }
            }
            return isTerminated.await(timeout, unit);
        }

        @Override
        public void setCorePoolSize(int newCorePoolSize)
        {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setMaximumPoolSize(int newMaximumPoolSize)
        {
            throw new UnsupportedOperationException();
        }
    }

    @PerClassLoader
    class InterceptingPooledExecutor extends AbstractInterceptingExecutor implements InterceptingExecutor
    {
        enum State { RUNNING, TERMINATING, TERMINATED }

        private class WaitingThread
        {
            final InterceptibleThread thread;
            Runnable task;
            State state = State.RUNNING;

            WaitingThread(ThreadFactory factory)
            {
                this.thread = (InterceptibleThread) factory.newThread(() -> {
                    InterceptibleThread thread = (InterceptibleThread) Thread.currentThread();
                    try
                    {
                        while (true)
                        {
                            try
                            {
                                task.run();
                            }
                            catch (Throwable t)
                            {
                                try { thread.getUncaughtExceptionHandler().uncaughtException(thread, t); }
                                catch (Throwable ignore) {}
                            }

                            boolean shutdown = isShutdown;
                            int remaining = completePending(task);
                            if (shutdown && remaining < threads.size())
                            {
                                threads.remove(thread);
                                thread.onTermination();
                                if (threads.isEmpty())
                                    isTerminated.signal(); // this has simulator side-effects, so try to perform before we interceptTermination
                                thread.interceptTermination(true);
                                return;
                            }

                            task = null;
                            waiting.add(this); // inverse order of waiting.add/isShutdown to ensure visibility vs shutdown()
                            thread.interceptTermination(false);
                            synchronized (this)
                            {
                                while (task == null)
                                {
                                    if (state == State.TERMINATING)
                                        return;

                                    try { wait(); }
                                    catch (InterruptedException | UncheckedInterruptedException ignore) { }
                                }
                            }
                        }
                    }
                    finally
                    {
                        try
                        {
                            runDeterministic(() -> {
                                if (null != threads.remove(thread))
                                {
                                    task = null;
                                    waiting.remove(this);
                                    thread.onTermination();
                                    if (isShutdown && threads.isEmpty() && waiting.isEmpty() && !isTerminated())
                                        isTerminated.signal();
                                }
                            });
                        }
                        finally
                        {
                            synchronized (this)
                            {
                                state = State.TERMINATED;
                                notify();
                            }
                        }
                    }
                });

                threads.put(thread, this);
            }

            synchronized void submit(Runnable task)
            {
                if (state != State.RUNNING)
                    throw new IllegalStateException();
                this.task = task;
                if (thread.isAlive()) notify();
                else thread.start();
            }

            synchronized void terminate()
            {
                if (state != State.TERMINATED)
                    state = State.TERMINATING;

                if (thread.isAlive()) notify();
                else thread.start();
                try
                {
                    while (state != State.TERMINATED)
                        wait();
                }
                catch (InterruptedException e)
                {
                    throw new UncheckedInterruptedException(e);
                }
            }
        }

        final Map<InterceptibleThread, WaitingThread> threads = synchronizedMap(new IdentityHashMap<>());
        final ThreadFactory threadFactory;
        final Queue<WaitingThread> waiting = new ConcurrentLinkedQueue<>();
        final int concurrency;

        InterceptingPooledExecutor(InterceptorOfExecution interceptorOfExecution, int concurrency, ThreadFactory threadFactory, InterceptingTaskFactory taskFactory)
        {
            super(interceptorOfExecution, taskFactory);
            this.threadFactory = threadFactory;
            this.concurrency = concurrency;
        }

        public void submitAndAwaitPause(Runnable task, InterceptorOfConsequences interceptor)
        {
            // we don't check isShutdown as we could have a task queued by simulation from prior to shutdown
            if (isTerminated()) throw new AssertionError();
            if (debugPending != null && !debugPending.contains(task)) throw new AssertionError();

            WaitingThread waiting = getWaiting();
            AwaitPaused done = new AwaitPaused(waiting);
            waiting.thread.beforeInvocation(interceptor, done);
            synchronized (waiting)
            {
                waiting.submit(task);
                done.awaitPause();
            }
        }

        public void submitUnmanaged(Runnable task)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            addPending(task);
            WaitingThread waiting = getWaiting();
            waiting.submit(task);
        }

        private WaitingThread getWaiting()
        {
            WaitingThread next = waiting.poll();
            if (next != null)
                return next;

            return new WaitingThread(threadFactory);
        }

        public synchronized void shutdown()
        {
            isShutdown = true;
            WaitingThread next;
            while (null != (next = waiting.poll()))
                next.terminate();

            if (pending == 0)
                terminate();
        }

        synchronized void terminate()
        {
            List<InterceptibleThread> snapshot = new ArrayList<>(threads.keySet());
            for (InterceptibleThread thread : snapshot)
            {
                WaitingThread terminate = threads.get(thread);
                if (terminate != null)
                    terminate.terminate();
            }
            runDeterministic(isTerminated::signal);
        }

        public synchronized List<Runnable> shutdownNow()
        {
            shutdown();
            threads.keySet().forEach(InterceptibleThread::interrupt);
            return Collections.emptyList();
        }

        @Override
        public boolean inExecutor()
        {
            return threads.containsKey(Thread.currentThread());
        }

        @Override
        public int getActiveTaskCount()
        {
            return threads.size() - waiting.size();
        }

        @Override
        public long getCompletedTaskCount()
        {
            return 0;
        }

        @Override
        public int getPendingTaskCount()
        {
            return 0;
        }

        @Override
        public int getCorePoolSize()
        {
            return concurrency;
        }

        @Override
        public int getMaximumPoolSize()
        {
            return concurrency;
        }

        public String toString()
        {
            return threadFactory.toString();
        }

        @Override
        public int concurrency()
        {
            return concurrency;
        }
    }

    // we might want different variants
    // (did consider a non-intercepting variant, or immediate executor, but we need to intercept the thread events)
    @PerClassLoader
    abstract class AbstractSingleThreadedExecutorPlus extends AbstractInterceptingExecutor implements SequentialExecutorPlus
    {
        static class AtLeastOnce extends SingleThreadExecutorPlus.AtLeastOnce
        {
            AtLeastOnce(SequentialExecutorPlus executor, Runnable run)
            {
                super(executor, run);
            }

            public boolean trigger()
            {
                boolean success;
                if (success = compareAndSet(false, true))
                    executor.execute(this);
                else
                    executor.execute(() -> {}); // submit a no-op, so we can still impose our causality orderings
                return success;
            }
        }

        final InterceptibleThread thread;
        final ArrayDeque<Runnable> queue = new ArrayDeque<>();
        volatile boolean executing, terminating, terminated;

        AbstractSingleThreadedExecutorPlus(InterceptorOfExecution interceptorOfExecution, ThreadFactory threadFactory, InterceptingTaskFactory taskFactory)
        {
            super(interceptorOfExecution, taskFactory);
            this.thread = (InterceptibleThread) threadFactory.newThread(() -> {
                InterceptibleThread thread = (InterceptibleThread) Thread.currentThread();
                try
                {
                    while (true)
                    {
                        Runnable task;
                        try
                        {
                            task = dequeue();
                        }
                        catch (InterruptedException | UncheckedInterruptedException ignore)
                        {
                            if (terminating) return;
                            else continue;
                        }

                        try
                        {
                            task.run();
                        }
                        catch (Throwable t)
                        {
                            try { thread.getUncaughtExceptionHandler().uncaughtException(thread, t); }
                            catch (Throwable ignore) {}
                        }

                        executing = false;
                        boolean shutdown = isShutdown;
                        if ((0 == completePending(task) && shutdown))
                            return;

                        thread.interceptTermination(false);
                    }
                }
                finally
                {
                    runDeterministic(thread::onTermination);
                    if (terminating)
                    {
                        synchronized (this)
                        {
                            terminated = true;
                            notifyAll();
                        }
                    }
                    else
                    {
                        runDeterministic(this::terminate);
                    }
                }
            });
            thread.start();
        }

        void terminate()
        {
            synchronized (this)
            {
                assert pending == 0;
                if (terminating)
                    return;

                terminating = true;
                if (Thread.currentThread() != thread)
                {
                    notifyAll();
                    try { while (!terminated) wait(); }
                    catch (InterruptedException e) { throw new UncheckedInterruptedException(e); }
                }
                terminated = true;
            }

            isTerminated.signal(); // this has simulator side-effects, so try to perform before we interceptTermination
            if (Thread.currentThread() == thread && thread.isIntercepting())
                thread.interceptTermination(true);
        }

        public synchronized void shutdown()
        {
            if (isShutdown)
                return;

            isShutdown = true;
            if (pending == 0)
                terminate();
        }

        public synchronized List<Runnable> shutdownNow()
        {
            if (isShutdown)
                return Collections.emptyList();

            isShutdown = true;
            List<Runnable> cancelled = new ArrayList<>(queue);
            queue.clear();
            cancelled.forEach(super::cancelPending);
            if (pending == 0) terminate();
            else thread.interrupt();
            return cancelled;
        }

        synchronized void enqueue(Runnable runnable)
        {
            queue.add(runnable);
            notify();
        }

        synchronized Runnable dequeue() throws InterruptedException
        {
            Runnable next;
            while (null == (next = queue.poll()) && !terminating)
                wait();

            if (next == null)
                throw new InterruptedException();

            return next;
        }

        public AtLeastOnce atLeastOnceTrigger(Runnable run)
        {
            return new AtLeastOnce(this, run);
        }

        @Override
        public boolean inExecutor()
        {
            return thread == Thread.currentThread();
        }

        @Override
        public int getCorePoolSize()
        {
            return 1;
        }

        @Override
        public int getMaximumPoolSize()
        {
            return 1;
        }

        public String toString()
        {
            return thread.toString();
        }
    }

    @PerClassLoader
    class InterceptingSequentialExecutor extends AbstractSingleThreadedExecutorPlus implements InterceptingExecutor, ScheduledExecutorPlus, OrderOn
    {
        static class InterceptableScheduledFutureTask<T> extends SyncFutureTask<T> implements InterceptableScheduledFuture<T>
        {
            final long delayNanos;
            Runnable onCancel;
            public InterceptableScheduledFutureTask(long delayNanos, Callable<T> call)
            {
                super(call);
                this.delayNanos = delayNanos;
            }

            @Override
            public long getDelay(TimeUnit unit)
            {
                return unit.convert(delayNanos, NANOSECONDS);
            }

            @Override
            public int compareTo(Delayed that)
            {
                return Long.compare(delayNanos, that.getDelay(NANOSECONDS));
            }

            public void onCancel(Runnable onCancel)
            {
                this.onCancel = onCancel;
            }

            @Override
            public boolean cancel(boolean b)
            {
                if (onCancel != null)
                {
                    onCancel.run();
                    onCancel = null;
                }
                return super.cancel(b);
            }
        }

        InterceptingSequentialExecutor(InterceptorOfExecution interceptorOfExecution, ThreadFactory threadFactory, InterceptingTaskFactory taskFactory)
        {
            super(interceptorOfExecution, threadFactory, taskFactory);
        }

        public void submitAndAwaitPause(Runnable task, InterceptorOfConsequences interceptor)
        {
            synchronized (this)
            {
                // we don't check isShutdown as we could have a task queued by simulation from prior to shutdown
                if (terminated) throw new AssertionError();
                if (executing) throw new AssertionError();
                if (debugPending != null && !debugPending.contains(task)) throw new AssertionError();
                executing = true;

                AwaitPaused done = new AwaitPaused(this);
                thread.beforeInvocation(interceptor, done);
                enqueue(task);
                done.awaitPause();
            }
        }

        public synchronized void submitUnmanaged(Runnable task)
        {
            addPending(task);
            enqueue(task);
        }

        @Override public int getActiveTaskCount()
        {
            return !queue.isEmpty() || executing ? 1 : 0;
        }

        @Override public long getCompletedTaskCount()
        {
            return 0;
        }

        @Override
        public int getPendingTaskCount()
        {
            return 0;
        }

        public ScheduledFuture<?> schedule(Runnable run, long delay, TimeUnit unit)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            long delayNanos = unit.toNanos(delay);
            return schedule(SCHEDULED_TASK, delayNanos, relativeToGlobalNanos(delayNanos), callable(run, null));
        }

        public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            long delayNanos = unit.toNanos(delay);
            return schedule(SCHEDULED_TASK, delayNanos, relativeToGlobalNanos(delayNanos), callable);
        }

        public ScheduledFuture<?> scheduleTimeoutWithDelay(Runnable run, long delay, TimeUnit unit)
        {
            return scheduleTimeoutAt(run, relativeToLocalNanos(unit.toNanos(delay)));
        }

        public ScheduledFuture<?> scheduleAt(Runnable run, long deadlineNanos)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            return schedule(SCHEDULED_TASK, localToRelativeNanos(deadlineNanos), localToGlobalNanos(deadlineNanos), callable(run, null));
        }

        public ScheduledFuture<?> scheduleTimeoutAt(Runnable run, long deadlineNanos)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            return schedule(SCHEDULED_TIMEOUT, localToRelativeNanos(deadlineNanos), localToGlobalNanos(deadlineNanos), callable(run, null));
        }

        public ScheduledFuture<?> scheduleSelfRecurring(Runnable run, long delay, TimeUnit unit)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            long delayNanos = unit.toNanos(delay);
            return schedule(SCHEDULED_DAEMON, delayNanos, relativeToGlobalNanos(delayNanos), callable(run, null));
        }

        public ScheduledFuture<?> scheduleAtFixedRate(Runnable run, long initialDelay, long period, TimeUnit unit)
        {
            if (isShutdown)
                throw new RejectedExecutionException();

            long delayNanos = unit.toNanos(initialDelay);
            return schedule(SCHEDULED_DAEMON, delayNanos, relativeToGlobalNanos(delayNanos), new Callable<Object>()
            {
                @Override
                public Object call()
                {
                    run.run();
                    if (!isShutdown)
                        scheduleAtFixedRate(run, period, period, unit);
                    return null;
                }

                @Override
                public String toString()
                {
                    return run.toString();
                }
            });
        }

        <T> ScheduledFuture<T> schedule(SimulatedAction.Kind kind, long delayNanos, long deadlineNanos, Callable<T> task)
        {
            return interceptorOfExecution.intercept().schedule(kind, delayNanos, deadlineNanos, new InterceptableScheduledFutureTask<>(delayNanos, task), this);
        }

        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable run, long initialDelay, long delay, TimeUnit unit)
        {
            return scheduleAtFixedRate(run, initialDelay, delay, unit);
        }

        public int concurrency()
        {
            return 1;
        }
    }



    @PerClassLoader
    class InterceptingPooledLocalAwareExecutor extends InterceptingPooledExecutor implements LocalAwareExecutorPlus
    {
        InterceptingPooledLocalAwareExecutor(InterceptorOfExecution interceptors, int concurrency, ThreadFactory threadFactory, InterceptingTaskFactory taskFactory)
        {
            super(interceptors, concurrency, threadFactory, taskFactory);
        }
    }

    @PerClassLoader
    class InterceptingLocalAwareSequentialExecutor extends InterceptingSequentialExecutor implements LocalAwareSequentialExecutorPlus
    {
        InterceptingLocalAwareSequentialExecutor(InterceptorOfExecution interceptorOfExecution, ThreadFactory threadFactory, InterceptingTaskFactory taskFactory)
        {
            super(interceptorOfExecution, threadFactory, taskFactory);
        }
    }

    @PerClassLoader
    static class DiscardingSequentialExecutor implements LocalAwareSequentialExecutorPlus, ScheduledExecutorPlus
    {
        @Override
        public void shutdown()
        {
        }

        @Override
        public List<Runnable> shutdownNow()
        {
            return Collections.emptyList();
        }

        @Override
        public boolean isShutdown()
        {
            return false;
        }

        @Override
        public boolean isTerminated()
        {
            return false;
        }

        @Override
        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
        {
            return false;
        }

        @Override
        public <T> Future<T> submit(Callable<T> task)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public <T> Future<T> submit(Runnable task, T result)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public Future<?> submit(Runnable task)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public void execute(WithResources withResources, Runnable task)
        {
        }

        @Override
        public <T> Future<T> submit(WithResources withResources, Callable<T> task)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public Future<?> submit(WithResources withResources, Runnable task)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public <T> Future<T> submit(WithResources withResources, Runnable task, T result)
        {
            return ImmediateFuture.cancelled();
        }

        @Override
        public boolean inExecutor()
        {
            return false;
        }

        @Override
        public int getCorePoolSize()
        {
            return 0;
        }

        @Override
        public void setCorePoolSize(int newCorePoolSize)
        {

        }

        @Override
        public int getMaximumPoolSize()
        {
            return 0;
        }

        @Override
        public void setMaximumPoolSize(int newMaximumPoolSize)
        {

        }

        @Override
        public int getActiveTaskCount()
        {
            return 0;
        }

        @Override
        public long getCompletedTaskCount()
        {
            return 0;
        }

        @Override
        public int getPendingTaskCount()
        {
            return 0;
        }

        @Override
        public AtLeastOnceTrigger atLeastOnceTrigger(Runnable runnable)
        {
            return new AtLeastOnceTrigger()
            {
                @Override
                public boolean trigger()
                {
                    return false;
                }

                @Override
                public void runAfter(Runnable run)
                {
                }

                @Override
                public void sync()
                {
                }
            };
        }

        @Override
        public void execute(Runnable command)
        {
        }

        @Override
        public ScheduledFuture<?> scheduleSelfRecurring(Runnable run, long delay, TimeUnit units)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> scheduleAt(Runnable run, long deadline)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> scheduleTimeoutAt(Runnable run, long deadline)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> scheduleTimeoutWithDelay(Runnable run, long delay, TimeUnit units)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
        {
            return new NotScheduledFuture<>();
        }

        @Override
        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
        {
            return new NotScheduledFuture<>();
        }
    }
}
