package netmatch.qtool;

import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PLayer;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PText;

import java.awt.*;
import java.awt.geom.Point2D;
import java.io.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;

/////////////////////////////////////////////////////////////////////////////
//  Network elements:
//     node, edge, loop, label
/////////////////////////////////////////////////////////////////////////////

interface QElement {
  void setAttr(String attr);
  String getAttr();
  void update();
  void delete(PLayer nodeLayer, PLayer edgeLayer);

  String getInfo();

  String getId();
}


class QText extends PText {
  private QNode owner;
  String attr;

  public QText(QNode node, String sText) {
    super(sText);
    this.owner = node;
  }

  public void update() {
    Point2D center = owner.getFullBoundsReference().getCenter2D();
    setBounds(owner.getFullBoundsReference().getX(), owner.getFullBoundsReference().getY(), owner.getFullBoundsReference().getWidth() * 0.5, owner.getFullBoundsReference().getHeight() * 0.5);
    centerBoundsOnPoint(center.getX(), center.getY());
    moveInFrontOf(owner);
  }

  QNode getOwner() {
    return owner;
  }

  public void setAttr(String attr) {
    this.attr = attr;
  }

  public String getAttr() {
    return attr;
  }
}

/////////////////////////////////////////////////////////////////////////////
//  Network manager:
//     clean, load, save
/////////////////////////////////////////////////////////////////////////////

public class Network {
  public static int nodeCount = 0;// id counters
  public static int edgeCount = 0;
  public static int pathCount = 0;
  public static int loopCount = 0;
  public static int count = 0;
  public static int width;
  public static int height;
  public static Random r = new Random();
  public static int nCount = 0;// counters
  public static int eCount = 0;
  public static int pCount = 0;
  public static int lCount = 0;

  // clean canvas
  public static void clean(PLayer nodeLayer, PLayer edgeLayer) {
    nodeLayer.removeAllChildren();
    edgeLayer.removeAllChildren();
    nodeCount = edgeCount = pathCount = loopCount = 0;
    nCount = eCount = pCount = lCount = 0;
  }

  public static void updateAll(PLayer nodeLayer) {
    Iterator it = nodeLayer.getAllNodes().iterator();
    while(it.hasNext()) {
      PNode node = (PNode) it.next();
      if(node instanceof QNode)
        ((QNode) node).update();
    }
  }

  public static void saveStructure(FileWriter writer, PCanvas canvas) {
    PLayer nodeLayer = canvas.getCamera().getLayer(1);
    PLayer edgeLayer = canvas.getCamera().getLayer(0);
    BufferedWriter bw = new BufferedWriter(writer);
    Iterator it = nodeLayer.getAllNodes().iterator();
    //System.out.println("I'm here!\n");
    try {//--
      while(it.hasNext()) {
        PNode node = (PNode) it.next();
        if(!(node instanceof QNode))
          continue;
        QNode qNode = (QNode) node;
        if(qNode.isSingle()) {
          bw.write(qNode.getId() + "\n");
          //System.out.println(qNode.getId());
        }
      }
      it = edgeLayer.getAllNodes().iterator();
      while(it.hasNext()) {
        PNode node = (PNode) it.next();
        if(node instanceof QLoop) {
          QLoop loop = (QLoop) node;
          bw.write(loop.getOwner().getId() + " " + loop.getId() + " " + loop.getOwner().getId() + "\n");
          //System.out.println(loop.getOwner().getId() + " " + loop.getId() + " " + loop.getOwner().getId());
        }
        else if(node instanceof QEdge) {
          QEdge edge = (QEdge) node;
          bw.write(edge.getSource().getId() + " " + edge.getId() + " " + edge.getTarget().getId() + "\n");
          //System.out.println(edge.getSource().getId() + " " + edge.getId() + " " + edge.getTarget().getId());
        }
        else if(node instanceof QPath) {
          QPath path = (QPath) node;
          bw.write(path.getSource().getId() + " " + path.getAttr() + " " + path.getTarget().getId() + "\n");
          //System.out.println(path.getSource().getId() + " " + path.getAttr() + " " + path.getTarget().getId());
        }
      }
      bw.close();
    }
    catch(IOException e) {
      System.err.println("Write error!");
    }
  }

  public static void saveNodeAttr(FileWriter writer, PLayer nodeLayer) {
    BufferedWriter bw = new BufferedWriter(writer);
    Iterator it = nodeLayer.getAllNodes().iterator();
    try {//--
      bw.write("Node Attributes\n");
      while(it.hasNext()) {
        PNode node = (PNode) it.next();
        if(!(node instanceof QNode))
          continue;
        QNode qNode = (QNode) node;
        bw.write(qNode.getId() + "=" + qNode.getAttr() + "\n");
      }
      bw.close();
    }
    catch(IOException e) {
      System.err.println("Write error!");
    }
  }

  public static void saveEdgeAttr(FileWriter writer, PLayer edgeLayer) {
    BufferedWriter bw = new BufferedWriter(writer);
    Iterator it = edgeLayer.getAllNodes().iterator();
    try {
      bw.write("Edge Attributes\n");
      while(it.hasNext()) {
        PNode node = (PNode) it.next();
        if(!(node instanceof QElement))
          continue;
        if(node instanceof QPath)
          continue;
        if(node instanceof QEdge) {
          QEdge edge = (QEdge) node;
          bw.write(edge.getSource().getId() + " (" + edge.getId() + ") " + edge.getTarget().getId() + " = " + edge.getAttr() + "\n");
        }
        else if(node instanceof QLoop) {
          QLoop loop = (QLoop) node;
          bw.write(loop.getOwner().getId() + " (" + loop.getId() + ") " + loop.getOwner().getId() + " = " + loop.getAttr() + "\n");
        }
        //if(!arr.contains(((QElement)node).getId())){
        //bw.write(((QElement)node).getId() + "=" + ((QElement)node).getAttr()+"\n");
        //arr.add(((QElement)node).getId());
        //}
      }
      bw.close();
    }
    catch(IOException e) {
      System.err.println("Write error!");
    }
  }

  private static int decodeMax(String s) {
    if(s.length() <= 1)
      return 0;
    if(s.charAt(0) != 'n' && s.charAt(0) != 'e' && s.charAt(0) != 'l')
      return 0;
    try {
      int bi = Integer.parseInt(s.substring(1));
      //System.out.println("BSSS: " + s);
      //System.out.println("BIII: " + bi);
      return bi;
    }
    catch(NumberFormatException e) {
      return 0;
    }
  }

  public static void loadStructure(FileReader reader, PCanvas canvas) throws InvalidSIFException {
    nCount = eCount = pCount = lCount = 0;
    PLayer nodeLayer = canvas.getCamera().getLayer(1);
    PLayer edgeLayer = canvas.getCamera().getLayer(0);
    BufferedReader br = new BufferedReader(reader);
    Dimension d = canvas.getSize();
    width = (int) d.getWidth();
    height = (int) d.getHeight();
    String s;
    count = 0;
    int mn_id = 0, me_id = 0, ml_id = 0;
    try {
      int line = 0;
      while((s = br.readLine()) != null) {
        line++;
        String[] ss = s.split("\\s");
        //System.out.println("line = " + line);
        //System.out.println("ss.length = " + ss.length);
        //System.out.println("s.length() = " + s.length());
        //if(ss.length > 0) System.out.println("ss[0].length() = " + ss[0].length());
        if(s.length() == 0 || ss.length == 0)
          continue;
        if(ss.length == 1 && ss[0].length() == 0)
          continue;

        //============================
        // ss[0] - source node id
        // ss[1] - edge/loop id or path
        // ss[2-..] - target nodes id
        //============================

        if(ss.length == 1) {// single node
          QNode node = addNode(ss[0].trim(), nodeLayer);
          int bi = decodeMax(ss[0].trim());
          if(bi > mn_id)
            mn_id = bi;
          continue;
        }

        //if (ss.length < 3) // invalid file
        //    throw new InvalidSIFException(line);

        // create(if not exist) source node
        QNode snode = addNode(ss[0].trim(), nodeLayer);
        int bi = decodeMax(ss[0].trim());
        if(bi > mn_id)
          mn_id = bi;

        ss[1] = ss[1].trim();

        // target nodes
        LinkedList targets = new LinkedList();
        for(int i = 2;i < ss.length;i++) {
          targets.add(addNode(ss[i].trim(), nodeLayer));
          bi = decodeMax(ss[i].trim());
          if(bi > mn_id)
            mn_id = bi;
        }

        // create edges, loops, paths
        for(Iterator it = targets.iterator();it.hasNext();) {
          QNode tnode = (QNode) it.next();
          // --------- loop --------- //
          if(snode == tnode) {
            QLoop loop = new QLoop(snode, ss[1]);
            bi = decodeMax(ss[1]);
            if(bi > ml_id)
              ml_id = bi;
            snode.addLoop(loop);
            loop.setAttr(ss[1]);
            edgeLayer.addChild(loop);
            lCount++;

          }
          else {
            // --------- path --------- //
            //System.out.println(ss[2].charAt(0)+"\n");
            if(ss[1].charAt(0) == '?' && (ss[1].charAt(1) == '=' || ss[1].charAt(1) == '>' || ss[1].charAt(1) == '!' || ss[1].charAt(1) == '<')) {
              // path have a dummy id
              QPath path = new QPath(snode, tnode, "path");
              path.setAttr(ss[1]);
              snode.addPath(path);
              tnode.addPath(path);
              edgeLayer.addChild(path);
              pCount++;
            }
            else {
              // --------- edge --------- //
              QEdge edge = new QEdge(snode, tnode, ss[1]);
              bi = decodeMax(ss[1]);
              if(bi > me_id)
                me_id = bi;
              edge.setAttr(ss[1]);
              snode.addEdge(edge);
              tnode.addEdge(edge);
              edgeLayer.addChild(edge);
              eCount++;
            }
          }// path or edge
        }// for all targets
      }// while (lines)
      nodeCount = mn_id + 1;
      edgeCount = me_id + 1;
      loopCount = ml_id + 1;
    }
    catch(IOException e) {
      System.err.println("Error loading file");
    }
    //doLayout(nodeLayer);
    updateAll(nodeLayer);
  }

  public static void loadNodeAttr(FileReader reader, PLayer nodeLayer) throws InvalidSIFException {
    BufferedReader br = new BufferedReader((reader));
    String s;
    try {
      // first line contains name of attributes
      br.readLine();// skip it? NO
      int line = 1;
      while((s = br.readLine()) != null) {
        line++;
        String[] ss = s.split("=");
        String id = ss[0].trim();// node id
        String label = ss[1].trim();// node attribute
        if(ss.length != 2)// invalid format
          throw new InvalidSIFException(line);
        Iterator it = nodeLayer.getAllNodes().iterator();
        QNode node = null;
        while(it.hasNext()) {
          PNode candNode = (PNode) it.next();
          if(!(candNode instanceof QNode))
            continue;
          if(((QNode) candNode).getId().equals(id)) {
            node = (QNode) candNode;
            break;
          }
        }
        if(node != null) {
          node.setAttr(label);
        }
      }
      //updateAll(nodeLayer);
    }
    catch(IOException e) {
      System.err.println("Error loading node labels file");
    }

  }

  public static void loadEdgeLoopAttr(FileReader reader, PLayer edgeLayer) throws InvalidSIFException {
    BufferedReader br = new BufferedReader((reader));
    String s;
    try {
      // first line contains name of structure file
      br.readLine();// skip it?
      int line = 1;
      while((s = br.readLine()) != null) {
        line++;
        String[] ss = s.split("=");
        String[] edge_info = ss[0].trim().split("\\s");
        String label = ss[1].trim();
        //System.out.println("id = " + id);
        //System.out.println("label = " + label);
        if(!(edge_info.length == 1 || edge_info.length == 3))
          throw new InvalidSIFException(line);
        if(edge_info.length == 1) {
          String id = edge_info[0].trim();
          Iterator it = edgeLayer.getAllNodes().iterator();
          while(it.hasNext()) {
            PNode node = (PNode) it.next();
            if(!(node instanceof QElement))
              continue;
            QElement elem = (QElement) node;
            if(elem.getId().equals(id))
              elem.setAttr(label);
          }
        }
        else {
          String id_from = edge_info[0].trim();
          String id_edge = edge_info[1].trim();
          if(id_edge.charAt(0) != '(' || id_edge.charAt(id_edge.length() - 1) != ')')
            throw new InvalidSIFException(line);
          id_edge = id_edge.substring(1, id_edge.length() - 1);
          String id_to = edge_info[2].trim();
          Iterator it = edgeLayer.getAllNodes().iterator();
          if(id_from.equals(id_to)) {// loop
            while(it.hasNext()) {
              PNode node = (PNode) it.next();
              if(!(node instanceof QLoop))
                continue;
              QLoop loop = (QLoop) node;
              if(loop.getId().equals(id_edge) && loop.getOwner().getId().equals(id_from))
                loop.setAttr(label);
            }
          }
          else {// edge
            while(it.hasNext()) {
              PNode node = (PNode) it.next();
              if(!(node instanceof QEdge))
                continue;
              QEdge edge = (QEdge) node;
              if(edge.getId().equals(id_edge) && edge.getSource().getId().equals(id_from) && edge.getTarget().getId().equals(id_to))
                edge.setAttr(label);
            }
          }
        }

      }
      //updateAll(nodeLayer);
    }
    catch(IOException e) {
      System.err.println("Error loading node labels file");
    }
  }

  public static QNode addNode(String id, PLayer nodeLayer) {
    Iterator it = nodeLayer.getAllNodes().iterator();
    QNode node = null;
    while(it.hasNext()) {
      PNode candNode = (PNode) it.next();
      if(!(candNode instanceof QNode))
        continue;
      //System.out.println(candNode.toString());
      if(((QNode) candNode).getId().equals(id))
        node = (QNode) candNode;
    }
    if(node != null)
      return node;
    int x = r.nextInt(width - QNode.NODE_INIT_WIDTH);
    x = x <= QNode.NODE_INIT_WIDTH ? QNode.NODE_INIT_WIDTH + 1 : x;
    int y = r.nextInt(height - QNode.NODE_INIT_HEIGHT);
    y = y <= QNode.NODE_INIT_HEIGHT ? QNode.NODE_INIT_HEIGHT + 1 : y;
    node = new QNode(x, y, id);
    node.setAttr(id);
    node.addToLayer(nodeLayer);
    node.update();
    nCount++;
    count++;
    return node;
  }

  /*public static GraphLoader getGraphLoader(PLayer nodeLayer, PLayer edgeLayer) {
    GraphLoader qLoader = new GraphLoader(null);
    HashMap idMap = new HashMap();
    int id = 0;
    Iterator it = nodeLayer.getAllNodes().iterator();
    while(it.hasNext()) {
      PNode candNode = (PNode)it.next();
      if(!(candNode instanceof QNode))
        continue;
      QNode node = (QNode)candNode;
      qLoader.InsertNode(node.getAttr());
      idMap.put(node.getId(), new Integer(id++));
    }
    it = edgeLayer.getAllNodes().iterator();
    while(it.hasNext()) {
      PNode candNode = (PNode)it.next();
      if(candNode instanceof QEdge) {
        QEdge edge = (QEdge)candNode;
        qLoader.InsertEdge(((Integer)idMap.get(edge.getSource().getId())).intValue(), ((Integer)idMap.get(edge.getTarget().getId())).intValue(), edge.getAttr());

      }
      else if(candNode instanceof QPath) {
        QPath path = (QPath)candNode;
        qLoader.InsertEdge(((Integer)idMap.get(path.getSource().getId())).intValue(), ((Integer)idMap.get(path.getTarget().getId())).intValue(), path.getAttr());

      }
      else if(candNode instanceof QLoop) {
        QLoop loop = (QLoop)candNode;
        qLoader.InsertNode(loop.getOwner().getAttr() + Common.SELF_EDGE);
        qLoader.InsertEdge(((Integer)idMap.get(loop.getOwner().getId())).intValue(), id, loop.getAttr());
        id++;
      }
    }
    return qLoader;
  }*/
}

// invalid file format exception

class InvalidSIFException extends Exception {
  int line;

  public InvalidSIFException(int line) {
    this.line = line;
  }

  public String getMessage() {
    return "Invalid SIF file.\nParsing error at line: " + line + "\n";
  }
}