/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.compilers.opt.util;

import java.util.Enumeration;
import java.util.HashSet;


/**
 * SpaceEffGraph package implements a generic directed graph that can
 * be a multigraph. It uses Lists to model nodes and edges information.
 *
 * SpaceEffGraph is a generic graph.  Extend to implement specific
 * graph types.
 */
public class SpaceEffGraph implements Graph, TopSortInterface {
  /**
   * First node
   */
  protected SpaceEffGraphNode _firstNode;
  /**
   * Last node
   */
  protected SpaceEffGraphNode _lastNode;
  /**
   * Nodes with no predecessors
   */
  private SpaceEffGraphNodeListHeader _rootNodes;
  /**
   * Topological sort order
   */
  private SpaceEffGraphNodeListHeader _topSortNodes; // top sort order.

  /**
   * Number of nodes
   */
  protected int numberOfNodes;

  /**
   * Get number of nodes
   * @return number of nodes
   */
  public final int numberOfNodes() { return numberOfNodes; }

  /**
   * Set number of nodes
   * @param n new number of nodes
   */
  public final void setNumberOfNodes(int n) { numberOfNodes = n; }

  /**
   * Get the next node number
   * @return the node number
   */
  public final int allocateNodeNumber() { return numberOfNodes++; }

  /**
   * Renumber the nodes densely from 0...numberOfNodes-1.
   */
  public void compactNodeNumbering() {
    int number = 0;
    for (SpaceEffGraphNode n = _firstNode; n != null; n = n.getNext()) {
      n.setNumber(number++);
    }
    numberOfNodes = number;
  }

  /**
   * Enumerate the nodes in no particular order
   */
  public GraphNodeEnumeration enumerateNodes() {
    return new NodeEnumeration(_firstNode);
  }

  //////////////////
  // The following are to implement TopSortInterface.
  //////////////////

  public SortedGraphNode startNode(boolean forward) {
    if (forward) {
      return (SortedGraphNode) _firstNode;
    } else {
      return (SortedGraphNode) _lastNode;
    }
  }

  public boolean isTopSorted(boolean forward) {
    if (forward) {
      return forwardTopSorted;
    } else {
      return backwardTopSorted;
    }
  }

  public void setTopSorted(boolean forward) {
    if (forward) {
      forwardTopSorted = true;
    } else {
      backwardTopSorted = true;
    }
  }

  public void resetTopSorted() {
    forwardTopSorted = false;
    backwardTopSorted = false;
  }

  public boolean forwardTopSorted = false, backwardTopSorted = false;

  //////////////////
  // End of TopSortInterface implementation
  //////////////////

  /**
   * Add a node to the graph.
   * @param inode node to add
   */
  public final void addGraphNode(GraphNode inode) {
    SpaceEffGraphNode node = (SpaceEffGraphNode) inode;
    //_nodes.add(node);
    if (_firstNode == null) {
      _firstNode = node;
      _lastNode = node;
    } else {
      _lastNode.append(node);  // this is cheaper than add() call.
      _lastNode = node;
    }
    numberOfNodes++;
  }

  /**
   * Remove a node from the graph.
   * @param node node to remove
   */
  public final void removeGraphNode(SpaceEffGraphNode node) {
    if (node == _firstNode) {
      if (node == _lastNode) {
        _firstNode = _lastNode = null;
      } else {
        _firstNode = node.getNext();
      }
    } else if (node == _lastNode) {
      _lastNode = node.getPrev();
    }
    node.remove();
    numberOfNodes--;
  }

  /**
   * Add an edge to the graph.
   * @param from start node
   * @param to end node
   * @see #addGraphEdge(SpaceEffGraphEdge)
   */
  public void addGraphEdge(GraphNode from, GraphNode to) {
    ((SpaceEffGraphNode) from).insertOut((SpaceEffGraphNode) to);
  }

  /**
   * Add an edge to the graph.
   * @param e edge to insert
   * @see #addGraphEdge(GraphNode,GraphNode)
   */
  public void addGraphEdge(SpaceEffGraphEdge e) {
    e.fromNode().appendOutEdge(e);
    e.toNode().appendInEdge(e);
  }

  /**
   * Reset the list of nodes of the graph.
   * WARNING!!!  Use with caution if you know what you are doing.
   * @param firstNode new value of the node list
   */
  public final void setFirstNode(SpaceEffGraphNode firstNode) {
    _firstNode = firstNode;
  }

  /**
   * Reset the list of nodes of the graph.
   * WARNING!!!  Use with caution if you know what you are doing.
   * @param lastNode new value of the node list
   */
  public final void setLastNode(SpaceEffGraphNode lastNode) {
    _lastNode = lastNode;
  }
  /**
   * Get the list of nodes.
   * @return list of nodes
   */
  public final SpaceEffGraphNode firstNode() {
    return _firstNode;
  }

  /**
   * Get the end of the list of nodes.
   * @return end of the list of nodes
   */
  public final SpaceEffGraphNode lastNode() {
    return _lastNode;
  }

  /**
   * Add a root node to the graph.
   * @param root a node to add
   */
  public final void addRootNode(SpaceEffGraphNode root) {
    //_rootNodes.add(root);
    if (_rootNodes == null) {
      _rootNodes = new SpaceEffGraphNodeListHeader();
    }
    _rootNodes.append(root);
  }

  /**
   * Get the list of root nodes.
   * @return list of root nodes
   */
  public final SpaceEffGraphNodeList rootNodes() {
    return _rootNodes.first();
  }

  /**
   * Get the topological order of nodes.
   * @return topological order of nodes
   */
  public final SpaceEffGraphNodeList topSortOrder() {
    return _topSortNodes.first();
  }

  /**
   * Clear the DFS flags.
   */
  public final void clearDFS() {
    for (SpaceEffGraphNode n = firstNode(); n != null; n = n.getNext()) {
      n.clearDfsVisited();
    }
  }

  /**
   * Build a topological sort of this graph
   */
  public void buildTopSort() {
    if (!forwardTopSorted) {
      //SortedGraphNode node =
      TopSort.buildTopological(this, true);
      // currently, no one cares about the return value, so we don't return it
    }
  }

  /**
   * Build a reverse topological sort of this graph
   * @return a node if we build a new order, null if we reused the old
   */
  public SortedGraphNode buildRevTopSort() {
    if (!backwardTopSorted) {
      return TopSort.buildTopological(this, false);
    } else {
      return null;
    }
  }

  ///////////////////////
  // Starting with the root nodes, topologically sort them using
  // the out edge information. Builds the _topSortNodes list.
  // TODO: figure out how this works and add comments (IP)
  ///////////////////////

  protected void initTopSort() {
    _topSortNodes = new SpaceEffGraphNodeListHeader();
  }

  protected void addTopSortNode(SpaceEffGraphNode node) {
    _topSortNodes.append(node);
  }

  public void topSort() {
    initTopSort();
    for (SpaceEffGraphNode n = firstNode(); n != null; n = n.getNext()) {
      if (n.firstInEdge() == null) { // no predecessors
        n.setDfsVisited();
        n.setOnStack();
        dfs(n);
        addTopSortNode(n);
      }
    }
  }

  private void dfs(SpaceEffGraphNode node) {
    for (SpaceEffGraphEdge edge = node.firstOutEdge(); edge != null; edge = edge.getNextOut()) {
      SpaceEffGraphNode succ = edge.toNode();
      if (!succ.dfsVisited()) {
        succ.setDfsVisited();
        succ.setOnStack();
        dfs(succ);
      } else if (succ.onStack() || succ == node) {
        edge.setBackEdge();
      }
    }
    node.clearOnStack();
    for (SpaceEffGraphEdge edge = node.firstOutEdge(); edge != null; edge = edge.getNextOut()) {
      SpaceEffGraphNode succ = edge.toNode();
      if (!succ.topVisited() && !edge.backEdge()) {
        addTopSortNode(succ);
        succ.setTopVisited();
      }
    }
  }

  /**
   * Return a string representation of this graph.
   * @return a string representation of the graph
   */
  public String toString() {
    StringBuilder res = new StringBuilder();
    for (SpaceEffGraphNode n = firstNode(); n != null; n = n.getNext()) {
      HashSet<SpaceEffGraphEdge> visitedNodes = new HashSet<SpaceEffGraphEdge>();
      int duplicatedNodes = 0;
      res.append("\nNode: ").append(n).append("\n");
      res.append("In nodes:\n");
      for (SpaceEffGraphEdge inEdge = n.firstInEdge(); inEdge != null; inEdge = inEdge.getNextIn()) {
        if (visitedNodes.contains(inEdge)) {
          duplicatedNodes ++;
          res.append("(Duplicated edge " + inEdge.toNodeString() + ")");
          if (duplicatedNodes > 5) {
            break;
          }
        } else {
          visitedNodes.add(inEdge);
          res.append(inEdge.getTypeString());
          res.append(" ");
          res.append(inEdge.fromNode());
          res.append("\n");
        }
      }
      res.append("\n");
      visitedNodes.clear();
      duplicatedNodes=0;
      res.append("Out nodes:\n");
      for (SpaceEffGraphEdge out = n.firstOutEdge(); out != null; out = out.getNextOut()) {
        if (visitedNodes.contains(out)) {
          duplicatedNodes ++;
          res.append("(Duplicated edge " + out.toNodeString() + ")");
          if (duplicatedNodes > 5) {
            break;
          }
        } else {
          res.append(out.getTypeString());
          res.append(" ");
          res.append(out.toNode());
          res.append("\n");
        }
      }
      if (res.length() > 50000) {
        res.append("....(giving up too long)\n");
        break;
      }
    }
    return res.toString();
  }

  ////////////////////
  // Return a breadth-first enumeration of the nodes in this CFG.
  // Note that this is different than topological ordering.
  // TODO: figure out how this works and add comments (IP)
  ////////////////////
  int markNumber;

  final int getNewMark() {
    return ++markNumber;
  }

  /**
   * Print, to System.out, the basic blocks in depth first order.
   */
  public void printDepthFirst() {
    markNumber = getNewMark();
    print(new DepthFirstEnumerator(_firstNode, markNumber));
  }

  /**
   * Print, to System.out, the basic blocks in the order given in
   * the supplied enumeration.
   * @param e enumeration order to print blocks
   */
  private void print(Enumeration<GraphNode> e) {
    while (e.hasMoreElements()) {
      SpaceEffGraphNode bb = (SpaceEffGraphNode) e.nextElement();
      bb.printExtended();
    }
  }

  private static final class NodeEnumeration implements GraphNodeEnumeration {
    private SpaceEffGraphNode _node;

    public NodeEnumeration(SpaceEffGraphNode n) { _node = n; }

    public boolean hasMoreElements() { return _node != null; }

    public GraphNode nextElement() { return next(); }

    public GraphNode next() {
      SpaceEffGraphNode n = _node;
      _node = n.getNext();
      return n;
    }
  }
}

