/*
 *
 * 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.qpid.proton.engine;

import java.nio.ByteBuffer;

import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.impl.TransportImpl;


/**
 * <p>
 * Operates on the entities in the associated {@link Connection}
 * by accepting and producing binary AMQP output, potentially
 * layered within SASL and/or SSL.
 * </p>
 * <p>
 * After a connection is bound with {@link #bind(Connection)}, the methods for accepting and producing
 * output are typically repeatedly called. See the specific methods for details of their legal usage.
 * </p>
 * <p>
 * <strong>Processing the input data received from another AMQP container.</strong>
 * </p>
 * <ol>
 * <li>{@link #getInputBuffer()} </li>
 * <li>Write data into input buffer</li>
 * <li>{@link #processInput()}</li>
 * <li>Check the result, e.g. by calling {@link TransportResult#checkIsOk()}</li>
 * </ol>
 * <p>
 * <strong>Getting the output data to send to another AMQP container:</strong>
 * </p>
 * <ol>
 * <li>{@link #getOutputBuffer()} </li>
 * <li>Read output from output buffer</li>
 * <li>{@link #outputConsumed()}</li>
 * </ol>
 *
 * <p>The following methods on the byte buffers returned by {@link #getInputBuffer()} and {@link #getOutputBuffer()}
 * must not be called:
 * </p>
 * <ol>
 * <li> {@link ByteBuffer#clear()} </li>
 * <li> {@link ByteBuffer#compact()} </li>
 * <li> {@link ByteBuffer#flip()} </li>
 * <li> {@link ByteBuffer#mark()} </li>
 * </ol>
 */
public interface Transport extends Endpoint
{

    public static final class Factory
    {
        public static Transport create() {
            return new TransportImpl();
        }
    }

    public static final int TRACE_OFF = 0;
    public static final int TRACE_RAW = 1;
    public static final int TRACE_FRM = 2;
    public static final int TRACE_DRV = 4;

    public static final int DEFAULT_MAX_FRAME_SIZE = -1;

    /** the lower bound for the agreed maximum frame size (in bytes). */
    public int MIN_MAX_FRAME_SIZE = 512;
    public int SESSION_WINDOW = 16*1024;
    public int END_OF_STREAM = -1;

    public void trace(int levels);

    public void bind(Connection connection);
    public void unbind();

    public int capacity();
    public ByteBuffer tail();
    public void process() throws TransportException;
    public void close_tail();


    public int pending();
    public ByteBuffer head();
    public void pop(int bytes);
    public void close_head();

    public boolean isClosed();

    /**
     * Processes the provided input.
     *
     * @param bytes input bytes for consumption
     * @param offset the offset within bytes where input begins
     * @param size the number of bytes available for input
     *
     * @return the number of bytes consumed
     * @throws TransportException if the input is invalid, if the transport is already in an error state,
     * or if the input is empty (unless the remote connection is already closed)
     * @deprecated use {@link #getInputBuffer()} and {@link #processInput()} instead.
     */
    @Deprecated
    public int input(byte[] bytes, int offset, int size);

    /**
     * Get a buffer that can be used to write input data into the transport.
     * Once the client has finished putting into the input buffer, {@link #processInput()}
     * must be called.
     *
     * Successive calls to this method are not guaranteed to return the same object.
     * Once {@link #processInput()} is called the buffer must not be used.
     *
     * @throws TransportException if the transport is already in an invalid state
     */
    ByteBuffer getInputBuffer();

    /**
     * Tell the transport to process the data written to the input buffer.
     *
     * If the returned result indicates failure, the transport will not accept any more input.
     * Specifically, any subsequent {@link #processInput()} calls on this object will
     * throw an exception.
     *
     * @return the result of processing the data, which indicates success or failure.
     * @see #getInputBuffer()
     */
    TransportResult processInput();

    /**
     * Has the transport produce up to size bytes placing the result
     * into dest beginning at position offset.
     *
     * @param dest array for output bytes
     * @param offset the offset within bytes where output begins
     * @param size the maximum number of bytes to be output
     *
     * @return the number of bytes written
     * @deprecated use {@link #getOutputBuffer()} and {@link #outputConsumed()} instead
     */
    @Deprecated
    public int output(byte[] dest, int offset, int size);

    /**
     * Get a read-only byte buffer containing the transport's pending output.
     * Once the client has finished getting from the output buffer, {@link #outputConsumed()}
     * must be called.
     *
     * Successive calls to this method are not guaranteed to return the same object.
     * Once {@link #outputConsumed()} is called the buffer must not be used.
     *
     * If the transport's state changes AFTER calling this method, this will not be
     * reflected in the output buffer.
     */
    ByteBuffer getOutputBuffer();

    /**
     * Informs the transport that the output buffer returned by {@link #getOutputBuffer()}
     * is finished with, allowing implementation-dependent steps to be performed such as
     * reclaiming buffer space.
     */
    void outputConsumed();

    /**
     * Signal the transport to expect SASL frames used to establish a SASL layer prior to
     * performing the AMQP protocol version negotiation. This must first be performed before
     * the transport is used for processing. Subsequent invocations will return the same
     * {@link Sasl} object.
     *
     * @throws IllegalStateException if transport processing has already begun prior to initial invocation
     */
    Sasl sasl() throws IllegalStateException;

    /**
     * Wrap this transport's output and input to apply SSL encryption and decryption respectively.
     *
     * This method is expected to be called at most once. A subsequent invocation will return the same
     * {@link Ssl} object, regardless of the parameters supplied.
     *
     * @param sslDomain the SSL settings to use
     * @param sslPeerDetails may be null, in which case SSL session resume will not be attempted
     * @return an {@link Ssl} object representing the SSL session.
     */
    Ssl ssl(SslDomain sslDomain, SslPeerDetails sslPeerDetails);

    /**
     * As per {@link #ssl(SslDomain, SslPeerDetails)} but no attempt is made to resume a previous SSL session.
     */
    Ssl ssl(SslDomain sslDomain);


    /**
     * Get the maximum frame size for the transport
     *
     * @return the maximum frame size
     */
    int getMaxFrameSize();

    void setMaxFrameSize(int size);

    int getRemoteMaxFrameSize();

    /**
     * Allows overriding the initial remote-max-frame-size to a value greater than the default 512bytes. The value set
     * will be used until such time as the Open frame arrives from the peer and populates the remote max frame size.
     *
     * This method must be called before before {@link #sasl()} in order to influence SASL behaviour.
     * @param size the remote frame size to use
     */
    void setInitialRemoteMaxFrameSize(int size);

    /**
     * Gets the local channel-max value to be advertised to the remote peer
     *
     * @return the local channel-max value
     * @see #setChannelMax(int)
     */
    int getChannelMax();

    /**
     * Set the local value of channel-max, to be advertised to the peer on the
     * <a href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-open">
     * Open frame</a> emitted by the transport.
     *
     * The remote peers advertised channel-max can be observed using {@link #getRemoteChannelMax()}.
     *
     * @param channelMax the local channel-max to advertise to the peer, in range [0 - 2^16).
     * @throws IllegalArgumentException if the value supplied is outside range [0 - 2^16).
     */
    void setChannelMax(int channelMax);

    /**
     * Gets the remote value of channel-max, as advertised by the peer on its
     * <a href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-open">
     * Open frame</a>.
     *
     * The local peers advertised channel-max can be observed using {@link #getChannelMax()}.
     *
     * @return the remote channel-max value
     */
    int getRemoteChannelMax();

    ErrorCondition getCondition();

    /**
     *
     * @param timeout local idle timeout in milliseconds
     */
    void setIdleTimeout(int timeout);
    /**
     *
     * @return local idle timeout in milliseconds
     */
    int getIdleTimeout();
    /**
     *
     * @return remote idle timeout in milliseconds
     */
    int getRemoteIdleTimeout();

    /**
     * Prompt the transport to perform work such as idle-timeout/heartbeat handling, and return an
     * absolute deadline in milliseconds that tick must again be called by/at, based on the provided
     * current time in milliseconds, to ensure the periodic work is carried out as necessary.
     *
     * A returned deadline of 0 indicates there is no periodic work necessitating tick be called, e.g.
     * because neither peer has defined an idle-timeout value.
     *
     * The provided milliseconds time values can be from {@link System#currentTimeMillis()} or derived
     * from {@link System#nanoTime()}, noting that for the later in particular that the returned
     * deadline could be a different sign than the given time, and (if non-zero) the returned
     * deadline should have the current time originally provided subtracted from it in order to
     * establish a relative time delay to the next deadline.
     *
     * @param nowMillis the current time in milliseconds
     * @return the absolute deadline in milliseconds to next call tick by/at, or 0 if there is none.
     */
    long tick(long nowMillis);

    long getFramesInput();

    long getFramesOutput();

    /**
     * Configure whether a synthetic Flow event should be emitted when messages are sent,
     * reflecting a change in the credit level on the link that may prompt other action.
     *
     * Defaults to true.
     *
     * @param emitFlowEventOnSend true if a flow event should be emitted, false otherwise
     */
    void setEmitFlowEventOnSend(boolean emitFlowEventOnSend);

    boolean isEmitFlowEventOnSend();
}
