/*
 * 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.client;

import io.undertow.UndertowMessages;
import org.xnio.ChannelListener;
import org.xnio.Option;
import io.undertow.connector.ByteBufferPool;
import org.xnio.StreamConnection;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.Channel;
import java.util.concurrent.TimeUnit;

/**
 * A client connection. This can be used to send requests, or to upgrade the connection.
 * <p>
 * In general these objects are not thread safe, they should only be used by the IO thread
 * that is responsible for the connection. As a result this client does not provide a mechanism
 * to perform blocking IO, it is designed for async operation only.
 *
 * @author Stuart Douglas
 */
public interface ClientConnection extends Channel {

    /**
     * Sends a client request. The request object should not be modified after it has been submitted to the connection.
     * <p>
     * Request objects can be queued. Once the request is in a state that it is ready to be sent the {@code clientCallback}
     * is invoked to provide the caller with the {@link ClientExchange}
     * <p>
     * If {@link #isMultiplexingSupported()} returns true then multiple requests may be active at the same time, and a later
     * request may complete before an earlier one.
     * <p>
     * Note that the request header may not be written out until after the callback has been invoked. This allows the
     * client to write out a header with a gathering write if the request contains content.
     *
     * @param request The request to send.
     */
    void sendRequest(ClientRequest request, ClientCallback<ClientExchange> clientCallback);

    /**
     * Upgrade the connection, if the underlying protocol supports it. This should only be called after an upgrade request
     * has been submitted and the target server has accepted the upgrade.
     *
     * @return The resulting StreamConnection
     */
    StreamConnection performUpgrade() throws IOException;

    /**
     *
     * @return The buffer pool used by the client
     */
    ByteBufferPool getBufferPool();

    SocketAddress getPeerAddress();

    <A extends SocketAddress> A getPeerAddress(Class<A> type);

    ChannelListener.Setter<? extends ClientConnection> getCloseSetter();

    SocketAddress getLocalAddress();

    <A extends SocketAddress> A getLocalAddress(Class<A> type);

    XnioWorker getWorker();

    XnioIoThread getIoThread();

    boolean isOpen();

    boolean supportsOption(Option<?> option);

    <T> T getOption(Option<T> option) throws IOException;

    <T> T setOption(Option<T> option, T value) throws IllegalArgumentException, IOException;

    boolean isUpgraded();

    /**
     *
     * @return <code>true</code> if this connection support server push
     */
    boolean isPushSupported();

    /**
     *
     * @return <code>true</code> if this client supports multiplexing
     */
    boolean isMultiplexingSupported();

    /**
     *
     * @return the statistics information, or <code>null</code> if statistics are not supported or disabled
     */
    ClientStatistics getStatistics();

    boolean isUpgradeSupported();

    /**
     * Adds a close listener, than will be invoked with the connection is closed
     *
     * @param listener The close listener
     */
    void addCloseListener(ChannelListener<ClientConnection> listener);

    /**
     *
     * @return <code>true</code> if the underlying protocol supports sending a ping
     */
    default boolean isPingSupported() {
        return false;
    }

    default void sendPing(PingListener listener, long timeout, TimeUnit timeUnit) {
        listener.failed(UndertowMessages.MESSAGES.pingNotSupported());
    }

    interface PingListener {

        void acknowledged();

        void failed(IOException e);

    }
}
