public static javax.swing.JComboBox StatusFilterBox; public class GraphPath {

    /**
     * Method to find a path between two selected vertices
     *
     * @param variables
     * @return the edges that compose the path
     */
    public static Collection<Edge> FindPath(Variables variables) {
        Vertex source;
        Vertex target;
        PickedState<Object> picked_state = variables.view.getPickedVertexState();
        if (!picked_state.getPicked().isEmpty() && picked_state.getPicked().size() > 1) {
            source = (Vertex) picked_state.getPicked().toArray()[0];
            target = (Vertex) picked_state.getPicked().toArray()[1];
//            System.out.println("Source: " + source.getID());
//            System.out.println("target: " + target.getID());
            Map<String, Edge> path = BFS(source, target, variables.layout.getGraph());
            return CleanPath(path, source.getID(), target.getID());
        }
        return null;
    }

    /**
     * Method to calculate the probability of reaching the destination from the
     * source following the cleanedPath path
     *
     * @param variables
     * @param cleanedPath requires the path
     * @return the detailed information in the form of a String for the
     * TooltipDialogBox
     */
    public static String PathProbability(Variables variables, Collection<Edge> cleanedPath) {
        String answer = "";
        if (cleanedPath != null) {
            float probabilityIn = 1;
            float probabilityOut = 1;
            String probPathIn = "";
            String probPathOut = "";
            String path = "";
            PickedState<Edge> picked_edge_state = variables.view.getPickedEdgeState();
            picked_edge_state.clear();
            for (Edge e : cleanedPath) {
                float in = Float.valueOf(e.getAttributeValue(VariableNames.MarkovIn));
                float out = Float.valueOf(e.getAttributeValue(VariableNames.MarkovOut));
                probPathIn += " * " + in;
                probPathOut += " * " + out;
                path += e.getID() + " - > ";
                System.out.print(e.getID() + " - > ");
                probabilityIn = probabilityIn * in;
                probabilityOut = probabilityOut * out;
                picked_edge_state.pick(e, true);
            }
            System.out.println();
//            System.out.println("probability: " + probability);
            PickedState<Object> picked_state = variables.view.getPickedVertexState();
            Vertex source = (Vertex) picked_state.getPicked().toArray()[0];
            Vertex target = (Vertex) picked_state.getPicked().toArray()[1];
            picked_state.clear();
            answer = "Source: " + source.getID()
                    + "\n" + "Target: " + target.getID()
                    + "\n" + "Path: " + path
                    + "\nThe probability of taking this path, linking the selected source to the destination is: "
                    + "\n" + "Prob IN: " + probabilityIn + " ( 1" + probPathIn + ")"
                    + "\n" + "Prob OUT: " + probabilityOut + " ( 1" + probPathOut + ")"
                    + "\n";
        }

//        answer += "<br>The probability of taking this path, linking the selected source to the destination is: ";
        return answer;
    }

    /**
     * Method to compute the Path probability, invoking the methods FindPath and
     * PathProbability
     *
     * @param variables
     * @return the detailed information in the form of a String for the
     * TooltipDialogBox
     */
    public static String ComputePath(Variables variables) {
        return PathProbability(variables, FindPath(variables));
    }

    /**
     * Method to clean the path returned from BFS, removing the paths that did
     * not reach the destination This algorithm backtracks from the Target to
     * the Source
     *
     * @param path is the path returned by BFS
     * @param source is the ID of the source vertex
     * @param target is the ID of the target vertex
     * @return the shortest path
     */
    public static Collection<Edge> CleanPath(Map<String, Edge> path, String source, String target) {
        Collection<Edge> cleanedPath = new ArrayList<>();
        String destination = target;
        while (!destination.equals(source)) {
            cleanedPath.add(path.get(destination));
            destination = ((Vertex) path.get(destination).getTarget()).getID();
        }
        return cleanedPath;
    }
    
    /**
     * Breadth first search algorithm that returns the minimum path if exist
     * @param source is the source vertex
     * @param target is the destination vertex
     * @param graph is the graph
     * @return the list of edges connecting source to target
     */
    public static Map<String, Edge> BFS(Vertex source, Vertex target, Graph graph)
    {

        Map<String, Boolean> visited = new HashMap<>();
        Map<String, Edge> path = new HashMap<>();

        LinkedList<Vertex> queue = new LinkedList<>();
        LinkedList<Edge> edgeQueue = new LinkedList<>();
 
        // Mark the current node as visited and enqueue it
        visited.put(source.getID(), Boolean.TRUE);
        queue.add(source);
 
        while (!queue.isEmpty())
        {
            // Dequeue a vertex from queue and print it
            source = queue.poll();
            Edge next = edgeQueue.poll();
            if(next != null)
                path.put(source.getID(), next);
//            System.out.print(source.getID() + " -> ");
            // Get all adjacent vertices of the dequeued vertex s
            // If a adjacent has not been visited, then mark it
            // visited and enqueue it
            Iterator<Object> i = graph.getInEdges(source).iterator();
            while (i.hasNext())
            {
                Edge edge = (Edge) i.next();
                Vertex n = (Vertex) edge.getSource();
                if (!visited.containsKey(n.getID()))
                {
                    visited.put(n.getID(), Boolean.TRUE);
                    queue.add(n);
                    edgeQueue.add(edge);
                    if(n.getID().equalsIgnoreCase(target.getID())) {
//                        System.out.println("Reached target: " + n.getID());
                        path.put(n.getID(), edge);
                        return path;
                    }
                }
            }
        }
        System.out.println();
        System.out.println("No path exists");
        return null;
    }
} public class Edge extends GraphObject {

    private String id;
    private Object source;
    private Object target;
    private final String type;           // Edge type (prov edges)
    //used to hide this edge when collapsing a group of edges
    private boolean hide;
    private Color defaultColor = new Color(0, 0, 255);
    private String influenceName = "Influence";

    /**
     * Constructor for testing purposes since it construct a mostly blank edge
     *
     * @param id is the edge ID
     * @param target is the target vertex
     * @param source is the source vertex
     */
    public Edge(String id, Object target, Object source) {
        this.id = id;
        this.source = source;
        this.target = target;
        this.type = "";
        hide = false;
        this.attributes = new HashMap<>();
        GraphAttribute att = new GraphAttribute(influenceName, "", "");
        this.attributes.put(att.getName(), att);
        if (!this.hasAttribute(VariableNames.edgeHiddenAttribute)) {
            GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
            this.attributes.put(hide.getName(), hide);
        } else {
            hide = Boolean.parseBoolean(this.getAttributeValue(VariableNames.edgeHiddenAttribute));
        }
//        this.attributes.putAll(attributes);
        setLabel("");
    }

    public Edge(String id, String type, Object target, Object source) {
        this.id = id;
        this.source = source;
        this.target = target;
        this.type = type;
        hide = false;
        this.attributes = new HashMap<>();
        GraphAttribute att = new GraphAttribute(influenceName, "", "");
        this.attributes.put(att.getName(), att);
        if (!this.hasAttribute(VariableNames.edgeHiddenAttribute)) {
            GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
            this.attributes.put(hide.getName(), hide);
        } else {
            hide = Boolean.parseBoolean(this.getAttributeValue(VariableNames.edgeHiddenAttribute));
        }
//        this.attributes.putAll(attributes);
        setLabel(type);
    }

    /**
     * Constructor
     *
     * @param id
     * @param type
     * @param value
     * @param label
     * @param attributes
     * @param target
     * @param source
     */
    public Edge(String id, String type, String value,
            String label, Map<String, GraphAttribute> attributes, Object target, Object source) {
        this.id = id;
        this.source = source;
        this.target = target;
        this.type = type;
        hide = false;
        this.attributes = new HashMap<>(attributes);
        GraphAttribute att = new GraphAttribute(influenceName, value, "");
        this.attributes.put(att.getName(), att);
        if (!this.hasAttribute(VariableNames.edgeHiddenAttribute)) {
            GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
            this.attributes.put(hide.getName(), hide);
        } else {
            hide = Boolean.parseBoolean(this.getAttributeValue(VariableNames.edgeHiddenAttribute));
        }
        setLabel(label);
    }

    /**
     * Constructor without extra attributes
     *
     * @param id
     * @param type
     * @param value
     * @param label
     * @param target
     * @param source
     */
    public Edge(String id, String type, String label, String value, Object target, Object source) {
        this.id = id;
        this.source = source;
        this.target = target;
        this.type = type;
//        hide = false;
        this.attributes = new HashMap<>();
        GraphAttribute att = new GraphAttribute(influenceName, value, "");
        this.attributes.put(att.getName(), att);
        if (!this.hasAttribute(VariableNames.edgeHiddenAttribute)) {
            GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
            this.attributes.put(hide.getName(), hide);
        } else {
            hide = Boolean.parseBoolean(this.getAttributeValue(VariableNames.edgeHiddenAttribute));
        }
        setLabel(label);
//        if (label.equalsIgnoreCase("") || label == null || "-".equals(label) || label.equalsIgnoreCase("Neutral")) {
//            setLabel("Neutral");
//            value = "0";
//        }

    }

    /**
     * Constructor without influence value, label, type (type=influence) and no
     * extra attribute
     *
     * @param id Edge's ID
     * @deprecated Used only on outdated TSVReader
     * @param target Vertex target
     * @param source Vertex source
     * @param influence Influence value and name (i.e. "+9 damage")
     */
    public Edge(String id, Object target, Object source, String influence) {
        this.id = id;
        this.source = source;
        this.target = target;
        influence = "0";
        this.type = getLabel();
        hide = false;
        this.attributes = new HashMap<>();
        GraphAttribute att = new GraphAttribute(influenceName, influence, "");
        this.attributes.put(att.getName(), att);
        GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
        this.attributes.put(hide.getName(), hide);
        if (influence.equalsIgnoreCase("")) {
            setLabel("Neutral");
        } else {
            setLabel(influence);
        }
    }

    /**
     * Return Edge id
     *
     * @return
     */
    public String getID() {
        return id;
    }

    /**
     * Set the edge ID
     *
     * @param t is the new value
     */
    public void setID(String t) {
        id = t;
    }

    /**
     * Method to get the edge source
     *
     * @return vertex source
     */
    public Object getSource() {
        return source;
    }

    /**
     * Set the edge's source
     *
     * @param t is the new source
     */
    public void setSource(Object t) {
        source = t;
    }

    /**
     * Method to get the edge target
     *
     * @return vertex target
     */
    public Object getTarget() {
        return target;
    }

    /**
     * Set the edge's target/destionation
     *
     * @param t is the new target
     */
    public void setTarget(Object t) {
        target = t;
    }

    /**
     * Method for returning the edge's numeric value (converted from String)
     *
     * @return (float) edge influence value
     */
    public float getValue() {
        if (Utils.tryParseDouble(this.getAttributeValue(influenceName))) {
            return Float.parseFloat(this.getAttributeValue(influenceName));
        } else if (Utils.tryParseDouble(this.getAttributeValue(influenceName).split(" ")[0])) {
            return Float.parseFloat(this.getAttributeValue(influenceName).split(" ")[0]);
        } else {
            return 0;
        }
    }

    /**
     * Returns the edge's value as the original String instead of converting to
     * numeric
     *
     * @return
     */
    public String getStringValue() {
        return this.getAttributeValue(influenceName);
    }

    /**
     * Updates the edge's value
     *
     * @param t is the string with the new value
     */
    public void setValue(String t) {
        this.getAttribute(influenceName).setValue(t);
    }

    /**
     * Method for returning the edge type
     *
     * @return
     */
    public String getType() {
        return this.type;
    }

    /**
     * Method to get the edge influence + value
     *
     * @param nGraphs is the number of different graphFiles values
     * @return (String) influence
     */
    public String getEdgeTooltip(int nGraphs) {
        return "<br>ID: " + this.id
                + "<br>Label: " + getLabel()
                + "<br>"
                + "<br>Type: " + getType()
                + "<br>Frequency: " + getFrequency(nGraphs)
                + "<br>" + printAttributes();
    }

    /**
     * Method to check if the edge is hidden (due to collapses)
     *
     * @return (boolean) if the edge is hidden or not
     */
    public boolean isHidden() {
        return hide;
//        return Boolean.parseBoolean(this.attributes.get(VariableNames.edgeHiddenAttribute).getValue());
    }

    /**
     * Method to set the hide parameter
     *
     * @param t (boolean) Hide = t
     */
    public void setHide(boolean t) {
        hide = t;
        GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, Boolean.toString(t), "");
        this.attributes.put(hide.getName(), hide);
    }

//    public String getHide() {
//        return this.attributes.get(VariableNames.edgeHiddenAttribute).getValue();
//    }
    /**
     * Method to check if the edge is of the neutral type (Empty influence or
     * value equals zero
     *
     * @return (boolean) is neutral or not
     */
    public boolean isNeutral() {
        return (getLabel().equalsIgnoreCase(""))
                || (getLabel().isEmpty())
                || (getLabel().equalsIgnoreCase("Neutral"));
    }

    /**
     * Method to override JUNG's default toString method
     *
     * @return edge details
     */
    @Override
    public String toString() {
        String font = VariableNames.FontConfiguration;
        if (getLabel().isEmpty()) {
            return font + this.type;
        } else if (getLabel().equals(this.type)) {
            return font + this.type;
        } else {
            return font + this.type + " (" + getLabel() + ")";
        }
    }

    private Color defaultColor() {
        if (this.getLabel().contains("Crash")) {
            return new Color(204, 0, 204);
        }
        if (((Vertex) this.getTarget()).getTime() < 100) {
            return Color.RED;
        } else if (((Vertex) this.getTarget()).getTime() < 180) {
            return Color.GREEN;
        } else {
            return Color.BLUE;
        }
//        return defaultColor;
    }

    /**
     * Method to get the edge color (red, black, or green), defined by value
     *
     * @param variables
     * @return
     */
    public Color getColor(Variables variables) {
        //TODO Refactor this method
        if (variables.isEdgeColorByMarkovIn) {
            if (!this.getAttributeValue(VariableNames.MarkovIn).equals(VariableNames.UnknownValue)) {
                return (Color) TrafficLight.splittedTrafficLight(Float.valueOf(this.getAttributeValue(VariableNames.MarkovIn)), 0, 1, false);
            } else {
                return defaultColor;
            }
        } else if (variables.isEdgeColorByMarkovOut) {
            if (!this.getAttributeValue(VariableNames.MarkovOut).equals(VariableNames.UnknownValue)) {
                return (Color) TrafficLight.splittedTrafficLight(Float.valueOf(this.getAttributeValue(VariableNames.MarkovOut)), 0, 1, false);
            } else {
                return defaultColor;
            }
        } else if (variables.isEdgeColorByGraphs) {
            String[] graphs = getAttributeValues(VariableNames.GraphFile);
            return TrafficLight.getGrayscaleColor(graphs.length, variables.numberOfGraphs);
        } else if (variables.isEdgeColorByValue) {
            float v = getValue();
            if (v == 0) {
                return defaultColor;
            } else {
                EdgeType et = null;
                if (variables.config.edgeTypes.containsKey(this.getLabel())) {
                    et = variables.config.edgeTypes.get(this.getLabel());
                } else if (variables.config.edgeTypes.containsKey(this.getType())) {
                    et = variables.config.edgeTypes.get(this.getType());
                }
                double min = et.min;
                double max = et.max;
                min = Math.min(min, 510);
                max = Math.max(max, 0);
                return (Color) TrafficLight.splittedTrafficLight(v, min, max, et.isInverted);
            }
        } else { // By Type
            if (variables.config.edgeTypes.containsKey(this.getType())) {
                return variables.config.edgeTypes.get(this.getType()).edgeColor;
            }
        }
        return defaultColor;
    }

    /**
     * Method used during the collapse of edges. This method defines if the
     * collapsed edge influence value will be the sum of the edges values or the
     * average
     *
     * @param variables
     * @return (boolean) Return true if influence from collapsed edges are
     * added. Return false if average
     */
    public boolean addInfluence(Variables variables) {
        EdgeType et = null;
        if (variables.config.edgeTypes.containsKey(this.getLabel())) {
            et = variables.config.edgeTypes.get(this.getLabel());
        } else if (variables.config.edgeTypes.containsKey(this.getType())) {
            et = variables.config.edgeTypes.get(this.getType());
        }
        if (et != null) {
            if (et.collapse.equalsIgnoreCase("AVERAGE")) {
                return false;
            }
        }

//        for (EdgeType edgetype : variables.config.edgetype) {
//            if (this.getType().contains(edgetype.type)) {
//                if (edgetype.collapse.equalsIgnoreCase("AVERAGE")) {
//                    return false;
//                }
//            }
//        }
        return true;
    }

    /**
     * Function to merge two edges
     *
     * @param edge is the edge that we want to merge with
     * @return the updated edge after merging with "edge"
     */
    public Edge merge(Edge edge, String mergeCode) {
        if (this.getType().equalsIgnoreCase(edge.getType())) {
            this.updateAllAttributes(edge.getAttributes());
            // We have a problem here when merging many graphs: StringBuilder.append(StringBuilder.java:132) java.lang.OutOfMemoryError: Java heap space
//            this.id = this.id + ", " + edge.id; 
            this.id = this.id + " ";
            if (!this.getLabel().contains(edge.getLabel())) {
                this.setLabel(this.getLabel() + ", " + edge.getLabel());
            }
            edge.setHide(true);
            GraphAttribute merged = new GraphAttribute(VariableNames.MergedEdgeAttribute, mergeCode, "");
            this.addAttribute(merged);
            GraphAttribute hide = new GraphAttribute(VariableNames.edgeHiddenAttribute, "false", "");
            this.addAttribute(hide);
        }
        return this;
    }

//    /**
//     * Method that calculates the edge frequency and returns a human-readable value (String)
//     * @param nGraphs is the total number of existing graphs used during the merge
//     * @return the edge's frequency between 0% and 100%
//     */
//    public String getEdgeFrequency(float nGraphs) {
//        if(this.hasAttribute(VariableNames.GraphFile)) {
//            float frequency = ((float) this.getAttributeValues(VariableNames.GraphFile).length / (float)nGraphs) * 100;
//            return String.format("%.02f", frequency) + "%";
//        } else
//            return "Frequency Unavailable";
//    }
//    
//    /**
//     * Method that calculates the edge frequency and returns the probability between 0 and 1
//     * @param nGraphs is the total number of existing graphs used during the merge
//     * @return the edge's frequency
//     */
//    public float getEdgeFrequencyValue(float nGraphs) {
//        if(this.hasAttribute(VariableNames.GraphFile)) {
//            return ((float) this.getAttributeValues(VariableNames.GraphFile).length / (float)nGraphs);
//        } else
//            return 0;
//    }
} public class EdgeStroke {

    /**
     * Defines neutral edges stroke
     *
     * @param edge The edge to be analyzed
     * @param isStrokeByValue define if we want to use the stroke size based on
     * the edge's value (TRUE) or by the #graphs it belongs (FALSE)
     * @param nGraphs is the number of graphFiles
     * @return Stroke (neutral edges are dashed, others are not)
     */
    public static Stroke EdgeStroke(Edge edge, boolean isStrokeByValue, float nGraphs) {
//        float[] dash = {5.0f};
//
//        if (edge.isNeutral()) {
//            return new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
//        }
        float size = 1;
//        if (!isStrokeByValue) {
////            Vertex target = (Vertex) edge.getTarget();
//            String[] graphs = edge.getAttributeValues(VariableNames.GraphFile);
//            size = 1 + (((float) graphs.length - 1) / (nGraphs - 1)) * 10;
//        }
        return new BasicStroke(size, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f);
    }

    /**
     * Allows to manually define (any) edge's stroke
     *
     * @deprecated
     * @param edge Edge to have the stroke changed
     * @param dash The array representing the dashing pattern
     * @return Stroke
     */
    public static Stroke EdgeStroke(Edge edge, float[] dash) {
        return new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
    }

    /**
     * Method to define edge's width according to its value
     *
     * @param value Edge's value
     * @param average Edge's type average value
     * @return (Stroke) Edge's width proportional to all edges of the same type.
     */
    public static BasicStroke defineStroke(float value, float average) {
        float size;
        size = (float) (5 * (Math.abs(value) / average)) + 1;
        size = Math.min(6, size);
        if (Float.isNaN(size)) {
            size = 1;
        }
        return new BasicStroke(size, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f);
    }

    /**
     * Method to define edge stroke by type
     *
     * @param edge Edge
     * @param variables Variables type
     * @return Stroke
     */
    public static Stroke StrokeByType(Edge edge, Variables variables) {
        if (variables.isEdgeStrokeByValue) {
            if (variables.config.edgeTypes.containsKey(edge.getLabel())) {
                EdgeType et = variables.config.edgeTypes.get(edge.getLabel());
                if (et.stroke.equalsIgnoreCase("MAX")) {
                    float maxAbs = Math.max(Math.abs(et.max), Math.abs(et.min));
                    return defineStroke(edge.getValue(), maxAbs);
                } else {
                    return defineStroke(edge.getValue(), et.total / et.count);
                }
            }
        }
        else if (variables.isEdgeStrokeByGraph) {
            String[] graphs = edge.getAttributeValues(VariableNames.GraphFile);
            return defineStroke(((float) graphs.length - 1), variables.numberOfGraphs);
//            float size = 1 + (((float) graphs.length - 1) / (variables.numberOfGraphs - 1)) * 10;
//            return new BasicStroke(size, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f);
        } else if (variables.isEdgeStrokeByMarkovIn) {
            if (!(edge.getTarget() instanceof AgentVertex) && !edge.getAttributeValue(VariableNames.MarkovIn).equals(VariableNames.UnknownValue))
                return defineStroke(Float.valueOf(edge.getAttributeValue(VariableNames.MarkovIn)), 1);
            else
                return defineStroke(0, 1);
        } else if (variables.isEdgeStrokeByMarkovOut) {
            if (!(edge.getTarget() instanceof AgentVertex) && !edge.getAttributeValue(VariableNames.MarkovIn).equals(VariableNames.UnknownValue))
                return defineStroke(Float.valueOf(edge.getAttributeValue(VariableNames.MarkovOut)), 1);
            else
                return defineStroke(0, 1);
        }
        return EdgeStroke(edge, variables.isEdgeStrokeByValue, variables.numberOfGraphs);
    }
} public class VertexStroke {

    /**
     * Method to compute Vertex Stroke
     *
     * @param v JUNG's V (Vertex) type
     * @param dash The array representing the dashing pattern
     * @param view VisualizationViewer<Object, Edge>
     * @param layout Layout<Object, Edge>
     * @return Stroke
     */
    public static Stroke VertexStroke(Object v, float[] dash, VisualizationViewer<Object, Edge> view, Layout<Object, Edge> layout) {
        PickedState<Object> picked_state = view.getPickedVertexState();
        if (picked_state.isPicked(v)) {
            return new BasicStroke(7.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
        } else {
            for (Object w : layout.getGraph().getNeighbors(v)) {
                if (picked_state.isPicked(w)) {
                    return new BasicStroke(4.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
                }
            }
//            float value = ((Vertex)v).getAttributeValueFloat("Cluster");
//            if(value != value)
                return new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
//            else
//                return new BasicStroke(5.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
        }
    }

    /**
     * Compute Vertex Stroke. Uses SDM terms (idle, promoted, fired, training,
     * hired)
     *
     * @param v
     * @param view
     * @param layout
     * @param variables
     * @return
     */
    public static Stroke VertexStroke(Object v, VisualizationViewer<Object, Edge> view, Layout<Object, Edge> layout, Variables variables) {
        float[] dash = null;
//        if (v instanceof Graph) {
//            return new BasicStroke(0);
//        }

        if (v instanceof Vertex) {
            if (!variables.config.vertexStrokevariables.isEmpty()) {
                for (String vertexStrokevariable : variables.config.vertexStrokevariables) {
                    String[] list = vertexStrokevariable.split(" ");
                    String att = ((Vertex) v).getAttributeValue(list[0]);
                    if (!"".equals(att)) {
                        for (int j = 1; j < list.length; j++) {
                            if (att.equalsIgnoreCase(list[j])) {
                                dash = new float[1];
                                dash[0] = 4.0f;
                            }
                        }
                    }
                }
            }
        }
        return VertexStroke(v, dash, view, layout);
    }
} public class VariableNames {
    public static String CollapsedVertexAgentAttribute = "Agents";
    public static String CollapsedVertexActivityAttribute = "Activities";
    public static String CollapsedVertexEntityAttribute = "Entities";
    public static String GraphFile = "GraphFile";
    public static String FilterAllEdges = "All Edges";
    public static String ChronologicalEdge = "Chronological";
    public static String FilterAllVertices = "(All Vertices)";
    public static String MouseModePicking = "Picking";
    public static String MouseModeTransforming = "Transforming";
    public static String EdgeModeQuadCurve = "QuadCurve";
    public static String EdgeModeLine = "Line";
    public static String FontConfiguration = "<html><font face=\"verdana\", size=\"6\", color=\"yellow\">";
    public static String SummarizedLabelString = " (Summarized)";
    public static String DefaultVertexPainter = "Prov";
    public static String EdgeTooltipFontConfiguration = "<html><font size=\"4\">";
    public static String UnknownValue = "Unknown";
    public static String time = "Timestamp";
    public static String edgeHiddenAttribute = "Hide";
    public static String vertexNewSource = "NewSource";
    public static String vertexNewTarget = "NewTarget";
    public static String similarityAttribute = "Similarity";
    
    public static String layout_timeline = "Timeline";
    public static String layout_circle = "Circle";
    public static String layout_hierarchy = "Hierarchy";
    public static String layout_provcircle2 = "ProvCircle2";
    public static String layout_list = "List";
    public static String layout_Two_Dimensional = "Two Dimensional";
    public static String layout_ISOM = "ISOM";
    public static String layout_provcircle = "ProvCircle";
    public static String layout_kk = "KK";
    public static String layout_spring = "Spring";
    public static String layout_temporal = "Temporal";
    public static String layout_fr2 = "FR2";
    public static String layout_dag = "DAG";
    public static String layout_One_Dimensional = "One Dimensional";
    public static String layout_One_Dimensional_2 = "One Dimensional (Variant)";
    public static String layout_timeline_graphs = "Timeline (Graphs)";
    public static String layout_spatial = "Spatial";
    public static String layout_fr = "FR";
    public static String layout_test = "Test";
    
    public static Color AgentColor = new Color(254, 211, 127);
    public static Color ActivityColor = new Color(159, 177, 252);
    public static Color EntityColor = new Color(255, 252, 135);
    public static String MergedEdgeAttribute = "Merged_Edge";
    public static String Frequency = "Frequency";
    public static String MarkovIn = "MarkovIn";
    public static String MarkovOut = "MarkovOut";
    public static String label = "Label";
    public static String MarkovLayout = "Markov Layout";
    public static String MarkovInLayout = "MarkovInLayout";
    public static String MarkovOutLayout = "MarkovOutLayout";
} public class Variables extends Object {

    public String mergingWithGraphPath;
    public String originalGraphPath = "demo";
    public Markov markov = new Markov();

//    public static String demo = File.separator + "Graph" + File.separator + "Merge_Test.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "AngryBots_3_Merge.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "Angry_Robots.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "Graph_to_Merge_01.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "Angry_Robots_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "Angry_Robots_paperCIG.xml";

    public static String demo = "Graph" + File.separator + "Car_Tutorial.xml";   
    public String configDemo = "Config" + File.separator + "Car_Tutorial_config.xml";
//    
//    public static String demo = File.separator + "Graph" + File.separator + "MorphWing2.xml";
//    public String configDemo = "Config" + File.separator + "Morph_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "rio_city_bus_example.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "bus_linha5.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "rio_de_janeiro_cidade_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "input.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "Test" + File.separator + "prov-dm-example1.prov-asn";
//    public static String demo = File.separator + "Graph" + File.separator + "test" + File.separator + "example-blog1.provn";
//    public static String demo = File.separator + "Graph" + File.separator + "workflow_trial_0.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "PROV_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "2D_Provenance.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "2D_Tower_Defense_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "map.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "graph_features_example.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "graph1.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "graph2.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "Gradient.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "map_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "program_prov_example.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "prog_example_config.xml";  
//    public static String demo = File.separator + "Graph" + File.separator + "Car_Tutorial.xml";
//    public String configDemo = File.separator + "Config" + File.separator + "Noise_config.xml";
//    public static String demo = File.separator + "Graph" + File.separator + "bash-count.xml"; 
//    public String configDemo = File.separator + "Config" + File.separator + "Reprozip_config.xml";
    public GuiBackground guiBackground = new GuiBackground();
    public VisualizationViewer<Object, Edge> view;
    public Layout<Object, Edge> layout;
    public GraphCollapser gCollapser;
    public DirectedGraph<Object, Edge> graph;
    public DirectedGraph<Object, Edge> collapsedGraph;
    public Config config = new Config();
    public boolean filterCredits = false;
    public boolean initConfig = false;
    public final Set exclusions = new HashSet();
    public boolean initLayout = true;
    public Filters filter = new Filters();
    public boolean prologIsInitialized = false;
    public File file;
    public DefaultModalGraphMouse mouse = new DefaultModalGraphMouse();
    public Collapser collapser = new Collapser();
    public boolean initialGraph = true;
    public SimilarityConfig mergeConfig;
    public SimilarityConfig similarityConfig;
    public boolean considerOnlyNeighborsSimilarityCollapse = true;

    public String selectedTimeScale = config.timeScale;
    public boolean doDerivate = false;
    public boolean removeDerivateOutliers = false;
    public boolean changedOutliersOption = false;

    public ImproveJUNGPerformance jungPerformance = new ImproveJUNGPerformance();
    public String layout_attribute_X = "Timestamp";
    public String layout_attribute_Y = "Timestamp";
    public int numberOfGraphs = 1;
    public Collection<String> graphNames;
    public boolean highlightVertexOutliers = true;
    public boolean vertexBorderByGraphs = false;
    public boolean vertexBorderByLabel = false;
    public ThresholdValues outliersThresholds;
    public boolean allowVariableLayout = false;
    public int edgeAlpha = 25;

    public Stack<StackElementUndoDeletion> undoDeletion = new Stack();

    public boolean allowMergeUndo = false;
    public boolean isEdgeColorByMarkovIn = false;
    public boolean isEdgeColorByMarkovOut = false;
    public boolean isEdgeColorByGraphs = false;
    public boolean isEdgeColorByType = false;
    public boolean isEdgeColorByValue = true;
    
    public boolean isEdgeStrokeByValue = true;
    public boolean isEdgeStrokeByGraph = false;
    public boolean isEdgeStrokeByMarkovIn = false;
    public boolean isEdgeStrokeByMarkovOut = false;
    public boolean isEdgeStrokeDefault = false;

    /**
     * Method that updates the number of graphs that comprises the current graph
     * Should be called everytime after a graph is loaded: File Open / Merge
     * Graph
     */
    public void updateNumberOfGraphs() {
        graphNames = Utils.DetectAllPossibleValuesFromAttribute(graph.getVertices(), VariableNames.GraphFile);
        numberOfGraphs = graphNames.size();
        this.config.detectGraphVisualizationModes(this.graphNames);
        this.config.addGraphFileVertexFilter(this.graphNames);
    }

    /**
     * Return the max value between 2 values
     *
     * @param type (String) Edge type to be analyzed
     * @param value (Float) Current max value
     * @param edge (Edge) Second value is extracted from this edge, if the
     * influence type is the same
     * @return max value between value and edge.getValue
     */
    public float CompareMax(float value, Edge edge) {
        value = Math.max(value, edge.getValue());
        return value;
    }

    /**
     * Return the min value between 2 values
     *
     * @param type (String) Edge type to be analyzed
     * @param value (Float) Current max value
     * @param edge (Edge) Second value is extracted from this edge, if the
     * influence type is the same
     * @return max value between value and edge.getValue
     */
    public float CompareMin(float value, Edge edge) {
        value = Math.min(value, edge.getValue());
        return value;
    }

    /**
     * Return the added value between 2 values
     *
     * @param type (String) Edge type to be analyzed
     * @param value (Float[]) Current value
     * @param edge (Edge) Second value is extracted from this edge, if the
     * influence type is the same
     * @return added value between value and edge.getValue
     */
    public float Add(float value, Edge edge) {
        value += Math.abs(edge.getValue());
        return value;
    }

    /**
     * Return the average value
     *
     * @param value (Float[]) Current value and counter
     * @param average (Boolean) To compute the average or not
     * @return average value between value and edge.getValue
     */
    public float[] Average(float[] value, String average) {
        if (average.equalsIgnoreCase("MEAN")) {
            value[0] = value[0] / (value[1] - 1);
        }
        return value;
    }

    /**
     * Return the new value
     *
     * @param etype
     * @param type (String) Edge type to be analyzed
     * @param edge (Edge) Second value is extracted from this edge, if the
     * influence type is the same
     * @param max (Boolean) To compute the max between values or to add values
     * @return value, according to the operation, between value and
     * edge.getValue
     */
    public EdgeType ComputeValue(EdgeType etype, Edge edge) {
        etype.max = CompareMax(etype.max, edge);
        etype.min = CompareMin(etype.min, edge);
        etype.count++;
        etype.total = Add(etype.total, edge);
        return etype;
    }

    /**
     * Find max values for each edge type. Used for edge width.
     *
     */
    public void ComputeEdgeTypeValues() {
        Collection<Edge> edges = this.graph.getEdges();
        for (Edge edge : edges) {
            if (this.config.edgeTypes.containsKey(edge.getLabel())) {
                EdgeType et = this.config.edgeTypes.get(edge.getLabel());
                ComputeValue(et, edge);
            } else if (this.config.edgeTypes.containsKey(edge.getType())) {
                EdgeType et = this.config.edgeTypes.get(edge.getType());
                ComputeValue(et, edge);
            }
        }
        GraphFrame.edgeFilterList.setSelectedIndex(0);
    }

    public void newLayout(String layout) {
        DirectedGraph<Object, Edge> displayGraph;
        if (this.layout != null && this.allowVariableLayout) {
            displayGraph = (DirectedGraph<Object, Edge>) this.layout.getGraph();
        } else {
            displayGraph = this.graph;
        }
        if (layout.equalsIgnoreCase(VariableNames.layout_circle)) {
            this.layout = new CircleLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_fr)) {
            this.layout = new FRLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_fr2)) {
            this.layout = new FRLayout2<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_ISOM)) {
            this.layout = new ISOMLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_kk)) {
            this.layout = new KKLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_spring)) {
            this.layout = new SpringLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_dag)) {
            this.layout = new DAGLayout<>(displayGraph);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_temporal)) {
            this.layout = new Temporal_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_spatial)) {
            this.layout = new Spatial_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_timeline)) {
            this.layout = new Timeline_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_timeline_graphs)) {
            this.layout = new TimelineGraphs_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_One_Dimensional)) {
            this.layout = new OneDimensional_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_One_Dimensional_2)) {
            this.layout = new OneDimensional_Layout_Ordered<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_Two_Dimensional)) {
            this.layout = new TwoDimensional_Layout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_provcircle)) {
            this.layout = new ProvCircleLayout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_provcircle2)) {
            this.layout = new ProvCircleLayout2<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_list)) {
            this.layout = new ListLayout<>(displayGraph, this);
        } else if (layout.equalsIgnoreCase(VariableNames.layout_hierarchy)) {
            this.layout = new Hierarchy_Layout_2<>(displayGraph, this);
        } else {
            this.layout = new Timeline_Layout<>(displayGraph, this);
        }
    }

    public void initGraphCollapser() {
        gCollapser = new GraphCollapser(this.graph, this.config.considerEdgeLabelForMerge);
    }

    /**
     * Method to set the parameters for Highlighting the outliers during a
     * display mode The actual highlighting happnes at GuiFunctions Stroke
     * method
     *
     * @param attribute is the attribute that we are using to display the values
     */
    public void highlightOutliers(String attribute) {
        if (this.highlightVertexOutliers) {
            ArrayList<Double> values = GraphUtils.getAttributeValuesFromVertices(this.graph, attribute);
            if (!values.isEmpty() && values.size() > 5) {
                this.outliersThresholds = Utils.calculateOutliers(values, attribute);
            } else {
                this.outliersThresholds = new ThresholdValues("", 0, 0);
                this.highlightVertexOutliers = false;
            }
        }
    }
} public class AgentVertex extends Vertex {

    /**
     * Constructor
     *
     * @param id This param is used by JUNG for collapsed vertices and tooltips.
     */
    public AgentVertex(String id) {
        super(id, "","");
    }
    
    public AgentVertex(String id, String label, String date) {
        super(id, label, date);
    }

    /**
     * Constructor overload
     *
     * @param array for TSV Reader
     */
    public AgentVertex(String[] array) {
        super(array[0], array[1], array[2]);
    }

    @Override
    public Paint getColor() {
        return VariableNames.AgentColor;
    }

    @Override
    public String getNodeType() {
        return "Agent";
    }

//    @Override
//    public String getDayName() {
//        return " ";
//    }
//
//    @Override
//    public float getTime() {
//        return 0;
//    }

} public class VertexPainter {
    
    /**
     *
     * @param mode
     * @param view
     * @param variables
     */
    public static void VertexPainter(final String mode, VisualizationViewer<Object, Edge> view, final Variables variables) {
//        variables.highlightVertexOutliers = true;
        variables.highlightOutliers(mode);
        if (mode.equals(VariableNames.MarkovLayout)) {
            variables.markov.computeMarkovInChainFromOrigin(variables);
        }
        Transformer vertexPainter = new Transformer<Object, Paint>() {
            @Override
            public Paint transform(Object v) {
//                ColorScheme vm = variables.config.vertexModes.get(mode);
                return variables.config.vertexModes.get(mode).Execute(v, variables);
            }
        };
        view.getRenderContext().setVertexFillPaintTransformer(vertexPainter);
    }
} public class EntityVertex extends Vertex {

    /**
     * Constructor
     *
     * @param id This param is used by JUNG for collapsed vertices and tooltips.
     */
    public EntityVertex(String id) {
        super(id,"","");
    }
    
    public EntityVertex(String id, String label, String time) {
        super(id, label, time);
    }

    public EntityVertex(String[] array) {
        super(array[0], array[1], array[2]);
    }

    @Override
    public Paint getColor() {
        return VariableNames.EntityColor;
    }

    @Override
    public String getNodeType() {
        return "Entity";
    }
} public abstract class Vertex extends GraphObject {

    private String id;                              // prov:id
    private double normalizedTime;
//    private String time;                            // prov:startTime
                                                    // Refactor for datetime type
    private String timeFormat;
    private String timeScale;
    private String timeLabel = "Timestamp";
    
    /**
     * Constructor without attributes
     * Using this constructor, attributes must be added later
     * 
     * @param id vertex unique ID
     * @param label HUman readable name
     * @param time Time-related value. Used for temporal layouts
     */
    public Vertex(String id, String label, String time) {
        this.id = id;
//        this.time = time;
        GraphAttribute t = new GraphAttribute(timeLabel, time, "");
        this.attributes = new HashMap<>();
        this.attributes.put(t.getName(), t);
        setLabel(label);
        timeFormat = "nanoseconds";
        timeScale = "nanoseconds";
    }
    
    /**
     * Constructor with attributes
     * @param id
     * @param label
     * @param time
     * @param attributes 
     */
    public Vertex(String id, String label, String time, Map<String, GraphAttribute> attributes) {
        this.id = id;
        this.attributes = new HashMap<>();
        this.attributes.putAll(attributes);
        GraphAttribute t = new GraphAttribute(timeLabel, time, "");
        this.attributes.put(t.getName(), t);
        setLabel(label);
        timeFormat = "nanoseconds";
        timeScale = "nanoseconds";
    }

    /**
     * Return the vertex ID
     *
     * @return (String) id
     */
    public String getID() {
        return id;
    }
    
    /**
     * Set vertex ID
     * @param t is the new ID
     */
    public void setID(String t) {
        id = t;
    }
    
    public void setNormalizedTime(double t) {
        this.normalizedTime = t;
    }
    
    public double getNormalizedTime() {
        return this.normalizedTime;
    }
    
    /**
     * Method for returning the vertex name (not type) from the sub-classes.
     * i.e. Agent Vertex name = Kohwalter
     *
     * @return (String) name
     */
    
    /**
     * Method for returning the vertex day (if any)
     *
     * @return (int) date
     */
    public double getTime() {    
//        String[] day = this.time.split(":");
        String time = this.attributes.get(timeLabel).getAverageValue();
        if(Utils.tryParseDouble(time))
            return (Double.parseDouble(time));
        else if(Utils.tryParseDate(time))
        {
            double milliseconds =  Utils.convertStringDateToLong(time);
            return milliseconds;
        }
        else
            return -1;
    }
    public long getMinTime() {    
        String time = this.attributes.get(timeLabel).getMin();
        if(Utils.tryParseDouble(time))
            return (long) ((Double.parseDouble(time))*1000);
        else if(Utils.tryParseDate(time))
        {
            long milliseconds = Utils.convertStringDateToLong(time);
            return milliseconds;
        }
        else
            return -1;
    }
    
    /**
     * Method to get the value of the variable time 
     * @return time
     */
    public String getTimeString() {    
        return this.attributes.get(timeLabel).getAverageValue();
    }
    
    /**
     * Method to set the value of the variable time
     * @param t is the new value
     */
    public void setTime(String t){
        GraphAttribute time = new GraphAttribute(timeLabel, t, "Normalized");
        this.attributes.put(timeLabel, time);
    }

    
    /**
     * (Optional) Method for returning the day of the week instead of the day's
     * number.
     *
     * @return (String) the day of the week (mon, tue, wed, ...)
     */
    public String getDayName() {
        String[] day = this.attributes.get(timeLabel).getAverageValue().split(":");
        return day[1];
    }
    
    /**
     * This overrides the default JUNG method for displaying information
     *
     * @return (String) id
     */
    @Override
    public String toString() {
        return this.getNodeType() + "<br> "
                + "<br>ID: " + this.id + "<br>"
                + "<b>Label: " + getLabel() + "</b>"
                + " <br>" + printTime()
                + " <br>"
                + " <br>" + printAttributes();
    }
    
    public String getTooltip(int nGraphs) {
        return this.getNodeType() + "<br> "
                + "<br>ID: " + this.id + "<br>"
                + "<b>Label: " + getLabel() + "</b>"
                + " <br>" + printTime()
                + "<br>Frequency: " + getFrequency(nGraphs)
                + " <br>"
                + " <br>" + printAttributes();
    }
    public String printTime()
    {
        double nt = this.getTime();
        return "Timestamp: " + Utils.convertTime(timeFormat, nt, timeScale) + " (" + timeScale + ")";
    }
    
    public void setTimeScalePrint(String timeFormat, String timeScale) {
        this.timeFormat = timeFormat;
        this.timeScale = timeScale;
    }

    /**
     * Method to return the attribute value (not necessarily a number)
     * If the attribute does not exist, returns "Unknown"
     * @param attribute
     * @return 
     */
    @Override
    public String getAttributeValue(String attribute) {
        if(attribute.equalsIgnoreCase("Label")) {
            return getLabel();
        }
        GraphAttribute aux = attributes.get(attribute);
        if(aux != null) {
            return aux.getAverageValue();
        }
        else {
            return deltaAttributeValue(attribute);
//            return "Unknown";
        }
    }
    
    /**
     * Method to return the values in an attribute that were separated by a comma
     * @param attribute is the attribute that we want to get the values
     * @return an array with the values
     */
    @Override
    public String[] getAttributeValues(String attribute) {
        String values = this.getAttributeValue(attribute);
        return values.split(", ");
    }
    
    /**
     * Method to return the attribute value as float
     * @param attribute
     * @return 
     */
    public double getAttributeValueDouble(String attribute) {
        if(isItTime(attribute)) {
            return (double)getNormalizedTime();
        }
        if(attributes.get(attribute) == null) {
            return deltaAttributeDoubleValue(attribute);
        }
        return getAttDoubleValue(attribute);
    }
    
    
    
    /**
     * Method that gets both attributes in the string and subtracts them. I.e.: First_Attribute - Second_Attribute
     * @param attribute must be a string with both atributes separared by " - ". Example: "First_Attribute - Second_Attribute"
     * @return the delta
     */
    private double deltaAttributeDoubleValue(String attribute) {
        String[] atts = attribute.split(" - ");
        if(atts.length == 2)
            return getAttDoubleValue(atts[0]) - getAttDoubleValue(atts[1]);
        else
            return Double.NaN;
    }
    
    /**
     * Method that returns the delta from the two attributes or Unknown if any attribute is invalid
     * @param attribute must be a string with both atributes separared by " - ". Example: "First_Attribute - Second_Attribute"
     * @return the delta as a String
     */
    private String deltaAttributeValue(String attribute) {
        if(attribute.equalsIgnoreCase("Label")) {
            return getLabel();
        }
        if(isItTime(attribute)) {
            return String.valueOf(getTime());
        }
        
        String[] atts = attribute.split(" - ");
        if(atts.length == 2) {
            if(VariableNames.UnknownValue.equals(getAttributeValue(atts[0])) || VariableNames.UnknownValue.equals(getAttributeValue(atts[1]))) return VariableNames.UnknownValue;
            else {
                String delta = Double.toString(getAttDoubleValue(atts[0]) - getAttDoubleValue(atts[1]));
                return delta;
            }
        }
        else
            return VariableNames.UnknownValue;
    }
    
    /**
     * Method that returns the float value of the attribute. If it is not convertable, then it returns Float.NaN
     * @param attribute the attribute that we want to get the value from
     * @return the float value or Float.NaN if it is not possible to convert to float
     */
    private double getAttDoubleValue(String attribute) {
        if(isItTime(attribute)) {
            return (double)getNormalizedTime();
        }
        if(attributes.get(attribute) != null) {
            if(Utils.tryParseDouble(attributes.get(attribute).getAverageValue())) {
                return Utils.convertStringToDouble(attributes.get(attribute).getAverageValue());
            }
            else {
                
                return Double.NaN;
            }
        }
        else {
            return Double.NaN;
        }
    }
    

    /**
     * Method for getting the vertex border size
     *
     * @deprecated use VertexStroke class instead
     * @param width Define the border width
     * @return (Stroke) returns the new vertex border width
     */
    public Stroke getStroke(float width) {
        float dash[] = null;
        final Stroke nodeStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
        return nodeStroke;
    }

    /**
     * Method for defining the vertex color
     *
     * @return (Paint) vertex color
     */
    public abstract Paint getColor();

    /**
     * Method used to identify the vertex type
     *
     * 
     * @return (String) vertex type
     */
    public abstract String getNodeType();

} public class VertexShape<V> extends EllipseVertexShapeTransformer<V> {

    int defaultSize = 15;
    String attribute = "Timestamp";
    String selectedShape = "Prov";
    int max;
    Variables variables;

    public VertexShape(int vertexSize, Variables v) {
        defaultSize = vertexSize;
        setSizeTransformer(new VertexSize<V>(vertexSize));
        variables = v;
    }
    
    public VertexShape(int vertexSize, String selectedMode, String att, Collection<Object> vertices, Variables v) {
        defaultSize = vertexSize;
        selectedShape = selectedMode;
        setSizeTransformer(new VertexSize<V>(vertexSize));
        attribute = att;
        max = (int) Utils.findMaximumAttributeValue(vertices, attribute);
        variables = v;
    }

    /**
     * Create the vertex shape using VertexShapeFactory factory;
     *
     * @param v JUNG's V (Vertex) type
     * @return Shape
     */
    @Override
    public Shape transform(V v) {
        switch (selectedShape) {
            case "Prov":
                return defaultShape(v);
            case "Summarized":
                return summarizedShape(v);
            case "Graphs":
                return multipleGraphShape(v);
            case "Attribute":
                return attributeValueShape(v);
            default:
                return defaultShape(v);
        }
    }

    /**
     * Method that defines the default vertex shape
     *
     * @param v is the vertex
     * @return the vertex shape
     */
    private Shape summarizedShape(V v) {
        if (v instanceof GraphVertex) {
            vertexGraphSizeTransformer(v);
        } else {
            setSizeTransformer(new VertexSize<V>(defaultSize));
        }
        return provShape(v, defaultSize);
    }
    
    private Shape defaultShape(V v) {
        setSizeTransformer(new VertexSize<V>(defaultSize));
        return provShape(v, defaultSize);
    }

    /**
     * Method that defines the prov shapes for the vertices
     *
     * @param v is the vertex
     * @param size is the size that we want for the vertex
     * @return the vertex shape
     */
    private Shape provShape(V v, int size) {
        if (v instanceof AgentVertex || ((Vertex)v).hasAttribute(VariableNames.CollapsedVertexAgentAttribute)) {
            return factory.getRegularPolygon(v, 5);
        } else if (v instanceof ActivityVertex || ((Vertex)v).hasAttribute(VariableNames.CollapsedVertexActivityAttribute)) {{//activity vertex
            return factory.getRoundRectangle(v);
        }
        } else if (v instanceof EntityVertex || ((Vertex)v).hasAttribute(VariableNames.CollapsedVertexEntityAttribute)) {
            return new Ellipse2D.Float(-7, -7, size, size);
        } else // Unknown
            return factory.getRegularPolygon(v, 3);
    }

    /**
     * Default Method for setting the vertex-graph size transformer
     *
     * @param v is the graph vertex
     */
    private void vertexGraphSizeTransformer(V v) {
        float graphSize = GraphUtils.getCollapsedVertexSize(v);
        float maxVertices = variables.graph.getVertexCount() * 0.25f;
        graphSize = graphSize / maxVertices;
        int size = (int) (defaultSize * (1.1 + 3 * graphSize));
        setSizeTransformer(new VertexSize<V>(size));
    }

    /**
     * Method to define the vertex size based on the number of graphs it belongs
     *
     * @param v is the vertex
     * @return the vertex shape
     */
    private Shape multipleGraphShape(V v) {
        int numberOfGraphs;
        int vertexSize;
        String[] graphs = ((Vertex) v).getAttributeValues(VariableNames.GraphFile);
        numberOfGraphs = graphs.length;
        vertexSize = (int) (defaultSize * (1 + 1.5 * (numberOfGraphs - 1) / variables.numberOfGraphs));
        setSizeTransformer(new VertexSize<V>(vertexSize));
        return provShape(v, vertexSize);
    }
    
    private Shape attributeValueShape(V v) {

        double value;
        int vertexSize;
        value = ((Vertex)v).getAttributeValueDouble(attribute);
        vertexSize = (int) (defaultSize * 2 * value / max);
        setSizeTransformer(new VertexSize<V>(vertexSize));
        return provShape(v, vertexSize);
    }
} public class AttValueColor {
    public String name;
    public String value;
    public Paint color;
} public class EdgeSourceTarget {
    public Edge edge;
    public Object source;
    public Object target;
    
} public class StackElementUndoDeletion {
    public Collection<Object> vertices;
    public Collection<EdgeSourceTarget> edges;
    public Collection<EdgeSourceTarget> insertedEdges;
    
    public StackElementUndoDeletion() {
        vertices = new ArrayList<>();
        edges = new ArrayList<>();
        insertedEdges = new ArrayList<>();
    }
} public class Utils {

    Color[] colors = new Color[17];

    public static Color getColor(int i) {
        int size = 25; // Array max position + 1
        int index = i % size;
        Color[] colors = new Color[size];
        colors[0] = new Color(0, 0, 0); // Black
        colors[1] = new Color(0, 0, 255); // Blue
        colors[2] = new Color(153, 153, 0); // Yellow-Mostard
        colors[3] = new Color(0, 255, 255); // Cyan/Aqua
        colors[4] = new Color(255, 0, 255); // Magenta
        colors[5] = new Color(128, 128, 128); // Gray
        colors[6] = new Color(128, 0, 0); // Brown / Maroon
        colors[7] = new Color(128, 128, 0); // olive
        colors[8] = new Color(210, 105, 30); // Chocolate
        colors[9] = new Color(0, 0, 128); // Navy
        colors[10] = new Color(255, 128, 128); // Pink
        colors[11] = new Color(255, 128, 0); // Orange
        colors[12] = new Color(192, 192, 192); // Silver / Light-Gray
        colors[13] = new Color(70, 130, 180); // Steel blue
        colors[14] = new Color(184, 134, 11); // Dark golden rod
        // Similar colors...
        colors[15] = new Color(0, 128, 128); // Teal
        colors[16] = new Color(0, 128, 0); // Green
        colors[17] = new Color(255, 0, 0); // Red
        colors[18] = new Color(0, 255, 0); // Lime/Green
        colors[19] = new Color(245, 245, 220); // Beige
        colors[20] = new Color(128, 0, 128); // Purple
        colors[21] = new Color(218,112,214); // Orchid
        colors[22] = new Color(210,105,30); // Chocolate
        colors[23] = new Color(240,255,255); // Azure
        colors[24] = new Color(255,20,147); // Deep-Pink

        return colors[index];
    }

    /**
     * Method to check if it is possible to parse the value to double
     *
     * @param value desired to be parsed to double
     * @return boolean
     */
    public static boolean tryParseDouble(String value) {
        value = value.replace(" ", "");
        value = value.replace(",", ".");
        if (value.isEmpty()) {
            return false;
        } else if (value == null) {
            return false;
        } else if (value.equalsIgnoreCase("")) {
            return false;
        } else if (value.equalsIgnoreCase("NaN")) {
            return false;
        } else {
            try {
                String v = new BigDecimal(value).toPlainString();
                Double.parseDouble(v);
                return true;
            } catch (NumberFormatException nfe) {
                return false;
            }
        }

    }

    /**
     * Function that verifies if it is possible to convert the String to a double
     *
     * @param value is the String to be verified
     * @return true if it is possible to convert to a double
     */
    public static double convertStringToDouble(String value) {
        value = value.replace(" ", "");
        value = value.replace(",", ".");
        String v;
        if(value.contains("Infinity")) {
            return Double.POSITIVE_INFINITY;
        } else {
            v = new BigDecimal(value.trim()).toPlainString();
            return Double.valueOf(v);
        }
        
        
    }

    /**
     * Function that verifies if it is possible to convert the String to a double
     *
     * @param value is the String to be verified
     * @return true if it is possible to convert to a double
     */
    public static double convertDouble(String value) {
        value = value.replace(" ", "");
        return Double.parseDouble(value);
    }

    /**
     * Function that verifies if it is possible to convert the String to an
     * integer
     *
     * @param value is the String to be verified
     * @return true if it is possible to convert to an integer
     */
    public static boolean tryParseInt(String value) {
        value = value.replace(" ", "");
        try {
            Integer.valueOf(value);
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }

    /**
     * Function to convert a String to an Int value
     *
     * @param value is the String to be converted
     * @return the int value of the string
     */
    public static int convertInt(String value) {
        value = value.replace(" ", "");
        return Integer.valueOf(value);
    }

    /**
     * Function to convert a String to an Int value, rounding it
     *
     * @param value is the String to be converted
     * @return the rounded int value of the string
     */
    public static double roundToInt(String value) {
        value = value.replace(" ", "");
        return Math.round(convertStringToDouble(value));
    }

    /**
     * Function to compare two doubles with an epsilon margin of error
     *
     * @param f1 is the first double
     * @param f2 is the second double
     * @param epsilon is the margin of error for the comparison
     * @return true if doubles are equals within the epsilon error margin
     */
//    public static boolean DoubleEqualTo(double f1, double f2, double epsilon) {
//        return Math.abs(f1 - f2) <= epsilon;
//    }

    public static boolean DoubleEqualTo(double f1, double f2, double epsilon) {
        return Math.abs(f1 - f2) <= epsilon;
    }

    public static boolean DoubleSimilar(double f1, double f2, double epsilon) {
        double max = Math.max(f1, f2);
        double min = Math.min(f1, f2);

        return min + Math.abs(max * epsilon) >= max;
    }
    
    public static boolean DoubleLesserThan(double f1, double f2, double epsilon) {
        if (Double.compare(f1, f2) < 0)
            return true;
        else
            return false;
    }

    /**
     * Clamp function to clamp the value between min and max
     *
     * @param min is the minimal accepted value
     * @param max is the maximum accepted value
     * @param value is the value to be clamped between min and max
     * @return clamped value
     */
    public static double clamp(double min, double max, double value) {
        return Math.max(min, Math.min(max, value));
    }

    /**
     * Method to convert a string to XMLGregorianCalendar
     *
     * @param time is the desired date in the "yyyy-MM-dd'T'HH:mm:ss"
     * @return the converted XMLGregorian Date
     */
    public static XMLGregorianCalendar stringToXMLGregorianCalendar(String time) {
        try {
            XMLGregorianCalendar result;
            Date date;
            SimpleDateFormat simpleDateFormat;
            GregorianCalendar gregorianCalendar;

            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            date = simpleDateFormat.parse(time);
            gregorianCalendar
                    = (GregorianCalendar) GregorianCalendar.getInstance();
            gregorianCalendar.setTime(date);
            result = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
//            System.out.println("Date: " + result.toString());
            return result;
        } catch (ParseException ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
        } catch (DatatypeConfigurationException ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * Function that verifies if it is possible to convert the String to Date
     *
     * @param value is the String to be verified
     * @return true if it is possible to convert to Date
     */
    public static boolean tryParseDate(String value) {
//        System.out.println("tryParseDate: " + value);
        if (value.isEmpty()) {
            return false;
        } else if (value == null) {
            return false;
        } else if (value.equalsIgnoreCase("")) {
            return false;
        } else if (value.equalsIgnoreCase("NaN")) {
            return false;
        }
        try {
            SimpleDateFormat simpleDateFormat;
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            simpleDateFormat.parse(value);
            return true;
        } catch (ParseException ex) {
            return false;
        }
    }

    /**
     * Convert a Date in the format of a string to double
     *
     * @param d1 is the string to be converted
     * @return the value in Double for the date
     */
    public static long convertStringDateToLong(String d1) {
        // Try to convert to date format and return how many milliseconds have passed since January 1, 1970, 00:00:00 GMT
        try {
            Date date;
            SimpleDateFormat simpleDateFormat;
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            date = simpleDateFormat.parse(d1);
            return date.getTime();
        } catch (ParseException ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
        }
        return 0;
    }

    /**
     * Method to return the median of a numeric array
     *
     * @param l is the array
     * @param start
     * @param end
     * @return the median
     */
    public static String median(Object[] l, int start, int end) {
        if (end - start > 1) {
            int middle = (end - start) / 2; //l.length / 2;
            middle = middle + start;
            boolean isString = false;
            double[] v = new double[l.length];
            int i = 0;
            for (Object s : l) {
                if (tryParseDouble((String) s)) {
                    v[i++] = convertStringToDouble((String) s);
                } else {
                    isString = true;
                }

            }
            if (!isString) {
                Arrays.sort(v);
                if ((end - start) % 2 == 0) {
                    double left = v[middle - 1];
                    double right = v[middle];
                    return String.valueOf((left + right) / 2);
                } else {
                    return String.valueOf(v[middle]);
                }
            } else {
                Arrays.sort(l);
                return (String) l[middle];
            }
        } else {
            return (String) l[start];
        }
    }

    /**
     * Retrieve the quartile value from an array
     *
     * @param values THe array of data
     * @param lowerPercent The percent cut off. For the lower quartile use 25,
     * for the upper-quartile use 75
     * @return
     */
    public static String quartile(Object[] values, int lowerPercent) {

        if (values == null || values.length == 0) {
            throw new IllegalArgumentException("The data array either is null or does not contain any data.");
        }
        int start, end;
        if (lowerPercent == 1) {
            start = 0;
            end = values.length / 2;
        } else {
            if ((values.length % 2) == 0) {
                start = (values.length / 2);
            } else {
                start = (values.length / 2) + 1;
            }
            end = values.length;
        }

        return median(values, start, end);
    }

    /**
     * Method to remove the infinity numbers from an arrayList
     *
     * @param allNumbers is the arrayList of double
     * @return the same arrayList without the infinity numbers
     */
    public static ArrayList<Double> removeInfinity(ArrayList<Double> allNumbers) {
        ArrayList<Double> normalNumbers = new ArrayList<>();
        for (double number : allNumbers) {
            if ((number != Double.NEGATIVE_INFINITY) && (number != Double.POSITIVE_INFINITY)) {
                normalNumbers.add(number);
            }
        }
        return normalNumbers;
    }

    /**
     * Method to remove outliers from a list
     *
     * @param allNumbers is the arrayList of double
     * @param att
     * @return the same arrayList without the outliers
     */
    public static ArrayList<Double> removeOutLierAnalysis(ArrayList<Double> allNumbers, String att) {
        if (allNumbers.isEmpty()) {
            return null;
        }
        ArrayList<Double> normalNumbers = new ArrayList<>();

        ThresholdValues thresholds = calculateOutliers(allNumbers, att);

        for (double number : allNumbers) {
            if ((thresholds.lowerThreshold <= number) && (number <= thresholds.upperThreshold)) {
                normalNumbers.add(number);
            }
        }

        return normalNumbers;
    }
    
    /**
     * Method to calculate the upper and lower outliers thresholds from a sequence of numbers
     * @param allNumbers contains the numbers
     * @param att
     * @return the lower and upper thresholds for outlier detection
     */
    public static ThresholdValues calculateOutliers(ArrayList<Double> allNumbers, String att) {
        Collections.sort(allNumbers);
        double q1;
        double q3;
        if (allNumbers.size() % 2 == 0) {
            int position;
//            mean = (allNumbers.get(position) + allNumbers.get(position - 1)) * 0.5;
            position = (int) (allNumbers.size() * 0.25);
            q1 = (allNumbers.get(position) + allNumbers.get(position - 1)) * 0.5f;
            position = (int) (allNumbers.size() * 0.75);
            q3 = (allNumbers.get(position) + allNumbers.get(position - 1)) * 0.5f;
        } else {
            int position;
//            mean = allNumbers.get(position);
            position = (int) (allNumbers.size() * 0.25);
            q1 = allNumbers.get(position);
            position = (int) (allNumbers.size() * 0.75);
            q3 = allNumbers.get(position);
        }
        double iqr = q3 - q1;
        double lowerThreshold = q1 - iqr * 1.5f;
        double upperThreshold = q3 + iqr * 1.5f;
        return new ThresholdValues(att, lowerThreshold, upperThreshold);
    }

    /**
     * Method to compute the standard deviation
     *
     * @param list is the list of double values
     * @return the stdev
     */
    public static double stdev(Double[] list) {
        double mean;
        mean = mean(list);
        return stdev(list, mean);
    }

    /**
     * Method to compute the standard deviation when the mean is already known
     *
     * @param list is the list of double values
     * @param mean is the known mean of the list
     * @return the stdev
     */
    public static double stdev(Double[] list, double mean) {
        double num = 0.0f;
        double numi;
        double deno = 0.0f;

        for (Double list1 : list) {
            numi = (double) Math.pow(list1 - mean, 2);
            num += numi;
            deno = list.length - 1;
        }

        double stdevResult = (double) Math.sqrt(num / deno);

//        double v = (double) ((int) stdevResult * 10000) * 0.0001f;
        return stdevResult;
    }

    /**
     * Computes the standard deviation for the attribute
     *
     * @param vertices the list of vertices
     * @param attribute the attribute in which we want to calculate the standard
     * deviation
     * @return the standard deviation
     */
    public static double std(Collection<Object> vertices, String attribute) {
        ArrayList<Double> values = new ArrayList<>();
        for (Object v1 : vertices) {
            double val = ((Vertex) v1).getAttributeValueDouble(attribute);
                if (!(val != val)) {
                    values.add(val);
                }
            }
        Double[] doubleArray = new Double[values.size()];
        doubleArray = Utils.listToDoubleArray(values);
        return stdev(doubleArray);
    }

    /**
     * Method to compute the mean of a list
     *
     * @param list is the list
     * @return the mean of the list
     */
    public static double mean(Double[] list) {
        double mean;
        double sum = 0.0F;
        for (Double list1 : list) {
            sum += list1;
        }
        mean = sum / list.length;

        return mean;
    }

    /**
     * Method to find the minimum value of a list
     *
     * @param list is the list of values
     * @return the minimum value of the list
     */
    public static double minimumValue(Double[] list) {
        double min = Double.POSITIVE_INFINITY;
        for (Double list1 : list) {
            min = Math.min(min, list1);
        }

        return min;
    }

    /**
     * Method to find the maximum value of a list
     *
     * @param list is the list of values
     * @return the maximum value of the list
     */
    public static double maximumValue(Double[] list) {
        double max = Double.NEGATIVE_INFINITY;
        for (Double list1 : list) {
            max = Math.max(max, list1);
        }
        return max;
    }
    
    /**
     * Method to find the maximum value in the graph from an attribute
     * @param nodes is the list of vertices
     * @param attribute is the attribute that we want to find the maximum value
     * @return the maximum value of attribute
     */
    public static double findMaximumAttributeValue(Collection<Object> nodes, String attribute) {
        double max = Double.NEGATIVE_INFINITY;
        for (Object node : nodes) {
            if (!((Vertex) node).getAttributeValue(attribute).contentEquals("Unknown")) {
                max = Math.max(max, ((Vertex) node).getAttributeValueDouble(attribute));
            }
        }
        return max;
    }

    /**
     * Method to convert a list of objects to an arraylist of double
     *
     * @param values is the list of objects to be convertable
     * @return the arraylist of double
     */
    public static Double[] listToDoubleArray(ArrayList<Double> values) {
        Double[] doubleArray = new Double[values.size()];
        int i = 0;
        for (Double f : values) {
            doubleArray[i++] = (f != null ? f : Double.NaN);
        }
        return doubleArray;
    }

    /**
     * Method to clone a graph
     *
     * @param graph The graph to be cloned
     * @return a clone of the graph
     */
    public static DirectedGraph<Object, Edge> copyGraph(DirectedGraph<Object, Edge> graph) {
        DirectedGraph<Object, Edge> clone = new DirectedSparseMultigraph<>();
        for (Edge e : graph.getEdges()) {
            clone.addEdge(e, e.getSource(), e.getTarget());
        }

        return clone;
    }

    /**
     * Function to convert the timestamp to different time scales
     *
     * @param timeFormat is the original timestamp scale used in the graph XML
     * @param time is the time we want to convert
     * @param timeScale is the current timescale being used
     * @return the converted value
     */
    public static long convertTime(String timeFormat, double time, String timeScale) {
        TimeUnit t;
        if(time == -1)
            return (long) time;
        // Convert the number to a lower scale being used to avoid losing the decimals
        switch (timeFormat) {
            case "nanoseconds":
                t = TimeUnit.NANOSECONDS;
                break;
            case "microseconds":
                t = TimeUnit.MICROSECONDS;
                break;
            case "milliseconds":
                t = TimeUnit.MILLISECONDS;
                break;
            case "seconds":
                t = TimeUnit.SECONDS;
                break;
            case "minutes":
                t = TimeUnit.MINUTES;
                break;
            case "hours":
                t = TimeUnit.HOURS;
                break;
            case "days":
                t = TimeUnit.DAYS;
                break;
            case "weeks":
                t = TimeUnit.DAYS;
                time = time / 7;
                break;
            default:
                t = TimeUnit.NANOSECONDS;
                break;
        }
        switch (timeScale) {
            case "nanoseconds":
                return t.toNanos((long) time);
            case "microseconds":
                return t.toMicros((long) time);
            case "milliseconds":
                return t.toMillis((long) time);
            case "seconds":
                return t.toSeconds((long) time);
            case "minutes":
                return t.toMinutes((long) time);
            case "hours":
                return t.toHours((long) time);
            case "days":
                return t.toDays((long) time);
            case "weeks":
                return (int) t.toDays((long) time) / 7;
            default:
                return (long) time;
        }
    }
    
        /**
     * Method to normalize vertex's timestamps to start from 0
     * @param graph
     * @param overwriteTime
     */
    public static void NormalizeTime(DirectedGraph<Object, Edge> graph, boolean overwriteTime) {
        Collection<Object> vertices = graph.getVertices();
        double minTime = Double.POSITIVE_INFINITY;
        for (Object v : vertices) {
            if (((Vertex) v).getTime() != -1) {
                minTime = Math.min(minTime, ((Vertex) v).getTime());
            }
        }
        // Normalize time
        for (Object v : vertices) {
            if (((Vertex) v).getTime() >= 0) {
                double normalized = ((Vertex) v).getTime() - minTime;
                ((Vertex) v).setNormalizedTime(normalized);
                if (overwriteTime && minTime != 0) {
                    ((Vertex) v).setTime(Double.toString(normalized));
                }
            } else {
                ((Vertex) v).setNormalizedTime(-1);
            }
        }
    }
    
    /**
     * Method to detect all values for the ATTRIBUTE that appears in the graph
     * @param vertices
     * @param attribute
     * @return 
     */
    public static Collection<String> DetectAllPossibleValuesFromAttribute(Collection<Object> vertices, String attribute) {
        Map<String, String> attributeList = new HashMap<>();
        for (Object v : vertices) {
            String value = ((Vertex) v).getAttributeValue(attribute);
            if(!value.contains("Unknown")) {
                String[] values = value.split(", ");
                for(String s : values) {
                    attributeList.put(s, s);
                }
            }
        }
        return attributeList.values();
    }
    
    /**
     * Method that determines if the provided attribute is a timestamp/date/time type of attribute
     * @param attribute the attribute we want to check
     * @return TRUE if it is Time/Timestamp/Date or FALSE if not
     */
    public static boolean isItTime(String attribute) {
        if(attribute.equalsIgnoreCase("Time"))         {
            return true;
        }
        if(attribute.equalsIgnoreCase("Timestamp")) {
            return true;
        }
        if(attribute.equalsIgnoreCase("Date")) {
            return true;
        }
        return false;
    }
    
    /**
     * Method to return a comparator that compares two vertices based on their timestamps
     * @return the comparator
     */
    public static Comparator getVertexTimeComparator() {
        Comparator comparator = new Comparator<Object>() {
            @Override
            public int compare(Object c1, Object c2) {
                    double c1t = ((Vertex) c1).getTime();
                    double c2t = ((Vertex) c2).getTime();
                    if (c1t != c2t) {
                        return Double.compare(c1t, c2t);
                    } else {
                        return ((Vertex) c2).getNodeType().compareTo(((Vertex) c1).getNodeType());
                    }
                    //TODO make agent lose priority to appear after the activity
            }
        };
        return comparator;
    }
    
    /**
     * Generic comparator
     * @param attribute Is the attribute that we want to sort
     * @return the comparator for the given attribute
     */
    public static Comparator getVertexAttributeComparator(final String attribute) {
        Comparator comparator = new Comparator<Object>() {
            @Override
            public int compare(Object c1, Object c2) {
                    long c1t = 0;
                    long c2t = 0;
                    if(!tryParseDouble(((Vertex) c1).getAttributeValue(attribute))){
                        String c1v = ((Vertex) c1).getAttributeValue(attribute);
                        String c2v = ((Vertex) c2).getAttributeValue(attribute);
                        int result = c1v.compareTo(c2v);
                        if(result == 0) {
                            return untieVertexAttributeComparator(c1, c2);
                        }
                        else
                            return result;
                    } else {
                        c1t = (long) (((Vertex) c1).getAttributeValueDouble(attribute) * 1000);
                        c2t = (long) (((Vertex) c2).getAttributeValueDouble(attribute)* 1000);
                    }
                    if (c1t != c2t) {
                        return Long.compare(c1t, c2t);
                    } else {
                        return untieVertexAttributeComparator(c1, c2);
//                        return ((Vertex) c1).getNodeType().compareTo(((Vertex) c2).getNodeType());
                    }
            }
        };
        return comparator;
    }
    
    private static int untieVertexAttributeComparator(Object c1, Object c2) {
        if((((Vertex) c1).getNodeType() != null) && (((Vertex) c2).getNodeType() != null)){
            if((((Vertex) c1).getNodeType().equalsIgnoreCase("Agent")) && !(((Vertex) c2).getNodeType().equalsIgnoreCase("Agent")))
                return -1;
            else if((((Vertex) c2).getNodeType().equalsIgnoreCase("Agent")) && !(((Vertex) c1).getNodeType().equalsIgnoreCase("Agent")))
                return 1;
            else if((((Vertex) c1).getNodeType().equalsIgnoreCase("Activity")) && !(((Vertex) c2).getNodeType().equalsIgnoreCase("Activity")))
                return -1;
            else if((((Vertex) c2).getNodeType().equalsIgnoreCase("Activity")) && !(((Vertex) c1).getNodeType().equalsIgnoreCase("Activity")))
                return 1;
            else if((((Vertex) c1).getNodeType().equalsIgnoreCase("Entity")) && !(((Vertex) c2).getNodeType().equalsIgnoreCase("Entity")))
                return 1;
            else if((((Vertex) c2).getNodeType().equalsIgnoreCase("Entity")) && !(((Vertex) c1).getNodeType().equalsIgnoreCase("Entity")))
                return -1;
            else
                return 0;
        }
        else
            return 0;
    }
    
    /**
     * Method to determine if the string has a minus sign in the beginning
     * @param s is the string that we want to know if it has a minus sign
     * @return TRUE if it has a minus sign and FALSE if it does not have it
     */
    public static boolean getMinusSign(String s) {
        String sign = s.substring(0, 1);
        boolean isReverse = false;
        
        if("-".equals(sign)) {
            isReverse = true;
        }
        return isReverse;
    }
    
    /**
     * Method to remove the minus sign in the beginning of the string (if it has it)
     * @param s the string that we want to remove the minus sign
     * @return the string without the minus sign
     */
    public static String removeMinusSign(String s) {
        String sign = s.substring(0, 1);
        if("-".equals(sign)) {
            return s.substring(1);
        }
        return s;
    }
    
    
    /**
     * Method to decide if the current edge should be highlighted or not
     * @param e the edge we are evaluating
     * @param variables location that has the view variable so we can select the vertices and edges that were marked by the user
     * @return true if the edge should be highlighted and false if not
     */
    public static boolean edgeHighlighted(Edge e, Variables variables){
        PickedState<Object> picked_state = variables.view.getPickedVertexState();
        PickedState<Edge> edge_picked_state = variables.view.getPickedEdgeState();
        if(!picked_state.getPicked().isEmpty() || !edge_picked_state.getPicked().isEmpty()) {
            if (!picked_state.getPicked().isEmpty()) {
                for (Object v : picked_state.getPicked()) {
                    if (e.getSource().equals(v)) {
                        return true;
                    } else if (e.getTarget().equals(v)) {
                        return true;
                    }
                }
            }
            if (!edge_picked_state.getPicked().isEmpty()) {
                for (Object v : edge_picked_state.getPicked()) {
                    if (e.equals(v)) {
                        return true;
                    }
                }
            }
            return false;
        }
        return true;
    }
} public class ActivityVertex extends Vertex {


    /**
     * Constructor
     *
     * @param id This param is used by JUNG for collapsed vertices and tooltips.
     */
    public ActivityVertex(String id) {
        super(id, "", "");
    }
    
    public ActivityVertex(String id, String label, String date) {
        super(id, label, date);
    }

    /**
     * Constructor overload
     *
     * @param array for TSV Reader
     */
    public ActivityVertex(String[] array) {
        super(array[0], array[1], array[2]);
    }


    @Override
    public Paint getColor() {
        return VariableNames.ActivityColor;
    }
    

    @Override
    public String getNodeType() {
        return "Activity";
    }
}
import static br.uff.ic.provviewer.GraphFrame.StatusFilterBox;
import br.uff.ic.provviewer.Path.GraphPath;
import br.uff.ic.utility.graph.Edge;
import br.uff.ic.provviewer.Stroke.EdgeStroke;
import br.uff.ic.provviewer.Stroke.VertexStroke;
import br.uff.ic.provviewer.VariableNames;
import br.uff.ic.provviewer.Variables;
import br.uff.ic.utility.graph.AgentVertex;
import br.uff.ic.provviewer.Vertex.ColorScheme.VertexPainter;
import br.uff.ic.utility.graph.EntityVertex;
import br.uff.ic.utility.graph.Vertex;
import br.uff.ic.provviewer.Vertex.VertexShape;
import br.uff.ic.utility.AttValueColor;
import br.uff.ic.utility.EdgeSourceTarget;
import br.uff.ic.utility.StackElementUndoDeletion;
import br.uff.ic.utility.Utils;
import br.uff.ic.utility.graph.ActivityVertex;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.util.PredicatedParallelEdgeIndexFunction;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import org.apache.commons.collections15.Predicate;
import org.apache.commons.collections15.Transformer;
     * @return a map with "Support", "Confidence", and "Lift" of the selected
     * pattern
     */
    public static Map<String, String> FindFrequencyOfNodes(Variables variables, List<String> correctTrials) {

        // Testing purposes
        List<String> correctTrials_test = new ArrayList<>();
        correctTrials_test.add("workflow_trial_7.xml");
        correctTrials_test.add("workflow_trial_11.xml");
        correctTrials_test.add("workflow_trial_16.xml");
        correctTrials_test.add("workflow_trial_17.xml");
        correctTrials_test.add("workflow_trial_18.xml");
        correctTrials_test.add("workflow_trial_22.xml");
        correctTrials_test.add("workflow_trial_24.xml");
        correctTrials_test.add("workflow_trial_25.xml");
        correctTrials_test.add("workflow_trial_28.xml");
        correctTrials_test.add("workflow_trial_32.xml");
        // End testing purposes
        double support_notok;
        double support_ok;
        double confidence_ok;
        double confidence_notok;
        double lift_notok;
        double lift_ok;
        int notok = 0;
        int ok = 0;
        String selected_vertices = "Selected vertices:";
        PickedState<Object> picked_state = variables.view.getPickedVertexState();
        int numberOfNodes = picked_state.getSelectedObjects().length;
        Map<String, Integer> graphFiles = new HashMap<>();
        Collection<String> commonFiles = new ArrayList<>();

        for (Object v : picked_state.getSelectedObjects()) { // For each vertex from the pattern selected by the user
            String[] files = ((Vertex) v).getAttributeValues(VariableNames.GraphFile);
            for (String f : files) { // For each graphFile from the current vertex
                if (graphFiles.containsKey(f)) {
                    int i = graphFiles.get(f);
                    i++;
                    graphFiles.put(f, i);
                } else {
                    graphFiles.put(f, 1);
                }
            }
            selected_vertices += "\n" + ((Vertex) v).getID();
        }

        for (String f : graphFiles.keySet()) {
            int qnt = graphFiles.get(f);
            if (qnt == numberOfNodes) {
                if (!correctTrials_test.contains(f)) {
//                    System.out.println("Common graphFile to all:" + f);
                    notok++;
                }
                commonFiles.add(f);
//                else if(correctTrials_test.contains(f)) {
////                    System.out.println("Common graphFile to all:" + f);
//                    ok++;
//                    commonFiles.add(f);
//                }
            }
        }

        int numberofCorrect = 0;
        for (String f : commonFiles) {
            for (String cf : correctTrials_test) {
                if (f.equalsIgnoreCase(cf)) {
                    numberofCorrect++;
                    break;
                }
            }
        }
        support_notok = (double) notok / (double) variables.numberOfGraphs;
        support_ok = (double) numberofCorrect / (double) variables.numberOfGraphs;
        confidence_ok = (double) numberofCorrect / (double) commonFiles.size();
        confidence_notok = (double) notok / (double) commonFiles.size();
        lift_notok = confidence_notok / ((double) (variables.numberOfGraphs - correctTrials_test.size()) / (double) variables.numberOfGraphs);
        lift_ok = confidence_ok / ((double) correctTrials_test.size() / (double) variables.numberOfGraphs);

        // Lets make the numbers be in the % notation (0% to 100%)
        confidence_notok = confidence_notok * 100.0f;
        support_notok = support_notok * 100.0f;
        confidence_ok = confidence_ok * 100.0f;
        support_ok = support_ok * 100.0f;

        // Probability of these nodes appearing together
        String s_support_notok = "Support: " + support_notok + " %";
        // Probability of these nodes leading to an undesirable outbome
        String s_confidence_notok = "Confidence: " + confidence_notok + " %";
        String liftResult_notok = "Lift: " + lift_notok;
        String s_support_ok = "Support: " + support_ok + " %";
        String s_confidence_ok = "Confidence: " + confidence_ok + " %";
        String liftResult_ok = "Lift: " + lift_ok;

        System.out.println("NOT OK:");
        System.out.println(s_support_notok);
        System.out.println(s_confidence_notok);
        System.out.println(liftResult_notok);
        System.out.println("OK:");
        System.out.println(s_support_ok);
        System.out.println(s_confidence_ok);
        System.out.println(liftResult_ok);

        Map<String, String> result = new HashMap<>();
        result.put("Support_NOTOK", s_support_notok);
        result.put("Confidence_NOTOK", s_confidence_notok);
        result.put("Lift_NOTOK", liftResult_notok);
        result.put("Support_OK", s_support_ok);
        result.put("Confidence_OK", s_confidence_ok);
        result.put("Lift_OK", liftResult_ok);
        result.put("Selected", selected_vertices);

//        System.out.println("Given that these nodes appear together, the probability of the selected nodes leading to a desirable outbome is: " + frequencyCorrect);
        return result;
    }

    static void ComputeMarkovChain(Variables variables) {
        variables.markov.computeMarkovChain(variables);
    }

    public static String ComputePath(Variables variables) {
        return GraphPath.ComputePath(variables);