/*
Copyright 2011-2016 Google Inc. 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.security.zynamics.zylib.types.graphs;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;

/**
 * Simple directed graph class that contains nodes and directed edges.
 * 
 * @param <NodeType> Type of the nodes in the graph.
 * @param <EdgeType> Type of the edges in the graph.
 */
public class DirectedGraph<NodeType extends IGraphNode<NodeType>, EdgeType extends IGraphEdge<NodeType>>
    implements Iterable<NodeType>, IDirectedGraph<NodeType, EdgeType> {
  /**
   * Nodes of the graph.
   */
  private final List<NodeType> m_nodes;

  /**
   * Edges of the graph.
   */
  private final List<EdgeType> m_edges;

  /**
   * Map of nodes to incoming edges.
   */
  private final HashMultimap<NodeType, EdgeType> m_incomingEdges = HashMultimap.create();

  /**
   * Map of nodes to outgoing edges.
   */
  private final HashMultimap<NodeType, EdgeType> m_outgoingEdges = HashMultimap.create();

  /**
   * Creates a new directed edge object.
   * 
   * @param nodes Nodes of the graph.
   * @param edges Edges of the graph.
   */
  public DirectedGraph(final List<NodeType> nodes, final List<EdgeType> edges) {
    Preconditions.checkNotNull(nodes, "Error: Nodes argument can not be null");
    Preconditions.checkNotNull(edges, "Error: Edges argument can not be null");

    for (final NodeType node : nodes) {
      Preconditions.checkNotNull(node, "Error: Node list contains null-nodes");
    }

    for (final EdgeType edge : edges) {
      m_outgoingEdges.put(edge.getSource(), edge);
      m_incomingEdges.put(edge.getTarget(), edge);
    }

    m_nodes = nodes;
    m_edges = edges;
  }

  @Override
  public int edgeCount() {
    return m_edges.size();
  }

  @Override
  public List<EdgeType> getEdges() {
    return Collections.unmodifiableList(m_edges);
  }

  public Set<EdgeType> getIncomingEdges(final NodeType node) {
    return m_incomingEdges.get(node);
  }

  @Override
  public List<NodeType> getNodes() {
    return Collections.unmodifiableList(m_nodes);
  }

  public Set<EdgeType> getOutgoingEdges(final NodeType node) {
    return m_outgoingEdges.get(node);
  }

  @Override
  public Iterator<NodeType> iterator() {
    return m_nodes.iterator();
  }

  @Override
  public int nodeCount() {
    return m_nodes.size();
  }
}
