package netmatch.algorithm;      

import cytoscape.CyEdge;
import cytoscape.CyNetwork;
import cytoscape.CyNode;
import cytoscape.Cytoscape;
import cytoscape.data.CyAttributes;
import cytoscape.task.Task;
import cytoscape.task.TaskMonitor;
import netmatch.Strings;

import javax.swing.*;
import java.util.*;
import java.util.List;

public class netMatch implements Task {
  private JPanel frame;
  private CyNetwork target;
  private CyNetwork query;
  private TaskMonitor taskMonitor;
  private boolean interrupted;
  private boolean completedSuccessfully;
  private ArrayList array;
  private ArrayList source;
  private String qea, qna;
  private ArrayList tea, tna;
  private GraphLoader qeLoader;
  private boolean isUsedEditor;
  private boolean isApproximate;
  private Vector qPaths;
  private ArrayList allPaths;

  /**
   * @param frame  Parent
   * @param target Target Graph
   * @param tea    Target Graph Edge Attirbutes type
   * @param tna    Target Graph Node Attributes type
   * @param query  Query Graph
   * @param qea    Query Graph Edge Attirbutes type
   * @param qna    Query Graph Node Attirbutes type
   */
  public netMatch(JPanel frame, CyNetwork target, ArrayList tea, ArrayList tna, CyNetwork query, String qea, String qna) {
    this.frame = frame;
    this.target = target;
    this.query = query;
    this.tea = tea;
    this.tna = tna;
    this.qea = qea;
    this.qna = qna;
    taskMonitor = null;
    interrupted = false;
    completedSuccessfully = false;
    array = null;
    source = null;
    isUsedEditor = false;
    isApproximate = false;
    qPaths = null;
    allPaths = null;
  }

 /*public void printArrayList(ArrayList l) {
    for(Iterator iterator = l.iterator();iterator.hasNext();) {
      Object o = iterator.next();
      System.out.print(o+" ");
    }
    System.out.println();
  }*/

  public netMatch(JPanel frame, CyNetwork target, ArrayList tea, ArrayList tna, GraphLoader gl,boolean isQueryApproximate,Vector paths) {
    this.frame = frame;
    this.target = target;
    this.tea = tea;
    this.tna = tna;
    this.qeLoader = gl;

    taskMonitor = null;
    interrupted = false;
    completedSuccessfully = false;
    array = null;
    source = null;

    isUsedEditor = true;
    isApproximate = isQueryApproximate;
    qPaths = paths;
    allPaths = null;
  }

  public GraphLoader loadGraphFromNetwork(CyNetwork network, ArrayList edgeAttr, ArrayList nodeAttr) throws Exception {
    GraphLoader loader;
    Hashtable names = new Hashtable();
    int i, k;
    boolean any;

    i = k = 0;
    loader = new GraphLoader(frame);
    //List nodes = network.nodesList();
    //List edges = network.edgesList();
    CyAttributes nodesAttributes = Cytoscape.getNodeAttributes();
    CyAttributes edgesAttributes = Cytoscape.getEdgeAttributes();
    //int size = nodes.size() + edges.size();
    int size = network.getNodeCount()+network.getEdgeCount();
    //for(Iterator it = nodes.iterator();it.hasNext();) {
    for(Iterator it = network.nodesIterator();it.hasNext();) {
      CyNode node = (CyNode)it.next();
      String name1 = node.getIdentifier();
      ArrayList name1Attr = getAttribute(nodesAttributes, node.getIdentifier(), nodeAttr);
      if(!names.containsKey(name1)) {
        names.put(name1, new Integer(i++));
        name1Attr.add(0,name1+Common.STD_EDGE);
        loader.InsertNode(name1Attr, node.getRootGraphIndex(), false);
      }
      taskMonitor.setPercentCompleted((++k * 100) / size);
      if(checkTask())
        return null;
    }
    //for(Iterator it = edges.iterator();it.hasNext();) {
    for(Iterator it = network.edgesIterator();it.hasNext();) {
      CyEdge edge = (CyEdge)it.next();
      ArrayList edge2Attr = getAttribute(edgesAttributes, edge.getIdentifier(), edgeAttr);
      CyNode source = (CyNode)edge.getSource();
      CyNode dest = (CyNode)edge.getTarget();
      String name1 = source.getIdentifier();
      String name2 = dest.getIdentifier();
      ArrayList name2Attr = getAttribute(nodesAttributes, dest.getIdentifier(), nodeAttr);
      any = false;
      if(name1.equals(name2)) {
        name2 += Common.SELF_EDGE;
        if(!names.containsKey(name2)) {
          names.put(name2, new Integer(i++));
          name2Attr.add(0,name2);
          loader.InsertNode(name2Attr, source.getRootGraphIndex(), true);
        }
        any = true;
      }
      if(any) {
        loader.InsertEdge(((Integer)names.get(name1)).intValue(), ((Integer)names.get(name2)).intValue(), edge2Attr, true);
        if(!Common.DIRECTED)
          loader.InsertEdge(((Integer)names.get(name2)).intValue(), ((Integer)names.get(name1)).intValue(), edge2Attr, true);
      }
      else {
        loader.InsertEdge(((Integer)names.get(name1)).intValue(), ((Integer)names.get(name2)).intValue(), edge2Attr, false);
        if(!Common.DIRECTED)
          loader.InsertEdge(((Integer)names.get(name2)).intValue(), ((Integer)names.get(name1)).intValue(), edge2Attr, false);
      }
      taskMonitor.setPercentCompleted((++k * 100) / size);
      if(checkTask())
        return null;
    }
    return loader;
  }

  public GraphLoader loadGraphFromNetwork(CyNetwork network, String edgeAttr, String nodeAttr) throws Exception {
    GraphLoader loader;
    Hashtable names = new Hashtable();
    int i, k;
    boolean any;

    i = k = 0;
    loader = new GraphLoader(frame);
    //List nodes = network.nodesList();
    //List edges = network.edgesList();
    CyAttributes nodesAttributes = Cytoscape.getNodeAttributes();
    CyAttributes edgesAttributes = Cytoscape.getEdgeAttributes();
    //int size = nodes.size() + edges.size();
    int size = network.getNodeCount()+network.getEdgeCount();
    //for(Iterator it = nodes.iterator();it.hasNext();) {
    for(Iterator it = network.nodesIterator();it.hasNext();) {
      CyNode node = (CyNode)it.next();
      String name1 = node.getIdentifier();
      String name1Attr = getAttribute(nodesAttributes, node.getIdentifier(), nodeAttr);
      if(!names.containsKey(name1)) {
        names.put(name1, new Integer(i++));
        loader.InsertNode(name1Attr, node.getRootGraphIndex(), false);
      }
      taskMonitor.setPercentCompleted((++k * 100) / size);
      if(checkTask())
        return null;
    }
    //for(Iterator it = edges.iterator();it.hasNext();) {
    for(Iterator it = network.edgesIterator();it.hasNext();) {
      CyEdge edge = (CyEdge)it.next();
      String type = getAttribute(edgesAttributes, edge.getIdentifier(), edgeAttr);
      CyNode source = (CyNode)edge.getSource();
      CyNode dest = (CyNode)edge.getTarget();
      String name1 = source.getIdentifier();
      String name2 = dest.getIdentifier();
      String name2Attr = getAttribute(nodesAttributes, dest.getIdentifier(), nodeAttr);
      any = false;
      if(name1.equals(name2)) {
        name2 += Common.SELF_EDGE;
        name2Attr += Common.SELF_EDGE;
        if(!names.containsKey(name2)) {
          names.put(name2, new Integer(i++));
          loader.InsertNode(name2Attr, source.getRootGraphIndex(), true);
        }
        any = true;
      }
      if(any) {
        loader.InsertEdge(((Integer)names.get(name1)).intValue(), ((Integer)names.get(name2)).intValue(), type, true);
        if(!Common.DIRECTED)
          loader.InsertEdge(((Integer)names.get(name2)).intValue(), ((Integer)names.get(name1)).intValue(), type, true);
      }
      else {
        loader.InsertEdge(((Integer)names.get(name1)).intValue(), ((Integer)names.get(name2)).intValue(), type, false);
        if(!Common.DIRECTED)
          loader.InsertEdge(((Integer)names.get(name2)).intValue(), ((Integer)names.get(name1)).intValue(), type, false);
      }
      taskMonitor.setPercentCompleted((++k * 100) / size);
      if(checkTask())
        return null;
    }
    return loader;
  }

  private ArrayList getAttribute(CyAttributes attributes, String id, ArrayList attrs) {
    ArrayList list = new ArrayList();
    Object type;
    boolean isList;
    try {
      if(attributes != null) {
        for(int i = 0;i < attrs.size();i++) {
          type = null;
          isList = false;
          String attr = (String)attrs.get(i);
          if(attr.equals(Strings.DEFAULT_ATTRIBUTES))
            type = id;
          else if(attributes.getType(attr) == CyAttributes.TYPE_STRING)
            type = attributes.getStringAttribute(id, attr);
          else if(attributes.getType(attr) == CyAttributes.TYPE_BOOLEAN)
            type = attributes.getBooleanAttribute(id, attr);
          else if(attributes.getType(attr) == CyAttributes.TYPE_FLOATING)
            type = attributes.getDoubleAttribute(id, attr);
          else if(attributes.getType(attr) == CyAttributes.TYPE_INTEGER)
            type = attributes.getIntegerAttribute(id, attr);
          else if(attributes.getType(attr) == CyAttributes.TYPE_SIMPLE_LIST) {
            List temp = attributes.getListAttribute(id,attr);//.getAttributeList(id,attr);
            for(int ii = 0; ii < temp.size();ii++)
              list.add(temp.get(ii));
            isList = true;
          }
          if(!isList) {
            if(type == null || type.equals("null")) {
              type = Common.ANY_LABEL + "_" + id;
              System.out.println("Found Empty Attribute. It becomes NODE:"+type);
            }
            list.add(type);
          }
        }
      }
    }
    catch(NullPointerException e) {
      e.printStackTrace();
    }
    return list;
  }

  private String getAttribute(CyAttributes attributes, String id, String attr) {
    String type = null;
    try {
      if(attributes != null) {
        if(attr.equals(Strings.DEFAULT_ATTRIBUTES))
          type = id;
        else if(attributes.getType(attr) == CyAttributes.TYPE_STRING)
          type = attributes.getStringAttribute(id, attr);
        else if(attributes.getType(attr) == CyAttributes.TYPE_BOOLEAN)
          type = attributes.getBooleanAttribute(id, attr).toString();
        else if(attributes.getType(attr) == CyAttributes.TYPE_FLOATING)
          type = attributes.getDoubleAttribute(id, attr).toString();
        else if(attributes.getType(attr) == CyAttributes.TYPE_INTEGER)
          type = attributes.getIntegerAttribute(id, attr).toString();
        /*else if(attributes.getType(attr) == CyAttributes.TYPE_SIMPLE_LIST) {
          //attributes.getAttributeList()
        }
        else if(attributes.getType(attr) == CyAttributes.TYPE_SIMPLE_MAP) {
          
        }*/
      }
      if(type == null || type.equals("null")) {
        type = Common.ANY_LABEL + "_" + id;
        System.out.println("Found Empty Attribute. It becomes NODE:"+type);
      }
    }
    catch(NullPointerException e) {
      type = Common.ANY_LABEL + "_" + id;
      System.out.println("Found Empty Attribute. It becomes NODE:"+type);
    }
    return type;
  }

  /** @return true if the task has completed successfully */
  public boolean isCompletedSuccessfully() {
    return completedSuccessfully;
  }

  /**
   * Gets the Task Title.
   *
   * @return human readable task title.
   */
  public String getTitle() {
    return "NetMatch";
  }

  public ArrayList getApproximatePaths() {
    return allPaths;
  }

  /** Run Algorithm for Cytoscape */
  public void run() {
    if(taskMonitor == null) {
      throw new IllegalStateException("Task Monitor is not set.");
    }
    try {
      taskMonitor.setPercentCompleted(0);
      taskMonitor.setStatus("Create Network Loader (Step 1 of 5)");
      GraphLoader dbLoader = loadGraphFromNetwork(target, tea, tna);
      if(checkTask())
        return;
      taskMonitor.setPercentCompleted(0);
      taskMonitor.setStatus("Create Network Graph Data (Step 2 of 5)");
      Graph db = new Graph(dbLoader, this);
      if(checkTask())
        return;
      taskMonitor.setPercentCompleted(0);
      taskMonitor.setStatus("Create Query Loader (Step 3 of 5)");
      GraphLoader qLoader;
      if(isUsedEditor)
        qLoader = this.qeLoader;
      else
        qLoader = loadGraphFromNetwork(query, qea, qna);
      if(checkTask())
        return;
      taskMonitor.setPercentCompleted(0);
      taskMonitor.setStatus("Create Query Graph Data (Step 4 of 5)");
      Graph q = new Graph(qLoader, this);
      if(Common.LABELED) {
        q.SetNodeComparator(new exactNodeComparator());
        q.SetEdgeComparator(new exactEdgeComparator());
      }
      else {
        q.SetNodeComparator(new approxNodeComparator());
        q.SetEdgeComparator(new approxEdgeComparator());
      }
      if(checkTask())
        return;
      taskMonitor.setPercentCompleted(-1);
      taskMonitor.setStatus("Start Matching... (Step 5 of 5)");
      VF2MonoState s0 = new VF2MonoState(q, db);// First parameter is query
      if(checkTask())
        return;
      array = new ArrayList();
      source = new ArrayList();

      Date d = new Date();
      long start = d.getTime();

      myMatch m = new myMatch(this, frame);
      myInteger val = m.match(s0, array, source);

      if(checkTask())
        return;
      if(val.intValue() > 0 && isApproximate) { // If there are matches and if the query contains a path
        ArrayList l2 = getArrayDest();
        ArrayList l1 = getArraySource();
        ArrayList l3 = new ArrayList();
        ArrayList l4 = new ArrayList();
        allPaths = new ArrayList();
        ArrayList paths = null;
        for(int j = 0;j < l2.size();j++) {
          if(checkTask())
            return;
          int tmp1[] = (int[])l1.get(j);
          int tmp2[] = (int[])l2.get(j);
          int count = 0;
          for(int i = 0;i < qPaths.size();i++) {
            paths = new ArrayList();
            String p = (String)qPaths.elementAt(i);
            String vals[] = p.split(",");
            int tsource = tmp2[getIndexOf((int[])l1.get(j),Integer.parseInt(vals[0]))];
            int tdest = tmp2[getIndexOf((int[])l1.get(j),Integer.parseInt(vals[1]))];
            String tmp = vals[2].substring(1);
            String condition = tmp.substring(0,1);
            if(tmp.indexOf('=') != -1)
              condition = tmp.substring(0,tmp.indexOf('=')+1);

            int tmp3[] = null;
            if(tmp2.length > 2) {
              tmp3 = new int [tmp2.length-2];
              int k = 0;
              for(int h=0;h<tmp2.length;h++)
                if(tmp2[h] != tsource && tmp2[h] != tdest) {
                  tmp3[k] = tmp2[h];
                  k++;
                }
            }
            
            int result[] = bfs(tmp3,target,tsource,tdest,condition,Integer.parseInt(tmp.substring(condition.length())),Common.DIRECTED);
            if(result != null) {
              paths.add(result);
              count++;
            }
            else
              break;
          }
          if(count == qPaths.size()) {
            l3.add(tmp1);
            l4.add(tmp2);
            allPaths.add(paths);
          }
        }
        setArrayDest(l4);
        setArraySource(l3);
        val.setValue(l4.size());
      }
      d = new Date();
      long end = d.getTime();
      completedSuccessfully = true;
      System.out.println("Netmatch elapsed time:"+(end-start));
    }
    catch(Exception e) {
      completedSuccessfully = false;
      taskMonitor.setException(e, "Please check data!");
    }
    catch(Error e) {
      completedSuccessfully = false;
      taskMonitor.setException(e, "NetMatch cancelled!");
    }
  }

  private int getIndexOf(int[] array,int val) {
    for(int i = 0; i < array.length;i++)
      if(array[i] == val)
        return i;
    return -1;
  }

  private boolean isPruningPossible(String cond) {
    return cond.equals(Common.EQ) || cond.equals(Common.LT) || cond.equals(Common.LE);
  }

  private int[] bfs(int[] nodes,CyNetwork tnetwork,int source, int dest,String condition, int number,boolean directed) {
    if (source == dest)
      return null;
    if(number == 0 && condition.equals(Common.EQ))
      return null;
    //CyNode cySource = (CyNode)tnetwork.getNode(source);
    //CyNode cyDest = (CyNode) tnetwork.getNode(dest);
    /*int v[] = tnetwork.getNodeIndicesArray();
    for(int i = 0 ; i < v.length ; i++)
      if(v[i] == dest) {
        CyNode node = (CyNode) tnetwork.getNode(v[i]);
        System.out.println("****************************TARGET "+ v[i] +" "+node.getIdentifier());       
      }*/

    BfsPath bfsPath = new BfsPath();
    List queue = new ArrayList();
    queue.add(bfsPath);
    boolean prune = isPruningPossible(condition);
    while(!queue.isEmpty()) {
      if(checkTask())
        return null;
      bfsPath = (BfsPath)queue.get(0);
      queue.remove(0);
      int nodeId;
      if(bfsPath.isEmpty())
        nodeId = source;
      else {
        //int lastEdgeId = bfsPath.getLastEdge();
        Pair p = bfsPath.getLastEdge();
        CyEdge edge = (CyEdge)tnetwork.getEdge(p.getEdge());
        if(p.isOutGoingEdge())
          nodeId = tnetwork.getIndex(edge.getTarget());
        else
          nodeId = tnetwork.getIndex(edge.getSource());
      }
      if(prune && bfsPath.size() > number)
        return null;
      if(dest == nodeId && bfsPath.checkCondition(condition,number)) {
        System.out.println("ApproximatePath found: Source:"+(tnetwork.getNode(source)).getIdentifier()+" Dest:"+(tnetwork.getNode(dest)).getIdentifier()+" Directed: "+directed);
        return bfsPath.getApproximatePath(tnetwork);
      }
      // Out Going edges for directed and undirected graphs
      int adjEdgesOut[] = tnetwork.getAdjacentEdgeIndicesArray(nodeId, false, false, true);  //int node_index,undirected_edges,incoming_directed_edges,outgoing_directed_edges
      for(int i = 0; i < adjEdgesOut.length; i++) {
        if(!bfsPath.contains(adjEdgesOut[i]) && isEligibleEdge(tnetwork,adjEdgesOut[i],nodes)) {
          BfsPath newBfsPath = new BfsPath(bfsPath);
          newBfsPath.add(adjEdgesOut[i],true);
          queue.add(newBfsPath);
        }
      }
      // InComing edges only for undirected graphs
      if(!directed) {
        int adjEdgesIn[] = tnetwork.getAdjacentEdgeIndicesArray(nodeId, false, true, false);
        for(int i = 0; i < adjEdgesIn.length; i++) {
          if(!bfsPath.contains(adjEdgesIn[i]) && isEligibleEdge(tnetwork,adjEdgesIn[i],nodes)) {
            BfsPath newBfsPath = new BfsPath(bfsPath);
            newBfsPath.add(adjEdgesIn[i],false);
            queue.add(newBfsPath);
          }
        }
      }
    }
    return null;
  }

  public boolean isEligibleEdge(CyNetwork net,int edge, int[] nonEligibleNodes) {
    if(nonEligibleNodes != null) {
      int s = net.getIndex((net.getEdge(edge)).getSource());
      int d = net.getIndex((net.getEdge(edge)).getTarget());
      for(int i = 0; i < nonEligibleNodes.length;i++) {
        if(nonEligibleNodes[i] == s || nonEligibleNodes[i] == d)
          return false;
      }
      return true;
    }
    return true;
  }

  public ArrayList getArrayDest() {
    return array;
  }

  public ArrayList getArraySource() {
    return source;
  }

  public void setArrayDest(ArrayList a) {
    array = a;
  }

  public void setArraySource(ArrayList a) {
    source = a;
  }

  /** Non-blocking call to interrupt the task. */
  public void halt() {
    this.interrupted = true;
  }

  /**
   * Sets the Task Monitor.
   *
   * @param taskMonitor TaskMonitor Object.
   */
  public void setTaskMonitor(TaskMonitor taskMonitor) throws IllegalThreadStateException {
    if(this.taskMonitor != null)
      throw new IllegalStateException("Task Monitor is already set.");
    this.taskMonitor = taskMonitor;
  }

  public void setPercentageComplete(int val) {
    taskMonitor.setPercentCompleted(val);
  }

  public boolean checkTask() {
    return interrupted;
  }
}