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

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgnitePredicate;
import org.jetbrains.annotations.Nullable;

/**
 * Convenient node predicate as a separate class. It allows to avoid "dragging" enclosing
 * class's state when predicates are created as anonymous classes in stateful enclosing context.
 * This class is also optimized for evaluation of large number of nodes.
 */
public class GridNodePredicate implements IgnitePredicate<ClusterNode>, Iterable<UUID> {
    /** */
    private static final long serialVersionUID = 0L;

    /** */
    @GridToStringInclude
    private final Set<UUID> ids;

    /** */
    @GridToStringExclude
    private int hash = Integer.MIN_VALUE;

    /**
     * Creates node predicate that evaluates to {@code true} for all
     * provided node IDs. Implementation will not make a defensive copy.
     *
     * @param ids Optional node IDs. If none provided - predicate will always return {@code false}.
     */
    public GridNodePredicate(Set<UUID> ids) {
        assert ids != null;

        this.ids = ids;
    }

    /**
     * Creates node predicate that evaluates to {@code true} for all
     * provided node IDs. Implementation will make a defensive copy.
     *
     * @param ids Optional node IDs. If none provided - predicate will always return {@code false}.
     */
    public GridNodePredicate(@Nullable Collection<UUID> ids) {
        this.ids = F.isEmpty(ids) ? Collections.<UUID>emptySet() : ids.size() == 1 ?
            Collections.singleton(F.first(ids)) : new HashSet<>(ids);
    }

    /**
     * Creates node predicate that evaluates to {@code true} for all
     * provided node IDs. Implementation will make a defensive copy.
     *
     * @param ids Optional node IDs. If none provided - predicate will always return {@code false}.
     */
    public GridNodePredicate(@Nullable UUID... ids) {
        if (F.isEmpty(ids))
            this.ids = Collections.emptySet();
        else if (ids.length == 1)
            this.ids = Collections.singleton(ids[0]);
        else {
            this.ids = U.newHashSet(ids.length);

            Collections.addAll(this.ids, ids);
        }
    }

    /**
     * Creates node predicate that evaluates to {@code true} for all
     * provided nodes. Implementation will make a defensive copy.
     *
     * @param nodes Optional grid nodes. If none provided - predicate
     *      will always return {@code false}.
     */
    public GridNodePredicate(@Nullable ClusterNode... nodes) {
        if (F.isEmpty(nodes))
            ids = Collections.emptySet();
        else if (nodes.length == 1) {
            ClusterNode node = nodes[0];

            ids = node != null ? Collections.singleton(node.id()) : Collections.<UUID>emptySet();
        }
        else {
            ids = U.newHashSet(nodes.length);

            for (ClusterNode n : nodes) {
                if (n != null)
                    ids.add(n.id());
            }
        }
    }

    /**
     * Gets set of node IDs this predicate is based on. Note that for performance
     * reasons this methods return the internal set that <b>should not</b> be
     * modified by the caller.
     *
     * @return Set of node IDs this predicate is based on.
     */
    public Set<UUID> nodeIds() {
        return ids;
    }

    /** {@inheritDoc} */
    @Override public Iterator<UUID> iterator() {
        return ids.iterator();
    }

    /** {@inheritDoc} */
    @Override public boolean apply(ClusterNode n) {
        assert n != null;

        return ids.contains(n.id());
    }

    /** {@inheritDoc} */
    @Override public int hashCode() {
        // Allow for multiple hash calculations to avoid
        // synchronization cost. Note that array of IDs don't change.
        if (hash == Integer.MIN_VALUE)
            hash = ids.hashCode();

        return hash;
    }

    /** {@inheritDoc} */
    @Override public boolean equals(Object o) {
        if (this == o)
            return true;

        if (!(o instanceof GridNodePredicate))
            return false;

        GridNodePredicate it = (GridNodePredicate)o;

        return ids.equals(it.ids);
    }

    /** {@inheritDoc} */
    @Override public String toString() {
        return S.toString(GridNodePredicate.class, this);
    }
}
