/*
 * Copyright 2015 MongoDB, Inc.
 * Copyright 2012 The Netty Project
 *
 * Licensed 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 com.mongodb.connection.netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.ReadTimeoutException;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static com.mongodb.assertions.Assertions.isTrue;
import static com.mongodb.assertions.Assertions.isTrueArgument;

/**
 * Passes a {@link ReadTimeoutException} if the time between a {@link #scheduleTimeout} and {@link #removeTimeout} is longer than the set
 * timeout.
 */
final class ReadTimeoutHandler extends ChannelInboundHandlerAdapter {
    private final long readTimeout;
    private volatile ScheduledFuture<?> timeout;

    ReadTimeoutHandler(final long readTimeout) {
        isTrueArgument("readTimeout must be greater than zero.", readTimeout > 0);
        this.readTimeout = readTimeout;
    }

    void scheduleTimeout(final ChannelHandlerContext ctx) {
        isTrue("Handler called from the eventLoop", ctx.channel().eventLoop().inEventLoop());
        if (timeout == null) {
            timeout = ctx.executor().schedule(new ReadTimeoutTask(ctx), readTimeout, TimeUnit.MILLISECONDS);
        }
    }

    void removeTimeout(final ChannelHandlerContext ctx) {
        isTrue("Handler called from the eventLoop", ctx.channel().eventLoop().inEventLoop());
        if (timeout != null) {
            timeout.cancel(false);
            timeout = null;
        }
    }

    private static final class ReadTimeoutTask implements Runnable {

        private final ChannelHandlerContext ctx;

        ReadTimeoutTask(final ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }

        @Override
        public void run() {
            if (ctx.channel().isOpen()) {
                try {
                    ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);
                    ctx.close();
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            }
        }
    }
}
