package netmatch;

import cytoscape.CyNetwork;
import cytoscape.Cytoscape;
import cytoscape.actions.GinyUtils;
import cytoscape.util.CyFileFilter;
import cytoscape.util.FileUtil;
import cytoscape.view.CyNetworkView;
import cytoscape.view.CytoscapeDesktop;
import cytoscape.view.cytopanels.CytoPanel;
import cytoscape.view.cytopanels.CytoPanelState;
import giny.model.GraphPerspective;
import giny.model.Node;
import giny.model.Edge;
import giny.view.NodeView;
import giny.view.EdgeView;
//import giny.util.SwingWorker;
import ding.view.DGraphView;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.DateFormat;
import java.util.*;

import netmatch.algorithm.Common;

public class netMatchResultsDialog extends JPanel {
  protected JTable table;
  JScrollPane scrollPane;
  protected netMatchResultsTableModel model;
  protected final int defaultRowHeight = Common.imageSize;
  protected boolean openAsNewChild = false;
  protected GraphPerspective[] gpComplexArray;    //The list of complexes, sorted by score when !null
  CyNetwork originalInputNetwork;                 //Keep a record of the original input record for use in the
  CyNetworkView originalInputNetworkView;         //Keep a record of this too, if it exists
  HashMap hmNetworkNames;                         //Keep a record of network names we create from the table
  JButton saveButton;
  JButton closeButton;
  JCheckBox newWindowCheckBox;
  boolean isApproximate;
  ArrayList allPaths;
  boolean showPics;

  CyNetwork network;

  public netMatchResultsDialog() {
    network = null;
    showPics = false;
    setLayout(new BorderLayout());
    hmNetworkNames = new HashMap();
    isApproximate = false;
    allPaths = null;
    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.setBackground(Color.WHITE);
    table = new JTable() {
      public String getToolTipText(MouseEvent e) {
        Point p = e.getPoint();
        int rowIndex = rowAtPoint(p);
        int colIndex = columnAtPoint(p);
        int realColumnIndex = convertColumnIndexToModel(colIndex);
        String tip = getColumnName(realColumnIndex);
        if(realColumnIndex == 0 || realColumnIndex == 1)
          tip += ":" + getValueAt(rowIndex, colIndex);
        /*if(realColumnIndex == 0) {
          String value = (String)getValueAt(rowIndex,colIndex);
          System.out.println("value:"+value);
          String nodes = null;
          String edges = null;
          int i = 0;
          for(StringTokenizer st = new StringTokenizer(value,",");st.hasMoreTokens();i++) {
            String s = st.nextToken();
            System.out.println("String:"+s+" "+i);
            if(i == 0)
              nodes = s;
            else if(i == 1)
              edges = s;
          }
          tip = "SubGraph has " + nodes + " nodes and " + edges + " edges.";
        }*/
        return tip;
      }
    };
    table.setRowHeight(defaultRowHeight);
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    table.setDefaultRenderer(String.class, new CenterAndBoldRenderer());
    table.setDefaultRenderer(StringBuffer.class, new JTextAreaRenderer());
    ListSelectionModel rowSM = table.getSelectionModel();
    rowSM.addListSelectionListener(new TableRowSelectionHandler());
    scrollPane = new JScrollPane(table);
    scrollPane.getViewport().setBackground(Color.WHITE);
    panel.add(scrollPane, BorderLayout.CENTER);
    JPanel bottomPanel = new JPanel();
    newWindowCheckBox = new JCheckBox("Create a new child network.", false);
    newWindowCheckBox.setToolTipText("<html>If checked, a new child network of<br>the selected match will be created.<br>" +
            "Otherwise, the occurrence of the<br>query match in the network window<br>will be highlighted.</html>");
    newWindowCheckBox.addItemListener(new netMatchResultsDialog.newWindowCheckBoxAction());
    newWindowCheckBox.setEnabled(false);
    bottomPanel.add(newWindowCheckBox);
    saveButton = new JButton("Save");
    saveButton.setToolTipText("Save result summary to a file.");
    saveButton.setEnabled(false);
    bottomPanel.add(saveButton);
    closeButton = new JButton("Close");
    closeButton.setToolTipText("Close NetMatch Results Dialog.");
    bottomPanel.add(closeButton);
    panel.add(bottomPanel, BorderLayout.SOUTH);
    add(panel, BorderLayout.CENTER);
  }

  public void set(ArrayList complexes,CyNetwork network,netMatchResultsTableModel tab,boolean isApproximate,ArrayList allPaths) {
    this.allPaths = allPaths;
    this.network = network;
    this.isApproximate = isApproximate;
    originalInputNetwork = network;
    originalInputNetworkView = Cytoscape.getNetworkView(network.getIdentifier());
    model = tab;
    gpComplexArray = model.getListOfComplexes();
    table.setModel(model);
    table.setVisible(true);
    initColumnSizes(table);
    saveButton.addActionListener(new netMatchResultsDialog.SaveAction(complexes, network));
    closeButton.addActionListener(new netMatchResultsDialog.CloseAction(this));
    saveButton.setEnabled(true);
    newWindowCheckBox.setEnabled(true);
  }

  public void clear() {
    isApproximate = false;
    originalInputNetwork = null;
    originalInputNetworkView = null;
    ActionListener al[] = saveButton.getActionListeners();
    for(int i = 0;i < al.length;i++)
      saveButton.removeActionListener(al[i]);
    saveButton.setEnabled(false);
    newWindowCheckBox.setEnabled(false);
    table.setVisible(false);
  }

  /**
   * Utility method to initialize the column sizes of the table
   *
   * @param table Table to initialize sizes for
   */
  private void initColumnSizes(JTable table) {
    TableColumn column;

    for(int i = 0;i < 3;i++) {
      column = table.getColumnModel().getColumn(i);
      column.sizeWidthToFit();
    }
  }

  /**
   * Handles the Save press for this dialog (save results to a file)
   */
   private class SaveAction extends AbstractAction {
    private ArrayList complexes;
    private CyNetwork network;

    /**
     * Save action constructor
     *
     * @param complexes Complexes to save
     * @param network   Network complexes are from for information about complex components
     */
    SaveAction(ArrayList complexes, CyNetwork network) {
      super("");
      this.complexes = complexes;
      this.network = network;
    }

    public void actionPerformed(ActionEvent e) {
      File file = FileUtil.getFile("Save Graph as Interactions", FileUtil.SAVE, new CyFileFilter[] {});
      if(file != null) {
        String fileName = file.getAbsolutePath();
        saveResults(complexes, network, fileName);
      }
    }

    /**
     * Save results to a file
     *
     * @param complexes The list of complexes
     * @param network   The network source of the complexes
     * @param fileName  The file name to write to
     * @return True if the file was written, false otherwise
     */
    public boolean saveResults(ArrayList complexes, CyNetwork network, String fileName) {
      if(complexes == null || network == null || fileName == null)
        return false;
      String lineSep = System.getProperty("line.separator");
      try {
        File file = new File(fileName);
        FileWriter fout = new FileWriter(file);
        fout.write("NetMatch Plugin Results" + lineSep);
        fout.write("Date: " + DateFormat.getDateTimeInstance().format(new Date()) + lineSep + lineSep);
        fout.write("Match Number\tNodeCount\tEdgeCount\tNames" + lineSep);
        //get GraphPerspectives for all complexes, score and rank them
        //convert the ArrayList to an array of GraphPerspectives and sort it by complex score
        GraphPerspective[] gpComplexArray = convertComplexListToNetworkList(complexes, network);
        for(int i = 0;i < gpComplexArray.length;i++) {
          GraphPerspective gpComplex = gpComplexArray[i];
          fout.write((i + 1) + "\t\t");
          NumberFormat nf = NumberFormat.getInstance();
          nf.setMaximumFractionDigits(3);
          fout.write(gpComplex.getNodeCount() + "\t\t");
          fout.write(gpComplex.getEdgeCount() + "\t\t");
          fout.write(getNodeNameList(gpComplex).toString() + lineSep);
        }
        fout.close();
        return true;
      }
      catch(IOException e) {
        JOptionPane.showMessageDialog(Cytoscape.getDesktop(), e.toString(), "Error Writing to \"" + fileName + "\"", JOptionPane.ERROR_MESSAGE);
        return false;
      }
    }

    /**
     * Utility method to get the names of all the nodes in a GraphPerspective
     *
     * @param gpInput The input graph perspective to get the names from
     * @return A concatenated set of all node names (separated by a comma)
     */
    private StringBuffer getNodeNameList(GraphPerspective gpInput) {
      Iterator i = gpInput.nodesIterator();
      StringBuffer sb = new StringBuffer();
      while(i.hasNext()) {
        Node node = (Node)i.next();
        sb.append(node.getIdentifier());
        if(i.hasNext()) {
          sb.append(", ");
        }
      }
      return (sb);
    }

    private GraphPerspective convertComplexToNetwork(int[] complex, CyNetwork sourceNetwork) {
      GraphPerspective gpComplex;
      gpComplex = sourceNetwork.createGraphPerspective(complex);
      return gpComplex;
    }

    private GraphPerspective[] convertComplexListToNetworkList(ArrayList complexList, CyNetwork sourceNetwork) {
      GraphPerspective gpComplexArray[] = new GraphPerspective[complexList.size()];
      for(int i = 0;i < complexList.size();i++) {
        gpComplexArray[i] = convertComplexToNetwork((int[])complexList.get(i), sourceNetwork);
      }
      return gpComplexArray;
    }
  }


  /**
   * Handles the Close press for this dialog
   */
   private class CloseAction extends AbstractAction {
    netMatchResultsDialog d;

    /**
     * Close action constructor
     *
     * @param d The netMatchResultsDialog handle
     */
    CloseAction(netMatchResultsDialog d) {
      super("");
      this.d = d;
    }

    public void actionPerformed(ActionEvent e) {
      CytoscapeDesktop desktop = Cytoscape.getDesktop();
      CytoPanel cytoPanel = desktop.getCytoPanel(SwingConstants.EAST);
      cytoPanel.remove(d);
      if (cytoPanel.getCytoPanelComponentCount() == 0) {
        cytoPanel.setState(CytoPanelState.HIDE);
      }
    }
  }

  /**
   * Handles the new window parameter choice
   */
  private class newWindowCheckBoxAction implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      openAsNewChild = e.getStateChange() != ItemEvent.DESELECTED;
    }
  }

  /**
   * Handler to selects nodes in graph or create a new network when a row is selected
   * Note: There is some fairly detailed logic in here to deal with all the cases that a user can interact
   * with this dialog box. Be careful when editing this code.
   */
  private class TableRowSelectionHandler implements ListSelectionListener {
    public void valueChanged(ListSelectionEvent e) {
      //Ignore extra messages.
      if(e.getValueIsAdjusting())
        return;
      ListSelectionModel lsm = (ListSelectionModel)e.getSource();
      final GraphPerspective gpComplex;
      NodeView nv;
      EdgeView ev;
      if(!lsm.isSelectionEmpty()) {
        final int selectedRow = lsm.getMinSelectionIndex();
        gpComplex = gpComplexArray[selectedRow];
        //only do this if a view has been created on this network
        if(originalInputNetworkView != null) {
          //start with no selected nodes
          GinyUtils.deselectAllNodes(originalInputNetworkView);
          GinyUtils.deselectAllEdges(originalInputNetworkView);

          //go through graph and select nodes in the complex
          for(Iterator i = gpComplex.nodesIterator(); i.hasNext();) {
            Node n = (Node)i.next();
            //System.out.println("n:"+n.getIdentifier());
            if(originalInputNetwork.containsNode(n)) {
              nv = originalInputNetworkView.getNodeView(n);
              if(nv != null)
                nv.setSelected(true);
            }
          }
          //if(!isApproximate) {
            for(Iterator i = gpComplex.edgesIterator(); i.hasNext();) {
              Edge ed = (Edge)i.next();
              if(originalInputNetwork.containsEdge(ed)) {
                ev = originalInputNetworkView.getEdgeView(ed);
                if(ev != null)
                  ev.setSelected(true);
              }
            }
          //}
          if(isApproximate){//else {
            ArrayList paths = (ArrayList)allPaths.get(selectedRow);
            for(int i =0 ; i< paths.size();i++) {
              int p[] = (int[])paths.get(i);
              for(int j =0 ; j< p.length;j++) {
                Edge edge = originalInputNetwork.getEdge(p[j]);
                Node s = edge.getSource();
                if(originalInputNetwork.containsNode(s)) {
                  nv = originalInputNetworkView.getNodeView(s);
                  if(nv != null)
                    nv.setSelected(true);
                }
                Node d = edge.getTarget();
                if(originalInputNetwork.containsNode(d)) {
                  nv = originalInputNetworkView.getNodeView(d);
                  if(nv != null)
                    nv.setSelected(true);
                }
                if(originalInputNetwork.containsEdge(edge)) {
                  ev = originalInputNetworkView.getEdgeView(edge);
                  if(ev != null)
                    ev.setSelected(true);
                }
                //System.out.print("["+s.getIdentifier()+","+d.getIdentifier()+"] ");
              }
              //System.out.println("");
            }
          }
          
          if(!openAsNewChild) {
            //switch focus to the original network if not going to create a new network
            Cytoscape.getDesktop().setFocus(originalInputNetworkView.getIdentifier());
          }
        }
        else /*if(!openAsNewChild)*/ {
          //Warn user that nothing will happen in this case because there is no view to select nodes with
          JOptionPane.showMessageDialog(Cytoscape.getDesktop(), "You must have a network view created to select nodes.");
        }
        if(openAsNewChild) {
          NumberFormat nf = NumberFormat.getInstance();
          nf.setMaximumFractionDigits(3);
          final String title = "Match " + (selectedRow + 1);
          //check if a network has already been created
          String id = (String)hmNetworkNames.get(new Integer(selectedRow + 1));
          if(id != null) {
            //just switch focus to the already created network
            Cytoscape.getDesktop().setFocus(id);
          }
          else {
            //create the child network and view
            final SwingWorker worker = new SwingWorker() {
              public Object construct() {
                //CyNetwork newNetwork = Cytoscape.createNetwork(gpComplex.getNodeIndicesArray(), gpComplex.getEdgeIndicesArray(), title, originalInputNetwork);
                CyNetwork newNetwork = Cytoscape.createNetwork(originalInputNetworkView.getSelectedNodeIndices(),originalInputNetworkView.getSelectedEdgeIndices(), title, originalInputNetwork);
                hmNetworkNames.put(new Integer(selectedRow + 1), newNetwork.getIdentifier());
                try {
                DGraphView view = (DGraphView)Cytoscape.createNetworkView(newNetwork);
                //layout new complex and fit it to window
                //randomize node positions before layout so that they don't all layout in a line
                //(so they don't fall into a local minimum for the SpringEmbedder)
                //If the SpringEmbedder implementation changes, this code may need to be removed
                NodeView nv;
                for(Iterator in = view.getNodeViewsIterator();in.hasNext();) {
                  nv = (NodeView)in.next();
                  nv.setXPosition(view.getCanvas().getWidth() * Math.random());
                  //height is small for many default drawn graphs, thus +100
                  nv.setYPosition((view.getCanvas().getHeight() + 100) * Math.random());
                }

                // AGGIUSTARE

                /*EdgeView ev;
                for(Iterator in = view.getEdgeViewsIterator();in.hasNext();) {
                  ev = (EdgeView)in.next();
                  ev.drawSelected();
                  //ev.setXPosition(view.getCanvas().getLayer().getGlobalFullBounds().getWidth() * Math.random());
                  //height is small for many default drawn graphs, thus +100
                  //ev.setYPosition((view.getCanvas().getLayer().getGlobalFullBounds().getHeight() + 100) * Math.random());
                }*/

                //SpringEmbeddedLayouter lay = new SpringEmbeddedLayouter(view);
                //lay.doLayout();
                view.fitContent();
                }
                catch(Exception e) {
                 // e.printStackTreace();
                }
                return null;
              }
            };
            worker.start();//execute();//.start();
          }
        }
      }
    }
  }

  /**
   * A table cell rendered that centers the item in the cell
   */
  private class CenterAndBoldRenderer extends DefaultTableCellRenderer {
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                                                   boolean hasFocus, int row, int column) {
      Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
      cell.setFont(new Font(this.getFont().getFontName(), Font.BOLD, 14));
      this.setHorizontalAlignment(CENTER);
      this.setVerticalAlignment(NORTH);
      return cell;
    }
  }

  /**
   * A text area renderer that creates a line wrapped, non-editable text area
   */
  private class JTextAreaRenderer extends JTextArea implements TableCellRenderer {

    /**
     * Constructor
     */
    public JTextAreaRenderer() {
      this.setLineWrap(true);
      this.setWrapStyleWord(true);
      this.setEditable(false);
      this.setAutoscrolls(true);
    }

    /**
     * Used to render a table cell.  Handles selection color and cell heigh and width.
     * Note: Be careful changing this code as there could easily be infinite loops created
     * when calculating preferred cell size as the user changes the dialog box size.
     *
     * @param table      Parent table of cell
     * @param value      Value of cell
     * @param isSelected True if cell is selected
     * @param hasFocus   True if cell has focus
     * @param row        The row of this cell
     * @param column     The column of this cell
     * @return The cell to render by the calling code
     */
    public Component getTableCellRendererComponent(JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column) {
      StringBuffer sb = (StringBuffer)value;
      this.setText(sb.toString());
      if(isSelected) {
        this.setBackground(table.getSelectionBackground());
        this.setForeground(table.getSelectionForeground());
      }
      else {
        this.setBackground(table.getBackground());
        this.setForeground(table.getForeground());
      }
      //row height calculations
      int currentRowHeight = table.getRowHeight(row);
      this.setSize(table.getColumnModel().getColumn(column).getWidth(), currentRowHeight);
      int textAreaPreferredHeight = (int)this.getPreferredSize().getHeight();
      //JTextArea can grow and shrink here
      if(currentRowHeight < textAreaPreferredHeight)
        table.setRowHeight(row, textAreaPreferredHeight); //grow row height
      else if((currentRowHeight > textAreaPreferredHeight) && (currentRowHeight != defaultRowHeight)) {
        //defaultRowHeight check in if statement avoids infinite loop shrink row height
        table.setRowHeight(row, defaultRowHeight);
      }
      return this;
    }
  }
}