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

import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.apache.ignite.internal.client.balancer.GridClientLoadBalancer;
import org.jetbrains.annotations.Nullable;

/**
 * A compute projection of grid client. Contains various methods for task execution, full and partial (per node)
 * topology refresh, and log viewing. An initial instance of compute projection over the whole remote grid is
 * provided via {@link GridClient#compute()} method. Further sub-projections may be created via
 * any of the {@code projection(...)} methods on this API.
 * <p>
 * You can create custom client projections based on any user-defined filtering. For example you can create
 * a projection over a certain group of nodes, or over all nodes that have a certain attribute. Once projection
 * is created, only nodes that belong to this projection will be contacted on remote grid for any operation.
 * This essentially allows users to create virtual subgrids for remote task execution.
 * <p>
 * Use any of the {@code execute(...)} methods to execute tasks on remote grid. Note that tasks need
 * to be deployed to remote grid first prior to execution. You can also use any of the
 * {@code affinityExecute(...)} methods to execute tasks on node that have affinity with some data key.
 * This way you can collocate your computation with the data cached on remote nodes.
 * <p>
 * You can also use any of the {@code refreshNode(...)} or {@code refreshTopology(...)} methods
 * to eagerly refresh metrics or attributes on remote nodes (note that attributes are static,
 * so it is sufficient to fetch and cache them only once). Metrics and attributes will be
 * cached in {@link GridClientNode} instances automatically if {@link GridClientConfiguration#isEnableMetricsCache()}
 * or {@link GridClientConfiguration#isEnableAttributesCache()} property is set to {@code true}.
 * <p>
 * Compute client also allows fetching contents of remote log files (including backwards mode) via any of
 * the provided {@code log(...)} methods.
 * <h1 class="header">Partition awareness</h1>
 * One of the unique properties of the Ignite remote clients is that they are
 * affinity aware. In other words, both compute and data APIs will optionally
 * contact exactly the node where the data is cached based on some affinity key.
 * This allows for collocation of computations and data and avoids extra network
 * hops that would be necessary if non-affinity nodes were contacted. Use
 * {@link #affinityExecute(String, String, Object, Object)} and
 * {@link #affinityExecuteAsync(String, String, Object, Object)} to synchronously
 * or asynchronously execute remote tasks on affinity nodes based on provided
 * affinity keys.
 */
public interface GridClientCompute {
    /**
     * Creates a projection that will communicate only with specified remote node.
     * <p>
     * If current projection is dynamic projection, then this method will check is passed node is in topology.
     * If any filters were specified in current topology, this method will check if passed node is accepted by
     * the filter. If current projection was restricted to any subset of nodes, this method will check if
     * passed node is in that subset. If any of the checks fails an exception will be thrown.
     *
     * @param node Single node to which this projection will be restricted.
     * @return Resulting static projection that is bound to a given node.
     * @throws GridClientException If resulting projection is empty.
     */
    public GridClientCompute projection(GridClientNode node) throws GridClientException;

    /**
     * Creates a projection that will communicate only with nodes that are accepted by the passed filter.
     * <p>
     * If current projection is dynamic projection, then filter will be applied to the most relevant
     * topology snapshot every time a node to communicate is selected. If current projection is a static projection,
     * then resulting projection will only be restricted to nodes that were in parent projection and were
     * accepted by the passed filter. If any of the checks fails an exception will be thrown.
     *
     * @param filter Filter that will select nodes for projection. If {@code null},
     *      then no filter would be applied to nodes in projection.
     * @return Resulting projection (static or dynamic, depending in parent projection type).
     * @throws GridClientException If resulting projection is empty.
     */
    public GridClientCompute projection(GridClientPredicate<? super GridClientNode> filter) throws GridClientException;

    /**
     * Creates a projection that will communicate only with specified remote nodes. For any particular call
     * a node to communicate will be selected with balancer of this projection.
     * <p>
     * If current projection is dynamic projection, then this method will check is passed nodes are in topology.
     * If any filters were specified in current topology, this method will check if passed nodes are accepted by
     * the filter. If current projection was restricted to any subset of nodes, this method will check if
     * passed nodes are in that subset (i.e. calculate the intersection of two collections).
     * If any of the checks fails an exception will be thrown.
     *
     * @param nodes Collection of nodes to which this projection will be restricted. If {@code null},
     *      created projection is dynamic and will take nodes from topology.
     * @return Resulting static projection that is bound to a given nodes.
     * @throws GridClientException If resulting projection is empty.
     */
    public GridClientCompute projection(Collection<GridClientNode> nodes) throws GridClientException;

    /**
     * Creates a projection that will communicate only with nodes that are accepted by the passed filter. The
     * balancer passed will override default balancer specified in configuration.
     * <p>
     * If current projection is dynamic projection, then filter will be applied to the most relevant
     * topology snapshot every time a node to communicate is selected. If current projection is a static projection,
     * then resulting projection will only be restricted to nodes that were in parent projection and were
     * accepted by the passed filter. If any of the checks fails an exception will be thrown.
     *
     * @param filter Filter that will select nodes for projection. If {@code null},
     *      then no filter would be applied to nodes in projection.
     * @param balancer Balancer that will select balanced node in resulting projection. If {@code null},
     *      then balancer which was specified while projection construction will be used.
     * @return Resulting projection (static or dynamic, depending in parent projection type).
     * @throws GridClientException If resulting projection is empty.
     */
    public GridClientCompute projection(GridClientPredicate<? super GridClientNode> filter,
        GridClientLoadBalancer balancer) throws GridClientException;

    /**
     * Creates a projection that will communicate only with specified remote nodes. For any particular call
     * a node to communicate will be selected with passed balancer..
     * <p>
     * If current projection is dynamic projection, then this method will check is passed nodes are in topology.
     * If any filters were specified in current topology, this method will check if passed nodes are accepted by
     * the filter. If current projection was restricted to any subset of nodes, this method will check if
     * passed nodes are in that subset (i.e. calculate the intersection of two collections).
     * If any of the checks fails an exception will be thrown.
     *
     * @param nodes Collection of nodes to which this projection will be restricted. If {@code null},
     *      then no filter would be applied to nodes in projection.
     * @param balancer Balancer that will select nodes in resulting projection. If {@code null},
     *      then balancer which was specified while projection construction will be used.
     * @return Resulting static projection that is bound to a given nodes.
     * @throws GridClientException If resulting projection is empty.
     */
    public GridClientCompute projection(Collection<GridClientNode> nodes, GridClientLoadBalancer balancer)
        throws GridClientException;

    /**
     * Gets load balancer used by this projection. By default, the balancer specified
     * in {@link GridClientConfiguration#getBalancer()} property is used. User can
     * provide custom balancers for different projections via
     * {@link #projection(GridClientPredicate, GridClientLoadBalancer)} method.
     *
     * @return Instance of {@link GridClientLoadBalancer} used by this projection.
     */
    public GridClientLoadBalancer balancer();

    /**
     * Executes task on remote grid. Only nodes included into this projection will
     * be contacted. Note that task must be deployed on remote grid prior to the
     * execution.
     *
     * @param taskName Task name or task class name.
     * @param taskArg Optional task argument.
     * @return Task execution result.
     * @throws GridClientException In case of error.
     * @throws GridServerUnreachableException If none of the servers can be reached.
     * @throws GridClientClosedException If client was closed manually.
     */
    public <R> R execute(String taskName, Object taskArg) throws GridClientException;

    /**
     * Asynchronously executes task on remote grid. Only nodes included into this projection will
     * be contacted. Note that task must be deployed on remote grid prior to the
     * execution.
     *
     * @param taskName Task name or task class name.
     * @param taskArg Optional task argument.
     * @return Future for remote execution.
     */
    public <R> GridClientFuture<R> executeAsync(String taskName, Object taskArg);

    /**
     * Executes task using cache affinity key for routing. This way the task will start executing
     * exactly on the node where this affinity key is cached hence allowing for
     * collocation of computations and data.
     *
     * @param taskName Task name or task class name.
     * @param cacheName Name of the cache on which affinity should be calculated.  If {@code null},
     *      then default cache will be used.
     * @param affKey Affinity key.
     * @param taskArg Optional task argument.
     * @return Task execution result.
     * @throws GridClientException In case of error.
     * @throws GridServerUnreachableException If none of the servers can be reached.
     * @throws GridClientClosedException If client was closed manually.
     */
    public <R> R affinityExecute(String taskName, String cacheName, Object affKey, Object taskArg)
        throws GridClientException;

    /**
     * Asynchronously executes task using cache affinity key for routing. This way
     * the task will start executing exactly on the node where this affinity key is cached
     * hence allowing for collocation of computations and data.
     *
     * @param taskName Task name or task class name.
     * @param cacheName Name of the cache on which affinity should be calculated.  If {@code null},
     *      then balancer which was specified while projection construction will be used.
     * @param affKey Affinity key.
     * @param taskArg Optional task argument.
     * @return Future for the remote execution.
     */
    public <R> GridClientFuture<R> affinityExecuteAsync(String taskName, String cacheName, Object affKey,
        Object taskArg);

    /**
     * Gets most recently refreshed topology.
     * If this compute instance is a projection, then only nodes that
     * satisfy projection criteria will be returned.
     *
     * @return Most recently refreshed topology.
     * @throws GridClientException If client doesn't have an actual topology version.
     */
    public Collection<GridClientNode> nodes() throws GridClientException;

    /**
     * Gets cached node with given id from most recently refreshed topology.
     *
     * @param id Node ID.
     * @return Node for given ID or {@code null} if node with given id was not found.
     * @throws GridClientException If client doesn't have an actual topology version.
     */
    public GridClientNode node(UUID id) throws GridClientException;

    /**
     * Gets cached nodes for the given IDs based on most recently refreshed topology.
     * If this compute instance is a projection, then only nodes that passes projection
     * criteria will be returned.
     *
     * @param ids Node IDs.
     * @return Collection of nodes for provided IDs.
     * @throws GridClientException If client doesn't have an actual topology version.
     */
    public Collection<GridClientNode> nodes(Collection<UUID> ids) throws GridClientException;

    /**
     * Gets all cached nodes that pass the filter. If this compute instance is a projection, then only
     * nodes that passes projection criteria will be passed to the filter.
     *
     * @param filter Node filter.
     * @return Collection of nodes that satisfy provided filter.
     * @throws GridClientException If client doesn't have an actual topology version.
     */
    public Collection<GridClientNode> nodes(GridClientPredicate<GridClientNode> filter)
        throws GridClientException;

    /**
     * Refreshes and returns node by its ID from remote grid. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()} parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to true.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param id Node ID.
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Node descriptor or {@code null} if node doesn't exist.
     * @throws GridClientException In case request failed.
     * @throws GridServerUnreachableException If none of the servers can be reached.
     * @throws GridClientClosedException If client was closed manually.
     */
    public GridClientNode refreshNode(UUID id, boolean includeAttrs, boolean includeMetrics) throws GridClientException;

    /**
     * Asynchronously refreshes and returns node by its ID from remote grid. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()} parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to true.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param id Node ID.
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Future for the refresh
     */
    public GridClientFuture<GridClientNode> refreshNodeAsync(UUID id, boolean includeAttrs, boolean includeMetrics);

    /**
     * Refreshes and returns node by its IP address from remote grid. All possible IP addresses
     * of a node will be checked. If there is more than one node for given IP address, then
     * first found node will be refreshed. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()} parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to true.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param ip IP address.
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Node descriptor or {@code null} if node doesn't exist.
     * @throws GridClientException In case of error.
     * @throws GridServerUnreachableException If none of the servers can be reached.
     * @throws GridClientClosedException If client was closed manually.
     */
    @Nullable public GridClientNode refreshNode(String ip, boolean includeAttrs, boolean includeMetrics)
        throws GridClientException;

    /**
     * Asynchronously refreshes and returns node by its IP address from remote grid. All possible IP addresses
     * of a node will be checked. If there is more than one node for given IP address, then
     * first found node will be refreshed. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()} parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to true.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param ip IP address.
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Future for the refresh operation.
     */
    public GridClientFuture<GridClientNode> refreshNodeAsync(String ip, boolean includeAttrs, boolean includeMetrics);

    /**
     * Refreshes and returns all nodes within topology. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()} parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to {@code true}.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Node descriptors.
     * @throws GridClientException In case of error.
     * @throws GridServerUnreachableException If none of the servers can be reached.
     * @throws GridClientClosedException If client was closed manually.
     */
    public List<GridClientNode> refreshTopology(boolean includeAttrs, boolean includeMetrics)
        throws GridClientException;

    /**
     * Asynchronously refreshes and returns all nodes within topology. Use {@code includeAttrs} and
     * {@code includeMetrics} parameters to automatically fetch remote node attributes and
     * metrics.
     * <p>
     * Note that fetched attributes and metrics may or may note be cached in {@link GridClientNode}
     * based on {@link GridClientConfiguration#isEnableMetricsCache()} and
     * {@link GridClientConfiguration#isEnableAttributesCache()}parameters. Even though topology
     * is refreshed automatically every {@link GridClientConfiguration#getTopologyRefreshFrequency()}
     * interval, node metrics and attributes will be fetched in background only if
     * {@link GridClientConfiguration#isAutoFetchMetrics()} or
     * {@link GridClientConfiguration#isAutoFetchAttributes()} set to {@code true}.
     * <p>
     * Also note that node attributes are static and, if cached, there is no need
     * to refresh them again.
     *
     * @param includeAttrs Whether to include node attributes.
     * @param includeMetrics Whether to include node metrics.
     * @return Future.
     */
    public GridClientFuture<List<GridClientNode>> refreshTopologyAsync(boolean includeAttrs, boolean includeMetrics);

    /**
     * Sets keep binary flag for the next task execution in the current thread.
     */
    public GridClientCompute withKeepBinaries();
}
