// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.lib.graph;

import com.google.common.collect.Sets;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * <p>A generic directed-graph Node class.  Type parameter T is the type
 * of the node's label.
 *
 * <p>Each node is identified by a label, which is unique within the graph
 * owning the node.
 *
 * <p>Nodes are immutable, that is, their labels cannot be changed.  However,
 * their predecessor/successor lists are mutable.
 *
 * <p>Nodes cannot be created directly by clients.
 *
 * <p>Clients should not confuse nodes belonging to two different graphs!  (Use
 * Digraph.checkNode() to catch such errors.)  There is no way to find the
 * graph to which a node belongs; it is intentionally not represented, to save
 * space.
 */
public final class Node<T> {

  private static final int ARRAYLIST_THRESHOLD = 6;
  private static final int INITIAL_HASHSET_CAPACITY = 12;

  // The succs and preds set representation changes depending on its size.
  // It is implemented using the following collections:
  // - null for size = 0.
  // - Collections$SingletonList for size = 1.
  // - ArrayList(6) for size = [2..6].
  // - HashSet(12) for size > 6.
  // These numbers were chosen based on profiling.

  private final T label;

  /**
   * A duplicate-free collection of edges from this node.  May be null,
   * indicating the empty set.
   */
  private Collection<Node<T>> succs = null;

  /**
   * A duplicate-free collection of edges to this node.  May be null,
   * indicating the empty set.
   */
  private Collection<Node<T>> preds = null;

  private final int hashCode;

  /**
   * Only Digraph.createNode() can call this!
   */
  Node(T label, int hashCode) {
    if (label == null) { throw new NullPointerException("label"); }
    this.label = label;
    this.hashCode = hashCode;
  }

  /**
   * Returns the label for this node.
   */
  public T getLabel() {
    return label;
  }

  /**
   * Returns a duplicate-free collection of the nodes that this node links to.
   */
  public Collection<Node<T>> getSuccessors() {
    if (succs == null) {
      return Collections.emptyList();
    } else {
      return Collections.unmodifiableCollection(succs);
    }
  }

  /**
   * Equivalent to {@code !getSuccessors().isEmpty()} but possibly more
   * efficient.
   */
  public boolean hasSuccessors() {
    return succs != null;
  }

  /**
   * Equivalent to {@code getSuccessors().size()} but possibly more efficient.
   */
  public int numSuccessors() {
    return succs == null ? 0 : succs.size();
  }

  /**
   * Removes all edges to/from this node.
   * Private: breaks graph invariant!
   */
  void removeAllEdges() {
    this.succs = null;
    this.preds = null;
  }

  /**
   * Returns an (unordered, possibly immutable) set of the nodes that link to
   * this node.
   */
  public Collection<Node<T>> getPredecessors() {
    if (preds == null) {
      return Collections.emptyList();
    } else {
      return Collections.unmodifiableCollection(preds);
    }
  }

  /**
   * Equivalent to {@code getPredecessors().size()} but possibly more
   * efficient.
   */
  public int numPredecessors() {
    return preds == null ? 0 : preds.size();
  }

  /**
   * Equivalent to {@code !getPredecessors().isEmpty()} but possibly more
   * efficient.
   */
  public boolean hasPredecessors() {
    return preds != null;
  }

  /**
   * Adds 'value' to either the predecessor or successor set, updating the
   * appropriate field as necessary.
   * @return {@code true} if the set was modified; {@code false} if the set
   * was not modified
   */
  private boolean add(boolean predecessorSet, Node<T> value) {
    final Collection<Node<T>> set = predecessorSet ? preds : succs;
    if (set == null) {
      // null -> SingletonList
      return updateField(predecessorSet, Collections.singletonList(value));
    }
    if (set.contains(value)) {
      // already exists in this set
      return false;
    }
    int previousSize = set.size();
    if (previousSize == 1) {
      // SingletonList -> ArrayList
      Collection<Node<T>> newSet = new ArrayList<>(ARRAYLIST_THRESHOLD);
      newSet.addAll(set);
      newSet.add(value);
      return updateField(predecessorSet, newSet);
    } else if (previousSize < ARRAYLIST_THRESHOLD) {
      // ArrayList
      set.add(value);
      return true;
  } else if (previousSize == ARRAYLIST_THRESHOLD) {
      // ArrayList -> HashSet
      Collection<Node<T>> newSet = Sets.newHashSetWithExpectedSize(INITIAL_HASHSET_CAPACITY);
      newSet.addAll(set);
      newSet.add(value);
      return updateField(predecessorSet, newSet);
    } else {
      // HashSet
      set.add(value);
      return true;
    }
  }

  /**
   * Removes 'value' from either 'preds' or 'succs', updating the appropriate
   * field as necessary.
   * @return {@code true} if the set was modified; {@code false} if the set
   * was not modified
   */
  private boolean remove(boolean predecessorSet, Node<T> value) {
    final Collection<Node<T>> set = predecessorSet ? preds : succs;
    if (set == null) {
      // null
      return false;
    }

    int previousSize = set.size();
    if (previousSize == 1) {
      if (set.contains(value)) {
        // -> null
        return updateField(predecessorSet, null);
      } else {
        return false;
      }
    }
    // now remove the value
    if (set.remove(value)) {
      // may need to change representation
      if (previousSize == 2) {
        // -> SingletonList
        List<Node<T>> list =
          Collections.singletonList(set.iterator().next());
        return updateField(predecessorSet, list);

      } else if (previousSize == 1 + ARRAYLIST_THRESHOLD) {
        // -> ArrayList
        Collection<Node<T>> newSet = new ArrayList<>(ARRAYLIST_THRESHOLD);
        newSet.addAll(set);
        return updateField(predecessorSet, newSet);
      }
      return true;
    }
    return false;
  }

  /**
   * Update either the {@link #preds} or {@link #succs} field to point to the
   * new set.
   * @return {@code true}, because the set must have been updated
   */
  private boolean updateField(boolean predecessorSet,
      Collection<Node<T>> newSet) {
    if (predecessorSet) {
      preds = newSet;
    } else {
      succs = newSet;
    }
    return true;
  }


  /**
   * Add 'to' as a successor of 'this' node.  Returns true iff
   * the graph changed.  Private: breaks graph invariant!
   */
  boolean addSuccessor(Node<T> to) {
    return add(false, to);
  }

  /**
   * Add 'from' as a predecessor of 'this' node.  Returns true iff
   * the graph changed.  Private: breaks graph invariant!
   */
  boolean addPredecessor(Node<T> from) {
    return add(true, from);
  }

  /**
   * Remove edge: fromNode.succs = {n | n in fromNode.succs && n != toNode}
   * Private: breaks graph invariant!
   */
  boolean removeSuccessor(Node<T> to) {
    return remove(false, to);
  }

  /**
   * Remove edge: toNode.preds = {n | n in toNode.preds && n != fromNode}
   * Private: breaks graph invariant!
   */
  boolean removePredecessor(Node<T> from) {
    return remove(true, from);
  }

  @Override
  public String toString() {
    return "node:" + label;
  }

  @Override
  public int hashCode() {
    return hashCode; // Fast, deterministic.
  }

  @Override
  public boolean equals(Object that) {
    return this == that; // Nodes are unique for a given label
  }
}
