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

import java.util.Collection;
import java.util.UUID;
import org.apache.ignite.cluster.ClusterGroup;
import org.apache.ignite.cluster.ClusterGroupEmptyException;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.lang.IgniteAsyncSupport;
import org.apache.ignite.lang.IgniteAsyncSupported;
import org.apache.ignite.lang.IgniteBiPredicate;
import org.apache.ignite.lang.IgniteFuture;
import org.jetbrains.annotations.Nullable;

/**
 * Provides functionality for topic-based message exchange among nodes defined by {@link #clusterGroup()}.
 * Users can send ordered and unordered messages to various topics. Note that same topic name
 * cannot be reused between ordered and unordered messages.
 * <p>
 * Instance of {@code IgniteMessaging} is obtained from {@link Ignite} as follows:
 * <pre class="brush:java">
 * Ignite ignite = Ignition.ignite();
 *
 * // Messaging instance spanning all cluster nodes.
 * IgniteMessaging m = ignite.message();
 * </pre>
 * You can also obtain an instance of messaging facade over a specific cluster group:
 * <pre class="brush:java">
 * // Cluster group over remote nodes (excluding the local node).
 * ClusterGroup remoteNodes = ignite.cluster().forRemotes();
 *
 * // Messaging instance spanning all remote cluster nodes.
 * IgniteMessaging m = ignite.message(remoteNodes);
 * </pre>
 * <p>
 * There are {@code 2} ways to subscribe to message listening, {@code local} and {@code remote}.
 * <p>
 * Local subscription, defined by {@link #localListen(Object, IgniteBiPredicate)} method, will add
 * a listener for a given topic on local node only. This listener will be notified whenever any
 * node within the underlying cluster group will send a message for a given topic to this node. Local listen
 * subscription will happen regardless of whether the local node belongs to this cluster group or not.
 * <p>
 * Remote subscription, defined by {@link #remoteListen(Object, IgniteBiPredicate)}, will add a
 * message listener for a given topic to all nodes in the underlying cluster group (possibly including this node if
 * it belongs to the cluster group as well). This means that any node within this cluster group can send
 * a message for a given topic and all nodes within the cluster group will receive listener notifications.
 * <h1 class="header">Ordered vs Unordered</h1>
 * Ignite allows for sending ordered messages (see {@link #sendOrdered(Object, Object, long)}), i.e.
 * messages will be received in the same order they were sent. Ordered messages have a {@code timeout}
 * parameter associated with them which specifies how long an out-of-order message will stay in a queue,
 * waiting for messages that are ordered ahead of it to arrive. If timeout expires, then all ordered
 * messages for a given topic that have not arrived yet will be skipped. When (and if) expired messages
 * actually do arrive, they will be ignored.
 */
public interface IgniteMessaging extends IgniteAsyncSupport {
    /**
     * Gets the cluster group to which this {@code GridMessaging} instance belongs.
     *
     * @return Cluster group to which this {@code GridMessaging} instance belongs.
     */
    public ClusterGroup clusterGroup();

    /**
     * Sends given message with specified topic to the nodes in the underlying cluster group.
     * <p>
     * By default all local listeners will be executed in the calling thread, or if you use
     * {@link #withAsync()}, listeners will execute in public thread pool (in this case it is user's
     * responsibility to implement back-pressure and limit number of concurrently executed async messages).
     *
     * @param topic Topic to send to, {@code null} for default topic.
     * @param msg Message to send.
     * @throws IgniteException If failed to send a message to any of the nodes.
     * @throws ClusterGroupEmptyException Thrown in case when cluster group is empty.
     */
    public void send(@Nullable Object topic, Object msg) throws IgniteException;

    /**
     * Sends given messages with the specified topic to the nodes in the underlying cluster group.
     * <p>
     * By default all local listeners will be executed in the calling thread, or if you use
     * {@link #withAsync()}, listeners will execute in public thread pool (in this case it is user's
     * responsibility to implement back-pressure and limit number of concurrently executed async messages).
     *
     * @param topic Topic to send to, {@code null} for default topic.
     * @param msgs Messages to send. Order of the sending is undefined. If the method produces
     *      the exception none or some messages could have been sent already.
     * @throws IgniteException If failed to send a message to any of the nodes.
     * @throws ClusterGroupEmptyException Thrown in case when cluster group is empty.
     */
    public void send(@Nullable Object topic, Collection<?> msgs) throws IgniteException;

    /**
     * Sends given message with specified topic to the nodes in the underlying cluster group. Messages sent with
     * this method will arrive in the same order they were sent. Note that if a topic is used
     * for ordered messages, then it cannot be reused for non-ordered messages. Note that local listeners
     * are always executed in public thread pool, no matter default or {@link #withAsync()} mode is used.
     * <p>
     * The {@code timeout} parameter specifies how long an out-of-order message will stay in a queue,
     * waiting for messages that are ordered ahead of it to arrive. If timeout expires, then all ordered
     * messages that have not arrived before this message will be skipped. When (and if) expired messages
     * actually do arrive, they will be ignored.
     *
     * @param topic Topic to send to, {@code null} for default topic.
     * @param msg Message to send.
     * @param timeout Message timeout in milliseconds, {@code 0} for default
     *      which is {@link IgniteConfiguration#getNetworkTimeout()}.
     * @throws IgniteException If failed to send a message to any of the nodes.
     * @throws ClusterGroupEmptyException Thrown in case when cluster group is empty.
     */
    public void sendOrdered(@Nullable Object topic, Object msg, long timeout) throws IgniteException;

    /**
     * Adds local listener for given topic on local node only. This listener will be notified whenever any
     * node within the cluster group will send a message for a given topic to this node. Local listen
     * subscription will happen regardless of whether local node belongs to this cluster group or not.
     *
     * @param topic Topic to subscribe to.
     * @param p Predicate that is called on each received message. If predicate returns {@code false},
     *      then it will be unsubscribed from any further notifications.
     */
    public void localListen(@Nullable Object topic, IgniteBiPredicate<UUID, ?> p);

    /**
     * Unregisters local listener for given topic on local node only.
     *
     * @param topic Topic to unsubscribe from.
     * @param p Listener predicate.
     */
    public void stopLocalListen(@Nullable Object topic, IgniteBiPredicate<UUID, ?> p);

    /**
     * Adds a message listener for a given topic to all nodes in the cluster group (possibly including
     * this node if it belongs to the cluster group as well). This means that any node within this cluster
     * group can send a message for a given topic and all nodes within the cluster group will receive
     * listener notifications.
     *
     * @param topic Topic to subscribe to, {@code null} means default topic.
     * @param p Predicate that is called on each node for each received message. If predicate returns {@code false},
     *      then it will be unsubscribed from any further notifications.
     * @return {@code Operation ID} that can be passed to {@link #stopRemoteListen(UUID)} method to stop listening.
     * @throws IgniteException If failed to add listener.
     */
    @IgniteAsyncSupported
    public UUID remoteListen(@Nullable Object topic, IgniteBiPredicate<UUID, ?> p) throws IgniteException;

    /**
     * Asynchronously adds a message listener for a given topic to all nodes in the cluster group (possibly including
     * this node if it belongs to the cluster group as well). This means that any node within this cluster
     * group can send a message for a given topic and all nodes within the cluster group will receive
     * listener notifications.
     *
     * @param topic Topic to subscribe to, {@code null} means default topic.
     * @param p Predicate that is called on each node for each received message. If predicate returns {@code false},
     *      then it will be unsubscribed from any further notifications.
     * @return a Future representing pending completion of the operation. The completed future contains
     *      {@code Operation ID} that can be passed to {@link #stopRemoteListen(UUID)} method to stop listening.
     * @throws IgniteException If failed to add listener.
     */
    public IgniteFuture<UUID> remoteListenAsync(@Nullable Object topic, IgniteBiPredicate<UUID, ?> p)
        throws IgniteException;

    /**
     * Unregisters all listeners identified with provided operation ID on all nodes in the cluster group.
     * <p>
     * Supports asynchronous execution (see {@link IgniteAsyncSupport}).
     *
     * @param opId Listen ID that was returned from {@link #remoteListen(Object, IgniteBiPredicate)} method.
     * @throws IgniteException If failed to unregister listeners.
     */
    @IgniteAsyncSupported
    public void stopRemoteListen(UUID opId) throws IgniteException;

    /**
     * Asynchronously unregisters all listeners identified with provided operation ID on all nodes in the cluster group.
     *
     * @param opId Listen ID that was returned from {@link #remoteListen(Object, IgniteBiPredicate)} method.
     * @return a Future representing pending completion of the operation.
     * @throws IgniteException If failed to unregister listeners.
     */
    public IgniteFuture<Void> stopRemoteListenAsync(UUID opId) throws IgniteException;

    /** {@inheritDoc} */
    @Deprecated
    @Override IgniteMessaging withAsync();
}
