package netmatch.algorithm;

public class Graph {
  private int n;// Number of nodes
  private Object[] attr;// Node attribute
  private int[] in_count;// Number of in edges for each node
  private int[] out_count;// Number of out edges for each node
  private int[][] in;// Nodes connected by in edges to each node
  private int[][] out;// Nodes connected by out edges to each node
  private Object[][] in_attr;// Edges attrributes for in edges
  private Object[][] out_attr;// Edges attrributes for out edges
  AttrComparator node_comparator;
  AttrComparator edge_comparator;
  netMatch instance;
  private int[] networkID;// CyNetwork ID

  public Graph(GraphLoader loader, netMatch instance) throws Exception {
    this.instance = instance;
    node_comparator = null;
    edge_comparator = null;
    n = loader.NodeCount();
    attr = new Object[n];
    int i, j, v = 0, size = 4 * n;
    for(i = 0;i < n;i++) {
      attr[i] = loader.GetNodeAttr(i);
      instance.setPercentageComplete((++v * 100) / size);
      if(instance.checkTask())
        return;
    }
    in_count = new int[n];
    out_count = new int[n];
    in = new int[n][];
    out = new int[n][];
    in_attr = new Object[n][];
    out_attr = new Object[n][];
    for(i = 0;i < n;i++) {
      int k = out_count[i] = loader.OutEdgeCount(i);
      out[i] = new int[k];
      out_attr[i] = new Object[k];
      for(j = 0;j < k;j++) {
        out_attr[i][j] = new Object();
        int n2 = out[i][j] = loader.GetOutEdge(i, j, out_attr);
        in_count[n2]++;
      }
      instance.setPercentageComplete((++v * 100) / size);
      if(instance.checkTask())
        return;
    }
    for(i = 0;i < n;i++) {
      int k = in_count[i];
      in[i] = new int[k];
      in_attr[i] = new Object[k];
      int l = 0;
      for(j = 0;j < n;j++) {
        if(HasEdge(j, i)) {
          in[i][l] = j;
          in_attr[i][l] = GetEdgeAttr(j, i);
          l++;
        }
      }
      if(l != k)
        throw new Exception();
      instance.setPercentageComplete((++v * 100) / size);
      if(instance.checkTask())
        return;
    }
    networkID = new int[n];
    for(i = 0;i < n;i++) {
      networkID[i] = loader.getCyNetworkID(i);
      instance.setPercentageComplete((++v * 100) / size);
      if(instance.checkTask())
        return;
    }
  }

  public void print() {
    System.out.println("GRAPH");
    for(int ij = 0;ij < attr.length;ij++)
      System.out.println("Node: " + ij + " " + attr[ij] + " incount:" + in_count[ij] + " outcount:" + out_count[ij]);
    System.out.println("IN");
    for(int ij = 0;ij < in_count.length;ij++) {
      int k = in_count[ij];
      for(int hj = 0;hj < k;hj++) {
        System.out.print(ij + "[" + in[ij][hj] + "," + in_attr[ij][hj] + "] ");
      }
      System.out.println();
    }
    System.out.println("OUT");
    for(int ij = 0;ij < out_count.length;ij++) {
      int k = out_count[ij];
      for(int hj = 0;hj < k;hj++) {
        System.out.print(ij + "[" + out[ij][hj] + "," + out_attr[ij][hj] + "] ");
      }
      System.out.println();
    }
  }

  /*-------------------------------------------------------------------
   * Set the object to invoke to compare node attributes
   * Note:
   *   The object is owned by the graph; i.e. it is
   *   deleted when the graph is deallocated.
   ------------------------------------------------------------------*/
  public void SetNodeComparator(AttrComparator comp) {
    node_comparator = comp;
  }

  /*-------------------------------------------------------------------
   * Set the function to invoke to test for node compatibility
   * Note:
   *   This function is provided for compatibility with older
   *   versions of the library. It creates an object of the class
   *   FunctionAttrComparator.
   ------------------------------------------------------------------*/
  /*public void SetNodeCompat(node_compat_fn fn) {
    SetNodeComparator(new FunctionAttrComparator(fn));
  }*/

  /*-------------------------------------------------------------------
   * Set the object to invoke to compare edge attributes
   * Note:
   *   The object is owned by the graph; i.e. it is
   *   deleted when the graph is deallocated.
   ------------------------------------------------------------------*/

  public void SetEdgeComparator(AttrComparator comp) {
    edge_comparator = comp;
  }

  /*-------------------------------------------------------------------
   * Set the function to invoke to test for edge compatibility
   * Note:
   *   This function is provided for compatibility with older
   *   versions of the library. It creates an object of the class
   *   FunctionAttrComparator.
   ------------------------------------------------------------------*/
  /*public void SetEdgeCompat(edge_compat_fn fn) {
    SetEdgeComparator(new FunctionAttrComparator(fn));
  }*/

  /*-------------------------------------------------------------------
   * Change the attribute of a node
   -------------------------------------------------------------------*/
  /*public void SetNodeAttr(int i,Object new_attr,boolean destroyOld) throws Exception {
    if(i < n)
      attr[i] = new_attr;
    else
      throw new Exception();
  }*/

  /*
   * Checks the existence of an edge, and returns its attribute
   * using the parameter pattr. Note: uses binary search.
   * Implem. note: Uses the out/out_attr vectors; this fact is xploited
   * in the constructor to generate the in_attr vector
   */

  public boolean HasEdge(int n1, int n2, Object pattr) throws Exception {
    int a, b, c;
    int[] id = out[n1];

    if(n1 < n && n2 < n) {
      a = 0;
      b = out_count[n1];
      while(a < b) {
        c = (a + b) >> 1;
        if(id[c] < n2)
          a = c + 1;
        else if(id[c] > n2)
          b = c;
        else {
          pattr = out_attr[n1][c];
          return true;
        }
      }
      return false;
    }
    throw new Exception();
  }

  /*
   * Change the attribute of an edge. It is an error if the edge
   * does not exist. Note: uses binary search.
   */
  /*public void SetEdgeAttr(int n1,int n2,Object new_attr,boolean destroyOld) throws Exception {
    int a,b,c;
    int id[];

    if(n1 < n && n2 < n) {

      // Replace the attribute in the out_attr array
      id = out[n1];
      a = 0;
      b = out_count[n1];
      while(a < b) {
        c = (a + b) >> 1;
        if(id[c] < n2)
          a = c + 1;
        else if(id[c] > n2)
          b = c;
        else {
          out_attr[n1][c] = new_attr;
          break;
        }
      }
      if(a >= b)
        System.out.println("SetEdgeAttr: non existent edge");

      // Replace the attribute in the in_attr array
      id = in[n2];
      a = 0;
      b = in_count[n2];
      while(a < b) {
        c = (a + b) >> 1; // c = (unsigned)(a + b) >> 1;
        if(id[c] < n1)
          a = c + 1;
        else if(id[c] > n1)
          b = c;
        else { // The old attr here is intentionally not destroyed with DestroyEdge, since destruction
              // has been performed previously through out_attr
          in_attr[n2][c] = new_attr;
          break;
        }
      }
      if(a >= b)
        System.out.println("SetEdgeAttr: inconsistent graph state");
    }
    else
      throw new Exception();
  }*/

  /*-----------------------------------------------
   * Number of nodes in the graph
   ----------------------------------------------*/

  public int NodeCount() {
    return n;
  }

  /*----------------------------------------------
   * Attribute of a node
   ---------------------------------------------*/
  public Object GetNodeAttr(int i) throws Exception {
    if(i < n)
      return attr[i];
    throw new Exception();
  }

  /*----------------------------------------------
   * CyNetwork ID
   ---------------------------------------------*/
  public int getCyNetworkID(int i) throws Exception {
    if(i < n)
      return networkID[i];
    throw new Exception();
  }

  /*----------------------------------------------
   * Check the presence of an edge
   ---------------------------------------------*/
  public boolean HasEdge(int n1, int n2) throws Exception {
    return HasEdge(n1, n2, null);
  }

  public Object GetEdgeAttr(int n1, int n2) throws Exception {
    int a, b, c;
    int[] id = out[n1];
    if(n1 < n && n2 < n) {
      a = 0;
      b = out_count[n1];
      while(a < b) {
        c = (a + b) >> 1;
        if(id[c] < n2)
          a = c + 1;
        else if(id[c] > n2)
          b = c;
        else
          return out_attr[n1][c];
      }
      return null;
    }
    throw new Exception();
  }

  /*------------------------------------------------
   * Number of edges going into a node
   ------------------------------------------------*/
  public int InEdgeCount(int node) throws Exception {
    if(node < n)
      return in_count[node];
    throw new Exception();
  }

  /*------------------------------------------------
   * Number of edges departing from a node
   ------------------------------------------------*/
  public int OutEdgeCount(int node) throws Exception {
    if(node < n)
      return out_count[node];
    throw new Exception();
  }

  /*-------------------------------------------------
   * Number of edges touching a node
   ------------------------------------------------*/
  /*public int EdgeCount(int node) throws Exception {
    if(node < n)
      return in_count[node] + out_count[node];
    else
      throw new Exception();
  }*/

  /*------------------------------------------------
   * Gets the other end of an edge entering a node
   -----------------------------------------------*/

  public int GetInEdge(int node, int i) throws Exception {
    if(node < n && i < in_count[node])
      return in[node][i];
    throw new Exception();
  }

  /*------------------------------------------------
   * Gets the other end of an edge entering a node
   * Also gets the attribute of the edge
   -----------------------------------------------*/
  public int GetInEdge(int node, int i, Object pattr) throws Exception {
    if(node < n && i < in_count[node]) {
      pattr = in_attr[node][i];
      return in[node][i];
    }
    throw new Exception();
  }

  public Object GetInEdge2(int node, int i) throws Exception {
    if(node < n && i < in_count[node])
      return in_attr[node][i];
    throw new Exception();
  }

  /*------------------------------------------------
   * Gets the other end of an edge leaving a node
   -----------------------------------------------*/
  public int GetOutEdge(int node, int i) throws Exception {
    if(node < n && i < out_count[node])
      return out[node][i];
    throw new Exception();
  }

  /*------------------------------------------------
   * Gets the other end of an edge leaving a node
   * Also gets the attribute of the edge
   -----------------------------------------------*/
  public int GetOutEdge(int node, int i, Object pattr) throws Exception {
    if(node < n && i < out_count[node]) {
      pattr = out_attr[node][i];
      return out[node][i];
    }
    throw new Exception();
  }

  public Object GetOutEdge2(int node, int i) throws Exception {
    if(node < n && i < out_count[node])
      return out_attr[node][i];
    throw new Exception();
  }

  /*-----------------------------------------------------------
   * Checks if two node attributes are compatible
   ----------------------------------------------------------*/
  public boolean CompatibleNode(Object attr1, Object attr2) throws Exception {
    if(node_comparator == null)
      return true;
    return node_comparator.compatible(attr1, attr2);
  }

  /*-----------------------------------------------------------
   * Checks if two edge attributes are compatible
   ----------------------------------------------------------*/
  public boolean CompatibleEdge(Object attr1, Object attr2) throws Exception {
    if(edge_comparator == null)
      return true;
    return edge_comparator.compatible(attr1, attr2);
  }
}