/*
 * Copyright 2014-2019 Real Logic Ltd.
 *
 * 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.aeron.cluster.service;

import io.aeron.Aeron;
import io.aeron.cluster.client.ClusterException;
import io.aeron.logbuffer.Header;
import org.agrona.DirectBuffer;

import java.util.Collection;

/**
 * Interface for a {@link ClusteredService} to interact with cluster hosting it.
 */
public interface Cluster
{
    enum Role
    {
        /**
         * The cluster node is a follower in the current leadership term.
         */
        FOLLOWER(0),

        /**
         * The cluster node is a candidate to become a leader in an election.
         */
        CANDIDATE(1),

        /**
         * The cluster node is the leader for the current leadership term.
         */
        LEADER(2);

        static final Role[] ROLES;

        static
        {
            final Role[] roles = values();
            ROLES = new Role[roles.length];
            for (final Role role : roles)
            {
                final int code = role.code();
                if (null != ROLES[code])
                {
                    throw new ClusterException("code already in use: " + code);
                }

                ROLES[code] = role;
            }
        }

        private final int code;

        Role(final int code)
        {
            this.code = code;
        }

        /**
         * The code which matches the role in the cluster.
         *
         * @return the code which matches the role in the cluster.
         */
        public final int code()
        {
            return code;
        }

        /**
         * Get the role from a code read from a counter.
         *
         * @param code for the {@link Role}.
         * @return the {@link Role} of the cluster node.
         */
        public static Role get(final int code)
        {
            if (code < 0 || code > (ROLES.length - 1))
            {
                throw new IllegalStateException("Invalid role counter code: " + code);
            }

            return ROLES[code];
        }
    }

    /**
     * The unique id for the hosting member of the cluster. Useful only for debugging purposes.
     *
     * @return unique id for the hosting member of the cluster.
     */
    int memberId();

    /**
     * The role the cluster node is playing.
     *
     * @return the role the cluster node is playing.
     */
    Role role();

    /**
     * Get the {@link Aeron} client used by the cluster.
     *
     * @return the {@link Aeron} client used by the cluster.
     */
    Aeron aeron();

    /**
     * Get the  {@link ClusteredServiceContainer.Context} under which the container is running.
     *
     * @return the {@link ClusteredServiceContainer.Context} under which the container is running.
     */
    ClusteredServiceContainer.Context context();

    /**
     * Get the {@link ClientSession} for a given cluster session id.
     *
     * @param clusterSessionId to be looked up.
     * @return the {@link ClientSession} that matches the clusterSessionId.
     */
    ClientSession getClientSession(long clusterSessionId);

    /**
     * Get the current collection of cluster client sessions.
     *
     * @return the current collection of cluster client sessions.
     */
    Collection<ClientSession> clientSessions();

    /**
     * Request the close of a {@link ClientSession} by sending the request to the consensus module.
     *
     * @param clusterSessionId to be closed.
     * @return true if the event to close a session was sent or false if back pressure was applied.
     * @throws IllegalArgumentException if the clusterSessionId is not recognised.
     */
    boolean closeSession(long clusterSessionId);

    /**
     * Current Epoch time in milliseconds.
     *
     * @return Epoch time in milliseconds.
     */
    long timeMs();

    /**
     * Schedule a timer for a given deadline and provide a correlation id to identify the timer when it expires or
     * for cancellation.
     * <p>
     * If the correlationId is for an existing scheduled timer then it will be reschedule to the new deadline.
     * <p>
     * Timers should only be scheduled or cancelled in the context of processing a
     * {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)} or
     * {@link ClusteredService#onTimerEvent(long, long)}. If applied to other events then they are not guaranteed
     * to be reliable.
     *
     * @param correlationId to identify the timer when it expires.
     * @param deadlineMs Epoch time in milliseconds after which the timer will fire.
     * @return true if the event to schedule a timer has been sent or false if back pressure is applied.
     * @see #cancelTimer(long)
     */
    boolean scheduleTimer(long correlationId, long deadlineMs);

    /**
     * Cancel a previous scheduled timer.
     * <p>
     * Timers should only be scheduled or cancelled in the context of processing a
     * {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)} or
     * {@link ClusteredService#onTimerEvent(long, long)}. If applied to other events then they are not guaranteed
     * to be reliable.
     *
     * @param correlationId for the timer provided when it was scheduled.
     * @return true if the event to cancel a scheduled timer has been sent or false if back pressure is applied.
     * @see #scheduleTimer(long, long)
     */
    boolean cancelTimer(long correlationId);

    /**
     * Should be called by the service when it experiences back-pressure on egress, closing sessions, or making
     * timer requests.
     */
    void idle();

    /**
     * Should be called by the service when it experiences back-pressure on egress, closing sessions, or making
     * timer requests.
     *
     * @param workCount a value of 0 will reset the idle strategy is a progressive back-off has been applied.
     */
    void idle(int workCount);
}

@@ -696,24 +696,68 @@
 rException;%0A
+import io.aeron.cluster.codecs.CloseReason;%0A
 import io.ae
@@ -4868,35 +4868,33 @@
 t, int, Header)%7D
- or
+,
 %0A     * %7B@link C
@@ -4926,33 +4926,193 @@
 ent(long, long)%7D
-.
+,%0A     * %7B@link ClusteredService#onSessionOpen(ClientSession, long)%7D, or%0A     * %7B@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)%7D.%0A     *
  If applied to o
@@ -5143,39 +5143,32 @@
 e not guaranteed
-%0A     *
  to be reliable.
@@ -5781,19 +5781,17 @@
 Header)%7D
- or
+,
 %0A     * 
@@ -5839,17 +5839,177 @@
 , long)%7D
-.
+,%0A     * %7B@link ClusteredService#onSessionOpen(ClientSession, long)%7D, or%0A     * %7B@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)%7D.%0A     *
  If appl
@@ -6056,23 +6056,16 @@
 aranteed
-%0A     *
  to be r

Complete the list of callbacks from which timers can be scheduled and cancelled.