/*
 * Copyright 2013 Red Hat, Inc.
 *
 * Red Hat 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.xnio.netty.transport;


import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.AbstractEventExecutor;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ScheduledFuture;
import org.xnio.XnioExecutor;
import org.xnio.XnioIoThread;

import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * {@link EventLoop} implementation which uses a {@link XnioIoThread}.
 *
 * @author <a href="mailto:[email protected]">Norman Maurer</a>
 */
final class XnioEventLoop extends AbstractEventExecutor implements EventLoop, IoThreadPowered{
    private final XnioIoThread executor;
    private final EventLoopGroup parent;

    XnioEventLoop(EventLoopGroup parent, XnioIoThread executor) {
        this.parent = parent ;
        this.executor = executor;
    }

    XnioEventLoop(XnioIoThread executor) {
        this.parent = this;
        this.executor = executor;
    }

    @Override
    public XnioIoThread ioThread() {
        return executor;
    }

    @Override
    public void shutdown() {
        // Not supported, just ignore
    }

    @Override
    public EventLoopGroup parent() {
        return parent;
    }

    @Override
    public boolean inEventLoop(Thread thread) {
        return thread == executor;
    }

    @Override
    public ChannelFuture register(Channel channel) {
        return register(channel, channel.newPromise());
    }

    @Override
    public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
        if (channel == null) {
            throw new NullPointerException("channel");
        }
        if (promise == null) {
            throw new NullPointerException("promise");
        }
        channel.unsafe().register(this, promise);
        return promise;
    }

    @Override
    public boolean isShuttingDown() {
        return executor.getWorker().isShutdown();
    }

    @Override
    public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
        return newFailedFuture(new UnsupportedOperationException());
    }

    @Override
    public Future<?> terminationFuture() {
        return newFailedFuture(new UnsupportedOperationException());
    }

    @Override
    public boolean isShutdown() {
        return executor.getWorker().isShutdown();
    }

    @Override
    public boolean isTerminated() {
        return executor.getWorker().isTerminated();
    }

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

    @Override
    public void execute(Runnable command) {
        executor.execute(command);
    }

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

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        ScheduledFutureWrapper<V> wrapper = new ScheduledFutureWrapper<V>(callable, delay, unit);
        wrapper.key = executor.executeAfter(wrapper, delay, unit);
        return wrapper;
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        FixedScheduledFuture wrapper = new FixedScheduledFuture(command, initialDelay, delay, unit);
        wrapper.key = executor.executeAfter(wrapper, delay, unit);
        return wrapper;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        FixedRateScheduledFuture wrapper = new FixedRateScheduledFuture(command, initialDelay, period, unit);
        wrapper.key = executor.executeAfter(wrapper, initialDelay, unit);
        return wrapper;
    }

    @Override
    public XnioEventLoop next() {
        return this;
    }

    private final class FixedRateScheduledFuture extends ScheduledFutureWrapper<Object> {
        private final long period;
        private int count = 1;
        private final long initialDelay;
        private FixedRateScheduledFuture(Runnable task, long delay, long period, TimeUnit unit) {
            super(Executors.callable(task), delay, unit);
            this.initialDelay = delay;
            this.period = period;
        }

        @Override
        public void run() {
            try {
                task.call();
                start = System.nanoTime();
                delay = initialDelay + period * (++count);
                key = executor.executeAfter(this,delay, TimeUnit.NANOSECONDS);
            } catch (Throwable cause) {
                tryFailure(cause);
            }
        }

    }

    private final class FixedScheduledFuture extends ScheduledFutureWrapper<Object> {
        private long furtherDelay;
        private FixedScheduledFuture(Runnable task, long initialDelay, long delay, TimeUnit unit) {
            super(Executors.callable(task), initialDelay, unit);
            this.furtherDelay = unit.toNanos(delay);
        }

        @Override
        public void run() {
            try {
                task.call();
                start = System.nanoTime();
                delay = furtherDelay;
                key = executor.executeAfter(this, furtherDelay, TimeUnit.NANOSECONDS);
            } catch (Throwable cause) {
                tryFailure(cause);
            }
        }

    }

    private class ScheduledFutureWrapper<V> extends DefaultPromise<V> implements ScheduledFuture<V>, Runnable {
        protected volatile XnioExecutor.Key key;
        protected final Callable<V> task;
        protected volatile long delay;
        protected volatile long start;

        ScheduledFutureWrapper(Callable<V> task, long delay, TimeUnit unit) {
            this.task = task;
            this.start = System.nanoTime();
            this.delay = unit.toNanos(delay);
        }

        @Override
        public long getDelay(TimeUnit unit) {
            long remaining = (start + delay) - System.nanoTime();
            if (remaining < 1 || unit == TimeUnit.NANOSECONDS) {
                return remaining;
            }
            return unit.convert(remaining, TimeUnit.NANOSECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
            if (d < 0) {
                return -1;
            } else if (d > 0) {
                return 1;
            } else {
                return 0;
            }
        }

        @Override
        public void run() {
            try {
                trySuccess(task.call());
            } catch (Throwable t) {
                tryFailure(t);
            }
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return setUncancellable() && key.remove();
        }
    }

}