/*
 *  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.mina.core.session;

import java.net.SocketAddress;
import java.util.Set;

import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.future.ReadFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.service.IoService;
import org.apache.mina.core.service.TransportMetadata;
import org.apache.mina.core.write.WriteRequest;
import org.apache.mina.core.write.WriteRequestQueue;

/**
 * <p>
 *   A handle which represents connection between two end-points regardless of
 *   transport types.
 * </p>
 * <p>
 *   {@link IoSession} provides user-defined attributes.  User-defined attributes
 *   are application-specific data which are associated with a session.
 *   It often contains objects that represents the state of a higher-level protocol
 *   and becomes a way to exchange data between filters and handlers.
 * </p>
 * <h3>Adjusting Transport Type Specific Properties</h3>
 * <p>
 *   You can simply downcast the session to an appropriate subclass.
 * </p>
 * <h3>Thread Safety</h3>
 * <p>
 *   {@link IoSession} is thread-safe.  But please note that performing
 *   more than one {@link #write(Object)} calls at the same time will
 *   cause the {@link IoFilter#filterWrite(IoFilter.NextFilter,IoSession,WriteRequest)}
 *   to be executed simultaneously, and therefore you have to make sure the
 *   {@link IoFilter} implementations you're using are thread-safe, too.
 * </p>
 * <h3>Equality of Sessions</h3>
 * TODO : The getId() method is totally wrong. We can't base
 * a method which is designed to create a unique ID on the hashCode method.
 * {@link Object#equals(Object)} and {@link Object#hashCode()} shall not be overriden
 * to the default behavior that is defined in {@link Object}.
 *
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 */
public interface IoSession {
    /**
     * @return a unique identifier for this session.  Every session has its own
     * ID which is different from each other.
     * 
     * TODO : The way it's implemented does not guarantee that the contract is
     * respected. It uses the HashCode() method which don't guarantee the key
     * unicity.
     */
    long getId();

    /**
     * @return the {@link IoService} which provides I/O service to this session.
     */
    IoService getService();

    /**
     * @return the {@link IoHandler} which handles this session.
     */
    IoHandler getHandler();

    /**
     * @return the configuration of this session.
     */
    IoSessionConfig getConfig();

    /**
     * @return the filter chain that only affects this session.
     */
    IoFilterChain getFilterChain();

    /**
     * Get the queue that contains the message waiting for being written.
     * As the reader might not be ready, it's frequent that the messages
     * aren't written completely, or that some older messages are waiting
     * to be written when a new message arrives. This queue is used to manage
     * the backlog of messages.
     * 
     * @return The queue containing the pending messages.
     */
    WriteRequestQueue getWriteRequestQueue();

    /**
     * @return the {@link TransportMetadata} that this session runs on.
     */
    TransportMetadata getTransportMetadata();

    /**
     * TODO This javadoc is wrong. The return tag should be short.
     * 
     * @return a {@link ReadFuture} which is notified when a new message is
     * received, the connection is closed or an exception is caught.  This
     * operation is especially useful when you implement a client application.
     * TODO : Describe here how we enable this feature.
     * However, please note that this operation is disabled by default and
     * throw {@link IllegalStateException} because all received events must be
     * queued somewhere to support this operation, possibly leading to memory
     * leak.  This means you have to keep calling {@link #read()} once you
     * enabled this operation.  To enable this operation, please call
     * {@link IoSessionConfig#setUseReadOperation(boolean)} with <code>true</code>.
     *
     * @throws IllegalStateException if
     * {@link IoSessionConfig#setUseReadOperation(boolean) useReadOperation}
     * option has not been enabled.
     */
    ReadFuture read();

    /**
     * Writes the specified <code>message</code> to remote peer.  This
     * operation is asynchronous; {@link IoHandler#messageSent(IoSession,Object)}
     * will be invoked when the message is actually sent to remote peer.
     * You can also wait for the returned {@link WriteFuture} if you want
     * to wait for the message actually written.
     * 
     * @param message The message to write
     * @return The associated WriteFuture
     */
    WriteFuture write(Object message);

    /**
     * (Optional) Writes the specified <code>message</code> to the specified <code>destination</code>.
     * This operation is asynchronous; {@link IoHandler#messageSent(IoSession, Object)}
     * will be invoked when the message is actually sent to remote peer. You can
     * also wait for the returned {@link WriteFuture} if you want to wait for
     * the message actually written.
     * <p>
     * When you implement a client that receives a broadcast message from a server
     * such as DHCP server, the client might need to send a response message for the
     * broadcast message the server sent.  Because the remote address of the session
     * is not the address of the server in case of broadcasting, there should be a
     * way to specify the destination when you write the response message.
     * This interface provides {@link #write(Object, SocketAddress)} method so you
     * can specify the destination.
     * 
     * @param message The message to write
     * @param destination <code>null</code> if you want the message sent to the
     *                    default remote address
     * @return The associated WriteFuture
     */
    WriteFuture write(Object message, SocketAddress destination);

    /**
     * Closes this session immediately or after all queued write requests
     * are flushed.  This operation is asynchronous.  Wait for the returned
     * {@link CloseFuture} if you want to wait for the session actually closed.
     *
     * @param immediately {@code true} to close this session immediately
     *                    . The pending write requests
     *                    will simply be discarded.
     *                    {@code false} to close this session after all queued
     *                    write requests are flushed.
     * @return The associated CloseFuture
     * @deprecated Use either the {@link #closeNow()} or the {@link #closeOnFlush()} methods
     */
    @Deprecated
    CloseFuture close(boolean immediately);

    /**
     * Closes this session immediately.  This operation is asynchronous, it 
     * returns a {@link CloseFuture}.
     * 
     * @return The {@link CloseFuture} that can be use to wait for the completion of this operation
     */
    CloseFuture closeNow();

    /**
     * Closes this session after all queued write requests are flushed.  This operation 
     * is asynchronous.  Wait for the returned {@link CloseFuture} if you want to wait 
     * for the session actually closed.
     *
     * @return The associated CloseFuture
     */
    CloseFuture closeOnFlush();

    /**
     * Closes this session after all queued write requests
     * are flushed. This operation is asynchronous.  Wait for the returned
     * {@link CloseFuture} if you want to wait for the session actually closed.
     * @deprecated use {@link #closeNow()}
     * 
     * @return The associated CloseFuture
     */
    @Deprecated
    CloseFuture close();

    /**
     * Returns an attachment of this session.
     * This method is identical with <code>getAttribute( "" )</code>.
     *
     * @return The attachment
     * @deprecated Use {@link #getAttribute(Object)} instead.
     */
    @Deprecated
    Object getAttachment();

    /**
     * Sets an attachment of this session.
     * This method is identical with <code>setAttribute( "", attachment )</code>.
     *
     * @param attachment The attachment
     * @return Old attachment. <code>null</code> if it is new.
     * @deprecated Use {@link #setAttribute(Object, Object)} instead.
     */
    @Deprecated
    Object setAttachment(Object attachment);

    /**
     * Returns the value of the user-defined attribute of this session.
     *
     * @param key the key of the attribute
     * @return <code>null</code> if there is no attribute with the specified key
     */
    Object getAttribute(Object key);

    /**
     * Returns the value of user defined attribute associated with the
     * specified key.  If there's no such attribute, the specified default
     * value is associated with the specified key, and the default value is
     * returned.  This method is same with the following code except that the
     * operation is performed atomically.
     * <pre>
     * if (containsAttribute(key)) {
     *     return getAttribute(key);
     * } else {
     *     setAttribute(key, defaultValue);
     *     return defaultValue;
     * }
     * </pre>
     * 
     * @param key the key of the attribute we want to retreive
     * @param defaultValue the default value of the attribute
     * @return The retrieved attribute or <code>null</code> if not found
     */
    Object getAttribute(Object key, Object defaultValue);

    /**
     * Sets a user-defined attribute.
     *
     * @param key the key of the attribute
     * @param value the value of the attribute
     * @return The old value of the attribute.  <code>null</code> if it is new.
     */
    Object setAttribute(Object key, Object value);

    /**
     * Sets a user defined attribute without a value.  This is useful when
     * you just want to put a 'mark' attribute.  Its value is set to
     * {@link Boolean#TRUE}.
     *
     * @param key the key of the attribute
     * @return The old value of the attribute.  <code>null</code> if it is new.
     */
    Object setAttribute(Object key);

    /**
     * Sets a user defined attribute if the attribute with the specified key
     * is not set yet.  This method is same with the following code except
     * that the operation is performed atomically.
     * <pre>
     * if (containsAttribute(key)) {
     *     return getAttribute(key);
     * } else {
     *     return setAttribute(key, value);
     * }
     * </pre>
     * 
     * @param key The key of the attribute we want to set
     * @param value The value we want to set
     * @return The old value of the attribute.  <code>null</code> if not found.
     */
    Object setAttributeIfAbsent(Object key, Object value);

    /**
     * Sets a user defined attribute without a value if the attribute with
     * the specified key is not set yet.  This is useful when you just want to
     * put a 'mark' attribute.  Its value is set to {@link Boolean#TRUE}.
     * This method is same with the following code except that the operation
     * is performed atomically.
     * <pre>
     * if (containsAttribute(key)) {
     *     return getAttribute(key);  // might not always be Boolean.TRUE.
     * } else {
     *     return setAttribute(key);
     * }
     * </pre>
     * 
     * @param key The key of the attribute we want to set
     * @return The old value of the attribute.  <code>null</code> if not found.
     */
    Object setAttributeIfAbsent(Object key);

    /**
     * Removes a user-defined attribute with the specified key.
     *
     * @param key The key of the attribute we want to remove
     * @return The old value of the attribute.  <code>null</code> if not found.
     */
    Object removeAttribute(Object key);

    /**
     * Removes a user defined attribute with the specified key if the current
     * attribute value is equal to the specified value.  This method is same
     * with the following code except that the operation is performed
     * atomically.
     * <pre>
     * if (containsAttribute(key) &amp;&amp; getAttribute(key).equals(value)) {
     *     removeAttribute(key);
     *     return true;
     * } else {
     *     return false;
     * }
     * </pre>
     * 
     * @param key The key we want to remove
     * @param value The value we want to remove
     * @return <code>true</code> if the removal was successful
     */
    boolean removeAttribute(Object key, Object value);

    /**
     * Replaces a user defined attribute with the specified key if the
     * value of the attribute is equals to the specified old value.
     * This method is same with the following code except that the operation
     * is performed atomically.
     * <pre>
     * if (containsAttribute(key) &amp;&amp; getAttribute(key).equals(oldValue)) {
     *     setAttribute(key, newValue);
     *     return true;
     * } else {
     *     return false;
     * }
     * </pre>
     * 
     * @param key The key we want to replace
     * @param oldValue The previous value
     * @param newValue The new value
     * @return <code>true</code> if the replacement was successful
     */
    boolean replaceAttribute(Object key, Object oldValue, Object newValue);

    /**
     * @param key The key of the attribute we are looking for in the session 
     * @return <code>true</code> if this session contains the attribute with
     * the specified <code>key</code>.
     */
    boolean containsAttribute(Object key);

    /**
     * @return the set of keys of all user-defined attributes.
     */
    Set<Object> getAttributeKeys();

    /**
     * @return <code>true</code> if this session is connected with remote peer.
     */
    boolean isConnected();
    
    /**
     * @return <code>true</code> if this session is active.
     */
    boolean isActive();

    /**
     * @return <code>true</code> if and only if this session is being closed
     * (but not disconnected yet) or is closed.
     */
    boolean isClosing();
    
    /**
     * @return <code>true</code> if the session has started and initialized a SSLEngine,
     * <code>false</code> if the session is not yet secured (the handshake is not completed)
     * or if SSL is not set for this session, or if SSL is not even an option.
     */
    boolean isSecured();
    
    /**
     * @return <code>true</code> if the session was created by an acceptor.
     */
    boolean isServer();

    /**
     * @return the {@link CloseFuture} of this session.  This method returns
     * the same instance whenever user calls it.
     */
    CloseFuture getCloseFuture();

    /**
     * @return the socket address of remote peer.
     */
    SocketAddress getRemoteAddress();

    /**
     * @return the socket address of local machine which is associated with this
     * session.
     */
    SocketAddress getLocalAddress();

    /**
     * @return the socket address of the {@link IoService} listens to to manage
     * this session.  If this session is managed by {@link IoAcceptor}, it
     * returns the {@link SocketAddress} which is specified as a parameter of
     * {@link IoAcceptor#bind()}.  If this session is managed by
     * {@link IoConnector}, this method returns the same address with
     * that of {@link #getRemoteAddress()}.
     */
    SocketAddress getServiceAddress();

    /**
     * 
     * Associate the current write request with the session
     *
     * @param currentWriteRequest the current write request to associate
     */
    void setCurrentWriteRequest(WriteRequest currentWriteRequest);

    /**
     * Suspends read operations for this session.
     */
    void suspendRead();

    /**
     * Suspends write operations for this session.
     */
    void suspendWrite();

    /**
     * Resumes read operations for this session.
     */
    void resumeRead();

    /**
     * Resumes write operations for this session.
     */
    void resumeWrite();

    /**
     * Is read operation is suspended for this session. 
     * 
     * @return <code>true</code> if suspended
     */
    boolean isReadSuspended();

    /**
     * Is write operation is suspended for this session.
     * 
     * @return <code>true</code> if suspended
     */
    boolean isWriteSuspended();

    /**
     * Update all statistical properties related with throughput assuming
     * the specified time is the current time.  By default this method returns
     * silently without updating the throughput properties if they were
     * calculated already within last
     * {@link IoSessionConfig#getThroughputCalculationInterval() calculation interval}.
     * If, however, <code>force</code> is specified as <code>true</code>, this method
     * updates the throughput properties immediately.

     * @param currentTime the current time in milliseconds
     * @param force Force the update if <code>true</code>
     */
    void updateThroughput(long currentTime, boolean force);

    /**
     * @return the total number of bytes which were read from this session.
     */
    long getReadBytes();

    /**
     * @return the total number of bytes which were written to this session.
     */
    long getWrittenBytes();

    /**
     * @return the total number of messages which were read and decoded from this session.
     */
    long getReadMessages();

    /**
     * @return the total number of messages which were written and encoded by this session.
     */
    long getWrittenMessages();

    /**
     * @return the number of read bytes per second.
     */
    double getReadBytesThroughput();

    /**
     * @return the number of written bytes per second.
     */
    double getWrittenBytesThroughput();

    /**
     * @return the number of read messages per second.
     */
    double getReadMessagesThroughput();

    /**
     * @return the number of written messages per second.
     */
    double getWrittenMessagesThroughput();

    /**
     * @return the number of messages which are scheduled to be written to this session.
     */
    int getScheduledWriteMessages();

    /**
     * @return the number of bytes which are scheduled to be written to this
     * session.
     */
    long getScheduledWriteBytes();

    /**
     * Returns the message which is being written by {@link IoService}.
     * @return <code>null</code> if and if only no message is being written
     */
    Object getCurrentWriteMessage();

    /**
     * Returns the {@link WriteRequest} which is being processed by
     * {@link IoService}.
     *
     * @return <code>null</code> if and if only no message is being written
     */
    WriteRequest getCurrentWriteRequest();

    /**
     * @return the session's creation time in milliseconds
     */
    long getCreationTime();

    /**
     * @return the time in millis when I/O occurred lastly.
     */
    long getLastIoTime();

    /**
     * @return the time in millis when read operation occurred lastly.
     */
    long getLastReadTime();

    /**
     * @return the time in millis when write operation occurred lastly.
     */
    long getLastWriteTime();

    /**
     * @param status The researched idle status
     * @return <code>true</code> if this session is idle for the specified
     * {@link IdleStatus}.
     */
    boolean isIdle(IdleStatus status);

    /**
     * @return <code>true</code> if this session is {@link IdleStatus#READER_IDLE}.
     * @see #isIdle(IdleStatus)
     */
    boolean isReaderIdle();

    /**
     * @return <code>true</code> if this session is {@link IdleStatus#WRITER_IDLE}.
     * @see #isIdle(IdleStatus)
     */
    boolean isWriterIdle();

    /**
     * @return <code>true</code> if this session is {@link IdleStatus#BOTH_IDLE}.
     * @see #isIdle(IdleStatus)
     */
    boolean isBothIdle();

    /**
     * @param status The researched idle status
     * @return the number of the fired continuous <code>sessionIdle</code> events
     * for the specified {@link IdleStatus}.
     * <p>
     * If <code>sessionIdle</code> event is fired first after some time after I/O,
     * <code>idleCount</code> becomes <code>1</code>.  <code>idleCount</code> resets to
     * <code>0</code> if any I/O occurs again, otherwise it increases to
     * <code>2</code> and so on if <code>sessionIdle</code> event is fired again without
     * any I/O between two (or more) <code>sessionIdle</code> events.
     */
    int getIdleCount(IdleStatus status);

    /**
     * @return the number of the fired continuous <code>sessionIdle</code> events
     * for {@link IdleStatus#READER_IDLE}.
     * @see #getIdleCount(IdleStatus)
     */
    int getReaderIdleCount();

    /**
     * @return the number of the fired continuous <code>sessionIdle</code> events
     * for {@link IdleStatus#WRITER_IDLE}.
     * @see #getIdleCount(IdleStatus)
     */
    int getWriterIdleCount();

    /**
     * @return the number of the fired continuous <code>sessionIdle</code> events
     * for {@link IdleStatus#BOTH_IDLE}.
     * @see #getIdleCount(IdleStatus)
     */
    int getBothIdleCount();

    /**
     * @param status The researched idle status
     * @return the time in milliseconds when the last <code>sessionIdle</code> event
     * is fired for the specified {@link IdleStatus}.
     */
    long getLastIdleTime(IdleStatus status);

    /**
     * @return the time in milliseconds when the last <code>sessionIdle</code> event
     * is fired for {@link IdleStatus#READER_IDLE}.
     * @see #getLastIdleTime(IdleStatus)
     */
    long getLastReaderIdleTime();

    /**
     * @return the time in milliseconds when the last <code>sessionIdle</code> event
     * is fired for {@link IdleStatus#WRITER_IDLE}.
     * @see #getLastIdleTime(IdleStatus)
     */
    long getLastWriterIdleTime();

    /**
     * @return the time in milliseconds when the last <code>sessionIdle</code> event
     * is fired for {@link IdleStatus#BOTH_IDLE}.
     * @see #getLastIdleTime(IdleStatus)
     */
    long getLastBothIdleTime();
}
