/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 io.undertow.protocols.ssl;

import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.Option;
import org.xnio.Options;
import io.undertow.connector.ByteBufferPool;
import org.xnio.SslClientAuthMode;
import org.xnio.StreamConnection;
import org.xnio.ssl.SslConnection;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * @author Stuart Douglas
 */
class UndertowSslConnection extends SslConnection {

    private static final Set<Option<?>> SUPPORTED_OPTIONS = Option.setBuilder().add(Options.SECURE, Options.SSL_CLIENT_AUTH_MODE).create();

    private final StreamConnection delegate;
    private final SslConduit sslConduit;
    private final ChannelListener.SimpleSetter<SslConnection> handshakeSetter = new ChannelListener.SimpleSetter<>();
    private final SSLEngine engine;

    /**
     * Construct a new instance.
     *
     * @param delegate the underlying connection
     */
    UndertowSslConnection(StreamConnection delegate, SSLEngine engine, ByteBufferPool bufferPool, Executor delegatedTaskExecutor) {
        super(delegate.getIoThread());
        this.delegate = delegate;
        this.engine = engine;
        sslConduit = new SslConduit(this, delegate, engine, delegatedTaskExecutor, bufferPool, new HandshakeCallback());
        setSourceConduit(sslConduit);
        setSinkConduit(sslConduit);
    }

    @Override
    public void startHandshake() throws IOException {
        sslConduit.startHandshake();
    }

    @Override
    public SSLSession getSslSession() {
        return sslConduit.getSslSession();
    }

    @Override
    public ChannelListener.Setter<? extends SslConnection> getHandshakeSetter() {
        return handshakeSetter;
    }

    @Override
    protected void notifyWriteClosed() {
        try {
            sslConduit.notifyWriteClosed();
        } finally {
            super.notifyWriteClosed();
        }
    }

    @Override
    protected void notifyReadClosed() {
        try {
            sslConduit.notifyReadClosed();
        } finally {
            super.notifyReadClosed();
        }
    }

    @Override
    public SocketAddress getPeerAddress() {
        return delegate.getPeerAddress();
    }

    @Override
    public SocketAddress getLocalAddress() {
        return delegate.getLocalAddress();
    }

    public SSLEngine getSSLEngine() {
        return sslConduit.getSSLEngine();
    }

    SslConduit getSslConduit() {
        return sslConduit;
    }

    /** {@inheritDoc} */
    @Override
    public <T> T setOption(final Option<T> option, final T value) throws IllegalArgumentException, IOException {
        if (option == Options.SSL_CLIENT_AUTH_MODE) {
            try {
                return option.cast(engine.getNeedClientAuth() ? SslClientAuthMode.REQUIRED : engine.getWantClientAuth() ? SslClientAuthMode.REQUESTED : SslClientAuthMode.NOT_REQUESTED);
            } finally {
                engine.setWantClientAuth(false);
                engine.setNeedClientAuth(false);
                if (value == SslClientAuthMode.REQUESTED) {
                    engine.setWantClientAuth(true);
                } else if (value == SslClientAuthMode.REQUIRED) {
                    engine.setNeedClientAuth(true);
                }
            }
        } else if (option == Options.SECURE) {
            throw new IllegalArgumentException();
        } else {
            return delegate.setOption(option, value);
        }
    }

    /** {@inheritDoc} */
    @Override
    public <T> T getOption(final Option<T> option) throws IOException {
        if (option == Options.SSL_CLIENT_AUTH_MODE) {
            return option.cast(engine.getNeedClientAuth() ? SslClientAuthMode.REQUIRED : engine.getWantClientAuth() ? SslClientAuthMode.REQUESTED : SslClientAuthMode.NOT_REQUESTED);
        } else {
            return option == Options.SECURE ? (T)Boolean.TRUE : delegate.getOption(option);
        }
    }

    /** {@inheritDoc} */
    @Override
    public boolean supportsOption(final Option<?> option) {
        return SUPPORTED_OPTIONS.contains(option) || delegate.supportsOption(option);
    }

    @Override
    protected boolean readClosed() {
        return super.readClosed();
    }

    @Override
    protected boolean writeClosed() {
        return super.writeClosed();
    }

    protected void closeAction() {
        sslConduit.close();
    }

    private final class HandshakeCallback implements Runnable {

        @Override
        public void run() {
            final ChannelListener<? super SslConnection> listener = handshakeSetter.get();
            if (listener == null) {
                return;
            }
            ChannelListeners.<SslConnection>invokeChannelListener(UndertowSslConnection.this, listener);
        }
    }
}
