/*
 * 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.ignite.internal.util.nio.impl;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
import org.apache.ignite.internal.util.nio.GridNioFilterAdapter;
import org.apache.ignite.internal.util.nio.GridNioFilterChain;
import org.apache.ignite.internal.util.nio.GridNioFinishedFuture;
import org.apache.ignite.internal.util.nio.GridNioFuture;
import org.apache.ignite.internal.util.nio.GridNioRecoveryDescriptor;
import org.apache.ignite.internal.util.nio.GridNioServerListener;
import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter;
import org.apache.ignite.internal.util.nio.GridNioSession;
import org.apache.ignite.lang.IgniteInClosure;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.Nullable;

/**
 * Tests filter chain event processing.
 */
public class GridNioFilterChainSelfTest extends GridCommonAbstractTest {
    /** Session opened event meta name. */
    private static final int OPENED_META_NAME = 11;

    /** Session closed event meta name. */
    private static final int CLOSED_META_NAME = 12;

    /** Exception caught. */
    private static final int EXCEPTION_CAUGHT_META_NAME = 13;

    /** Message received event meta name. */
    private static final int MESSAGE_RECEIVED_META_NAME = 14;

    /** Message write event meta name. */
    private static final int MESSAGE_WRITE_META_NAME = 15;

    /** Session close event meta name. */
    private static final int CLOSE_META_NAME = 16;

    /** Session idle timeout meta name. */
    private static final int IDLE_META_NAME = 17;

    /** Session write timeout meta name. */
    private static final int WRITE_TIMEOUT_META_NAME = 18;

    /**
     * @throws Exception If failed.
     */
    public void testChainEvents() throws Exception {
        final AtomicReference<String> connectedEvt = new AtomicReference<>();
        final AtomicReference<String> disconnectedEvt = new AtomicReference<>();
        final AtomicReference<String> msgEvt = new AtomicReference<>();
        final AtomicReference<String> idleEvt = new AtomicReference<>();
        final AtomicReference<String> writeTimeoutEvt = new AtomicReference<>();

        final AtomicReference<String> sndEvt = new AtomicReference<>();
        final AtomicReference<String> closeEvt = new AtomicReference<>();

        final AtomicReference<ByteBuffer> rcvdMsgObj = new AtomicReference<>();
        final AtomicReference<Object> sndMsgObj = new AtomicReference<>();

        GridNioServerListener<Object> testLsnr = new GridNioServerListenerAdapter<Object>() {
            @Override public void onConnected(GridNioSession ses) {
                connectedEvt.compareAndSet(null, ses.<String>meta(OPENED_META_NAME));
            }

            @Override public void onDisconnected(GridNioSession ses, @Nullable Exception e) {
                disconnectedEvt.compareAndSet(null, ses.<String>meta(CLOSED_META_NAME));
            }

            @Override public void onMessage(GridNioSession ses, Object msg) {
                msgEvt.compareAndSet(null, ses.<String>meta(MESSAGE_RECEIVED_META_NAME));

                rcvdMsgObj.compareAndSet(null, (ByteBuffer)msg);
            }

            @Override public void onSessionWriteTimeout(GridNioSession ses) {
                writeTimeoutEvt.compareAndSet(null, ses.<String>meta(WRITE_TIMEOUT_META_NAME));
            }

            @Override public void onSessionIdleTimeout(GridNioSession ses) {
                idleEvt.compareAndSet(null, ses.<String>meta(IDLE_META_NAME));
            }
        };

        GridNioFilterAdapter testHead = new GridNioFilterAdapter("TestHead") {
            @Override public void onSessionOpened(GridNioSession ses) throws IgniteCheckedException {
                proceedSessionOpened(ses);
            }

            @Override public void onSessionClosed(GridNioSession ses) throws IgniteCheckedException {
                proceedSessionClosed(ses);
            }

            @Override public void onExceptionCaught(GridNioSession ses, IgniteCheckedException ex) throws IgniteCheckedException {
                proceedExceptionCaught(ses, ex);
            }

            @Override public GridNioFuture<?> onSessionWrite(GridNioSession ses, Object msg, boolean fut, IgniteInClosure<IgniteException> ackC) {
                sndEvt.compareAndSet(null, ses.<String>meta(MESSAGE_WRITE_META_NAME));

                sndMsgObj.compareAndSet(null, msg);

                return null;
            }

            @Override public void onMessageReceived(GridNioSession ses, Object msg) throws IgniteCheckedException {
                proceedMessageReceived(ses, msg);
            }

            @Override public GridNioFuture<Boolean> onSessionClose(GridNioSession ses) {
                closeEvt.compareAndSet(null, ses.<String>meta(CLOSE_META_NAME));

                return null;
            }

            @Override public void onSessionIdleTimeout(GridNioSession ses) throws IgniteCheckedException {
                proceedSessionIdleTimeout(ses);
            }

            @Override public void onSessionWriteTimeout(GridNioSession ses) throws IgniteCheckedException {
                proceedSessionWriteTimeout(ses);
            }
        };

        GridNioFilterChain<Object> chain = new GridNioFilterChain<>(log,  testLsnr, testHead,
            new AppendingFilter("A"), new AppendingFilter("B"), new AppendingFilter("C"), new AppendingFilter("D"));

        GridNioSession ses = new MockNioSession();

        ByteBuffer snd = ByteBuffer.wrap(new byte[1]);
        ByteBuffer rcvd = ByteBuffer.wrap(new byte[1]);

        chain.onSessionOpened(ses);
        chain.onSessionClosed(ses);
        chain.onMessageReceived(ses, rcvd);
        chain.onSessionIdleTimeout(ses);
        chain.onSessionWriteTimeout(ses);
        assertNull(chain.onSessionClose(ses));
        assertNull(chain.onSessionWrite(ses, snd, true, null));

        assertEquals("DCBA", connectedEvt.get());
        assertEquals("DCBA", disconnectedEvt.get());
        assertEquals("DCBA", msgEvt.get());
        assertEquals("DCBA", idleEvt.get());
        assertEquals("DCBA", writeTimeoutEvt.get());

        assertEquals("ABCD", sndEvt.get());
        assertEquals("ABCD", closeEvt.get());

        assertTrue(snd == sndMsgObj.get());
        assertTrue(rcvd == rcvdMsgObj.get());
    }

    /**
     *
     */
    private static class AppendingFilter extends GridNioFilterAdapter {
        /** Param that will be appended to event trace. */
        private String param;

        /**
         * Constructs test filter.
         *
         * @param param Filter parameter.
         */
        private AppendingFilter(String param) {
            super(AppendingFilter.class.getSimpleName());

            this.param = param;
        }

        /** {@inheritDoc} */
        @Override public void onSessionOpened(GridNioSession ses) throws IgniteCheckedException {
            chainMeta(ses, OPENED_META_NAME);

            proceedSessionOpened(ses);
        }

        /** {@inheritDoc} */
        @Override public void onSessionClosed(GridNioSession ses) throws IgniteCheckedException {
            chainMeta(ses, CLOSED_META_NAME);

            proceedSessionClosed(ses);
        }

        /** {@inheritDoc} */
        @Override public void onExceptionCaught(GridNioSession ses, IgniteCheckedException ex) throws IgniteCheckedException {
            chainMeta(ses, EXCEPTION_CAUGHT_META_NAME);

            proceedExceptionCaught(ses, ex);
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<?> onSessionWrite(GridNioSession ses, Object msg, boolean fut, IgniteInClosure<IgniteException> ackC) throws IgniteCheckedException {
            chainMeta(ses, MESSAGE_WRITE_META_NAME);

            return proceedSessionWrite(ses, msg, fut, ackC);
        }

        /** {@inheritDoc} */
        @Override public void onMessageReceived(GridNioSession ses, Object msg) throws IgniteCheckedException {
            chainMeta(ses, MESSAGE_RECEIVED_META_NAME);

            proceedMessageReceived(ses, msg);
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<Boolean> onSessionClose(GridNioSession ses) throws IgniteCheckedException {
            chainMeta(ses, CLOSE_META_NAME);

            return proceedSessionClose(ses);
        }

        /** {@inheritDoc} */
        @Override public void onSessionIdleTimeout(GridNioSession ses) throws IgniteCheckedException {
            chainMeta(ses, IDLE_META_NAME);

            proceedSessionIdleTimeout(ses);
        }

        /** {@inheritDoc} */
        @Override public void onSessionWriteTimeout(GridNioSession ses) throws IgniteCheckedException {
            chainMeta(ses, WRITE_TIMEOUT_META_NAME);

            proceedSessionWriteTimeout(ses);
        }

        /** {@inheritDoc} */
        public String toString() {
            return "AppendingFilter [param=" + param + ']';
        }

        /**
         * Appends parameter of this filter to a meta with specified name.
         *
         * @param ses Session.
         * @param metaKey Meta key.
         */
        private void chainMeta(GridNioSession ses, int metaKey) {
            String att = ses.meta(metaKey);

            att = (att == null ? "" : att) + param;

            ses.addMeta(metaKey, att);
        }
    }

    /**
     */
    public class MockNioSession extends GridMetadataAwareAdapter implements GridNioSession {
        /** Local address */
        private InetSocketAddress locAddr = new InetSocketAddress(0);

        /** Remote address. */
        private InetSocketAddress rmtAddr = new InetSocketAddress(0);

        /**
         * Creates empty mock session.
         */
        public MockNioSession() {
            // No-op.
        }

        /**
         * Creates new mock session with given addresses.
         *
         * @param locAddr Local address.
         * @param rmtAddr Remote address.
         */
        public MockNioSession(InetSocketAddress locAddr, InetSocketAddress rmtAddr) {
            this();

            this.locAddr = locAddr;
            this.rmtAddr = rmtAddr;
        }

        /** {@inheritDoc} */
        @Override public InetSocketAddress localAddress() {
            return locAddr;
        }

        /** {@inheritDoc} */
        @Override public InetSocketAddress remoteAddress() {
            return rmtAddr;
        }

        /** {@inheritDoc} */
        @Override public long bytesSent() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long bytesReceived() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long createTime() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long closeTime() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long lastReceiveTime() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long lastSendTime() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public long lastSendScheduleTime() {
            return 0;
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<Boolean> close() {
            return new GridNioFinishedFuture<>(true);
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<?> send(Object msg) {
            return new GridNioFinishedFuture<>(true);
        }

        /** {@inheritDoc} */
        @Override public void sendNoFuture(Object msg, IgniteInClosure<IgniteException> ackC) {
            // No-op.
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<Object> resumeReads() {
            return null;
        }

        /** {@inheritDoc} */
        @Override public GridNioFuture<Object> pauseReads() {
            return null;
        }

        /** {@inheritDoc} */
        @Override public boolean accepted() {
            return false;
        }

        /** {@inheritDoc} */
        @Override public boolean readsPaused() {
            return false;
        }

        /** {@inheritDoc} */
        @Override public void outRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) {
            // No-op.
        }

        /** {@inheritDoc} */
        @Nullable @Override public GridNioRecoveryDescriptor outRecoveryDescriptor() {
            return null;
        }

        /** {@inheritDoc} */
        @Override public void inRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) {
            // No-op.
        }

        /** {@inheritDoc} */
        @Nullable @Override public GridNioRecoveryDescriptor inRecoveryDescriptor() {
            return null;
        }

        /** {@inheritDoc} */
        @Override public void systemMessage(Object msg) {
            // No-op.
        }
    }
}