/******************************************************************************
* Copyright 2013-2016 LASIGE                                                  *
*                                                                             *
* Licensed under the Apache License, Version 2.0 (the "License"); you may     *
* not use this file except in compliance with the License. You may obtain a   *
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0           *
*                                                                             *
* Unless required by applicable law or agreed to in writing, software         *
* distributed under the License is distributed on an "AS IS" BASIS,           *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.    *
* See the License for the specific language governing permissions and         *
* limitations under the License.                                              *
*                                                                             *
*******************************************************************************
* Displays a mapping graphically, and shows detailed information about it.    *
*                                                                             *
* @author Daniel Faria, Catia Pesquita & Catarina Martins                     *
******************************************************************************/
package aml.ui;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.border.BevelBorder;
import javax.swing.border.TitledBorder;

import org.gephi.graph.api.DirectedGraph;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.GraphController;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.Node;
import org.gephi.layout.plugin.force.StepDisplacement;
import org.gephi.layout.plugin.force.yifanHu.YifanHuLayout;
import org.gephi.layout.plugin.labelAdjust.LabelAdjust;
import org.gephi.preview.api.PreviewController;
import org.gephi.preview.api.PreviewModel;
import org.gephi.preview.api.PreviewProperty;
import org.gephi.preview.api.ProcessingTarget;
import org.gephi.preview.api.RenderTarget;
import org.gephi.preview.types.DependantColor;
import org.gephi.preview.types.DependantOriginalColor;
import org.gephi.preview.types.EdgeColor;
import org.gephi.project.api.ProjectController;
import org.openide.util.Lookup;

import aml.AML;
import aml.filter.QualityFlagger;
import aml.filter.RepairMap;
import aml.match.Alignment;
import aml.match.Mapping;
import aml.ontology.Lexicon;
import aml.ontology.Ontology;
import aml.ontology.RelationshipMap;
import aml.ontology.URIMap;
import aml.ontology.ValueMap;
import aml.settings.LexicalType;
import aml.settings.MappingRelation;
import aml.settings.MappingStatus;
import aml.util.Table2Set;
import processing.core.PApplet;
import aml.settings.EntityType;
 

public class ViewMapping extends JDialog implements ActionListener
{
	
//Attributes
	
	//Constants
	private static final long serialVersionUID = 4516245633857479148L;
  	private static final int MAX_RETRIES = 5;
  	
  	//Ontology and Alignment attributes
	private AML aml;
	private URIMap uris;
	private RelationshipMap rm;
	private Ontology source, target;
  	private Alignment a;
	private int mapping, sourceId, targetId;
	private Mapping m;
	private EntityType t;

	//Dimensions
	private int width;
	private int height;
	
	//Components
	private JMenuBar menu;
	private JMenu view;
	private JMenuItem next, previous, options, redraw;
	private JTabbedPane tabbedPane;
	private PApplet mappingViewer;
	private JPanel details, conflicts, sourcePanel, targetPanel;
	private Vector<JCheckBox> check;
	private Vector<Mapping> mappings;
	private Vector<MappingButton> mappingButtons;
	private Vector<JLabel> labels;
	private JButton reset, setCorrect, setIncorrect;
	
	//Graph components
	private GraphModel model;
  	private DirectedGraph directedGraph;
  	private HashSet<Integer> nodes;
  	private Table2Set<Integer,Integer> edges;
  	private int classDistance;
  	private float[] sourceColor, targetColor;

//Constructors
  	
    public ViewMapping()
    {
        super();
        //Set the size & colors
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
		Dimension screenSize = env.getMaximumWindowBounds().getSize();
		width = screenSize.width;
		height = screenSize.height;
     	this.setMinimumSize(new Dimension((int)(width*0.9),(int)(height*0.9)));
     	this.setPreferredSize(new Dimension((int)(width*0.9),(int)(height*0.9)));
		sourceColor = new float[3];
		AMLColor.BLUE.getRGBColorComponents(sourceColor);
		targetColor = new float[3];
		AMLColor.BROWN.getRGBColorComponents(targetColor);
		
     	//Get the ontologies and alignment
     	aml = AML.getInstance();
		source = aml.getSource();
		target = aml.getTarget();
		uris = aml.getURIMap();
        rm = aml.getRelationshipMap();
        
        //Setup the menu bar
        menu = new JMenuBar();
        view = new JMenu("View");
        next = new JMenuItem("Next Mapping");
        next.addActionListener(this);
        view.add(next);
        previous = new JMenuItem("Previous Mapping");
        previous.addActionListener(this);
        view.add(previous);
        view.addSeparator();
        redraw = new JMenuItem("Refresh");
        redraw.addActionListener(this);
        view.add(redraw);
        view.addSeparator();
        options = new JMenuItem("Graph Options");
        options.addActionListener(this);
        view.add(options);
        menu.add(view);
        this.setJMenuBar(menu);
        
        refresh();
    }
    
//Public Methods
    
	@Override
	public void actionPerformed(ActionEvent e)
	{
		Object b = e.getSource();
		if(b == redraw)
		{
			this.refresh();			
		}
		else if(b == next)
		{
			mapping++;
			if(mapping >= a.size())
				aml.goTo(0);
			else
				aml.goTo(mapping);
			this.refresh();			
		}
		else if(b == previous)
		{
			mapping--;
			if(mapping < 0)
				aml.goTo(a.size() - 1);
			else
				aml.goTo(mapping);
			this.refresh();
		}
		else if(b == options)
		{
			String lang = aml.getLabelLanguage();
			boolean anc = aml.showAncestors();
			boolean des = aml.showDescendants();
			int cls = aml.getClassDistance();
			int ind = aml.getIndividualDistance();
			new ViewOptions();
			if(!lang.equals(aml.getLabelLanguage()) ||
					anc != aml.showAncestors() ||
					des != aml.showDescendants() ||
					cls != aml.getClassDistance() ||
					ind != aml.getIndividualDistance())
				this.refresh();
		}
		else if(b == reset)
		{
			for(int i = 0; i < check.size(); i++)
			{
				if(check.get(i).isSelected())
				{
					Mapping n = mappings.get(i);
					check.get(i).setSelected(false);
					if(n.getStatus().equals(MappingStatus.UNKNOWN))
						continue;
					int index = a.getIndex(n.getSourceId(),n.getTargetId());
					a.get(index).setStatus(MappingStatus.UNKNOWN);
					aml.refreshMapping(index);
					mappingButtons.get(i).refresh();
				}
			}
		}
		else if(b == setCorrect)
		{
			for(int i = 0; i < check.size(); i++)
			{
				if(check.get(i).isSelected())
				{
					Mapping n = mappings.get(i);
					check.get(i).setSelected(false);
					if(n.getStatus().equals(MappingStatus.CORRECT))
						continue;
					int index = a.getIndex(n.getSourceId(),n.getTargetId());
					a.get(index).setStatus(MappingStatus.CORRECT);
					aml.refreshMapping(index);
					mappingButtons.get(i).refresh();
				}
			}
		}
		else if(b == setIncorrect)
		{
			for(int i = 0; i < check.size(); i++)
			{
				if(check.get(i).isSelected())
				{
					Mapping n = mappings.get(i);
					check.get(i).setSelected(false);
					if(n.getStatus().equals(MappingStatus.INCORRECT))
						continue;
					int index = a.getIndex(n.getSourceId(),n.getTargetId());
					a.get(index).setStatus(MappingStatus.INCORRECT);
					aml.refreshMapping(index);
					mappingButtons.get(i).refresh();
				}
			}
		}
		else
		{
			int index = mappingButtons.indexOf(b);
			if(index > -1)
			{
				Mapping n = mappings.get(index);
				index = a.getIndex(n.getSourceId(),n.getTargetId());
				aml.goTo(index);
				refresh();
			}
		}		
	}

//Private Methods
    
	private void refresh()
	{
		a = aml.getAlignment();
		mapping = aml.getActiveMapping();
        m = a.get(mapping);
        sourceId = m.getSourceId();
        targetId = m.getTargetId();
        t = uris.getType(sourceId);
        
        //Set the title and modality
        this.setTitle(m.toGUI());
		this.setModalityType(ModalityType.APPLICATION_MODAL);

        //Setup the Tabbed Pane
        tabbedPane = new JTabbedPane();
        tabbedPane.setBackground(AMLColor.WHITE);

        //Add the graph
        buildGraph();
        if(mappingViewer != null)
        {
        	tabbedPane.addTab("Graph View", mappingViewer);
        	mappingViewer.mouseClicked();
        }
        else
        	tabbedPane.addTab("Graph View", new JPanel());
        JLabel lab1 = new JLabel("Graph View",SwingConstants.CENTER);
        lab1.setPreferredSize(new Dimension(120, 15));
        tabbedPane.setTabComponentAt(0, lab1);
        //Add the details
        buildDetailPanel();
        tabbedPane.addTab("Details", details);
        JLabel lab2 = new JLabel("Details",SwingConstants.CENTER);
        lab2.setPreferredSize(new Dimension(120, 15));
        tabbedPane.setTabComponentAt(1, lab2);
        //Add the issues
        buildConflictPanel();
        tabbedPane.addTab("Status & Conflicts", conflicts);
        JLabel lab3 = new JLabel("Status & Conflicts",SwingConstants.CENTER);
        lab3.setPreferredSize(new Dimension(120, 15));
        tabbedPane.setTabComponentAt(2, lab3);
        
        //Set the tabbed pane as the content pane
		setContentPane(tabbedPane);
		
		//Wrap up
		this.pack();
		GraphicsEnvironment g = GraphicsEnvironment.getLocalGraphicsEnvironment();
		int left = g.getCenterPoint().x - (int)(this.getPreferredSize().width / 2);
		this.setLocation(left, 0);
        this.setVisible(true);
	}
	
    //Builds the Mapping graph using the Gephi Toolkit
	private void buildGraph()
	{
		//Initialize a project and therefore a workspace
		ProjectController pc = Lookup.getDefault().lookup(ProjectController.class);
		pc.newProject();
		//Initialize the graph-building variables
		GraphController graphController = Lookup.getDefault().lookup(GraphController.class);
		model = graphController.getModel();
		directedGraph = model.getDirectedGraph();
		//Initialize the node sets (which don't include the starting nodes)
		nodes = new HashSet<Integer>();
		edges = new Table2Set<Integer,Integer>();
		//Get the maximum distance
		classDistance = aml.getClassDistance();
		
		//Add the starting source and target nodes to the graph
		addNode(sourceId,8);
		addNode(targetId,8);
		//And the mapping between them
		addMapping(sourceId,targetId);
		//Add all ancestors and descendants as per the view parameters
		if(aml.showAncestors())
		{
			addAncestors(sourceId);
			addAncestors(targetId);
		}
		if(aml.showDescendants())
		{
			addDescendants(sourceId);
			addDescendants(targetId);
		}
		if(t.equals(EntityType.INDIVIDUAL))
			addInstancedClasses();
		else if(t.equals(EntityType.CLASS))
		{
			addEquiv(sourceId);
			addEquiv(targetId);
		}
		//Add all additional mappings of the initial nodes
		addOtherMappings(sourceId,targetId);
		//Now find if there are any mappings between the node sets and add them
		addAllMappings();
		//Finally, render the graph
		renderGraph();
	}
	
	//Builds the details panel
	private void buildDetailPanel()
	{
		//Setup the panels
		details = new JPanel();
		details.setLayout(new BoxLayout(details, BoxLayout.Y_AXIS));
		JPanel topFiller = new JPanel();
		topFiller.setPreferredSize(new Dimension(topFiller.getPreferredSize().width,10));
		topFiller.setMaximumSize(new Dimension(topFiller.getMaximumSize().width,10));
		details.add(topFiller);
		
		if(t.equals(EntityType.CLASS))
		{
			sourcePanel = buildClassDetailPanel(sourceId);
			sourcePanel.setBorder(new TitledBorder("Source Class:"));
			targetPanel = buildClassDetailPanel(targetId);
			targetPanel.setBorder(new TitledBorder("Target Class:"));
		}
		else if(t.equals(EntityType.INDIVIDUAL))
		{
			sourcePanel = buildIndivDetailPanel(sourceId);
			sourcePanel.setBorder(new TitledBorder("Source Individual:"));
			targetPanel = buildIndivDetailPanel(targetId);
			targetPanel.setBorder(new TitledBorder("Target Individual:"));
		}
		else
		{
			sourcePanel = buildPropDetailPanel(sourceId);
			sourcePanel.setBorder(new TitledBorder("Source Property:"));
			targetPanel = buildPropDetailPanel(targetId);
			targetPanel.setBorder(new TitledBorder("Target Property:"));
		}
		
		//Set the sizes of the subpanels and add them to the details panel
		sourcePanel.setPreferredSize(new Dimension((int)(width*0.85),sourcePanel.getPreferredSize().height));
		sourcePanel.setMaximumSize(new Dimension((int)(width*0.85),sourcePanel.getPreferredSize().height));
		details.add(sourcePanel);
		JPanel midFiller1 = new JPanel();
		midFiller1.setPreferredSize(new Dimension(midFiller1.getPreferredSize().width,10));
		midFiller1.setMaximumSize(new Dimension(midFiller1.getMaximumSize().width,10));
		details.add(midFiller1);
		targetPanel.setPreferredSize(new Dimension((int)(width*0.85),targetPanel.getPreferredSize().height));
		targetPanel.setMaximumSize(new Dimension((int)(width*0.85),targetPanel.getPreferredSize().height));
		details.add(targetPanel);
		JPanel midFiller2 = new JPanel();
		midFiller2.setPreferredSize(new Dimension(midFiller2.getPreferredSize().width,10));
		midFiller2.setMaximumSize(new Dimension(midFiller2.getMaximumSize().width,10));
		details.add(midFiller2);
		//Initialize and construct the mapping panel
		JPanel mappingPanel = new JPanel();
		mappingPanel.setLayout(new BoxLayout(mappingPanel, BoxLayout.Y_AXIS));
		mappingPanel.setBorder(new TitledBorder("Mapping:"));
        JLabel type = new JLabel("<html>Type: <i>" + t + " Mapping</i></html>");
		mappingPanel.add(type);
        JLabel sim = new JLabel("<html>Final Similarity: <i>" + m.getSimilarityPercent() + "</i></html>");
        mappingPanel.add(sim);
        if(t.equals(EntityType.CLASS))
        {
	        QualityFlagger qf = aml.getQualityFlagger();
	        if(qf != null)
	        {
	        	Vector<String> labels = qf.getLabels();
	        	for(int i = 0; i < labels.size(); i++)
	        	{
	        		JLabel simQ = new JLabel("<html>" + labels.get(i) + "<i>" +
	        				qf.getSimilarityPercent(sourceId,targetId,i) + "</i></html>");
	        		mappingPanel.add(simQ);
	        	}
	        }
        }
		//Set its size and add it to the details panel
		mappingPanel.setPreferredSize(new Dimension((int)(width*0.85),mappingPanel.getPreferredSize().height));
		mappingPanel.setMaximumSize(new Dimension((int)(width*0.85),mappingPanel.getPreferredSize().height));
		details.add(mappingPanel);
		JPanel bottomFiller = new JPanel();
		details.add(bottomFiller);
	}
	
	//Builds the conflict panel
	private void buildConflictPanel()
	{
		conflicts = new JPanel();
		conflicts.setLayout(new BoxLayout(conflicts, BoxLayout.Y_AXIS));
		
		//The header button panel
		setCorrect = new JButton("Set Correct");
		setCorrect.setBackground(AMLColor.GREEN);
		setCorrect.setPreferredSize(new Dimension(110,28));
		setCorrect.addActionListener(this);
		reset = new JButton("Reset");
		reset.setBackground(AMLColor.GRAY);
		reset.setPreferredSize(new Dimension(110,28));
		reset.addActionListener(this);
		setIncorrect = new JButton("Set Incorrect");
		setIncorrect.setBackground(AMLColor.RED);
		setIncorrect.setPreferredSize(new Dimension(110,28));
		setIncorrect.addActionListener(this);
		JPanel sub = new JPanel();
		sub.setBorder(new BevelBorder(1));
		sub.add(setCorrect);
		sub.add(reset);
		sub.add(setIncorrect);
		JPanel headerPanel = new JPanel(new FlowLayout());
		headerPanel.setMaximumSize(new Dimension((int)(width*0.9),40));
		headerPanel.add(sub);
		conflicts.add(headerPanel);
		
		JPanel mappingPanel = new JPanel();
		mappingPanel.setLayout(new BoxLayout(mappingPanel, BoxLayout.Y_AXIS));
		check = new Vector<JCheckBox>();
		mappingButtons = new Vector<MappingButton>();
		mappings = new Vector<Mapping>();
		labels = new Vector<JLabel>();
		
		mappings.add(m);
		labels.add(new JLabel("[Active Mapping]"));
		for(Mapping n : a.getConflicts(m))
		{
			mappings.add(n);
			labels.add(new JLabel("[Cardinality Conflict]"));
		}
		RepairMap rMap = aml.getRepairMap();
		if(rMap != null)
		{
			for(Mapping n : rMap.getConflictMappings(m))
			{
				if(!mappings.contains(n))
				{
					mappings.add(n);
					labels.add(new JLabel("[Coherence Conflict]"));
				}
				else
				{
					labels.get(mappings.indexOf(n)).setText("[Cardinality & Coherence Conflict]");
				}
			}
		}
		for(int i = 0; i < mappings.size(); i++)
		{
			JCheckBox c = new JCheckBox(""); 
			check.add(c);
			MappingButton b = new MappingButton(mappings.get(i));
			mappingButtons.add(b);
			if(!mappings.get(i).equals(m))
				b.addActionListener(this);
			JPanel subPanel = new JPanel(new BorderLayout());
			subPanel.add(c,BorderLayout.LINE_START);
			JPanel subSubPanel = new JPanel(new BorderLayout());
			subSubPanel.add(b,BorderLayout.LINE_START);
			subSubPanel.add(labels.get(i), BorderLayout.CENTER);
			subPanel.add(subSubPanel, BorderLayout.CENTER);
			subPanel.setMaximumSize(new Dimension(subPanel.getMaximumSize().width,28));
			subPanel.setPreferredSize(new Dimension(subPanel.getPreferredSize().width,28));
			mappingPanel.add(subPanel);
		}		
		JPanel filler = new JPanel();
		mappingPanel.add(filler);
		JScrollPane scrollPane = new JScrollPane(mappingPanel);
		scrollPane.setBorder(new BevelBorder(1));
		conflicts.add(scrollPane);
	}

//Auxiliary Methods for buildGraph()
	
	//Adds all mappings between listed nodes to the graph
	private void addAllMappings()
	{
		if(a == null)
			return;
		for(int i : nodes)
		{
			for(int j : a.getSourceMappings(i))
				if(nodes.contains(j))
					addMapping(i, j);
			for(int j : a.getTargetMappings(i))
				if(nodes.contains(j))
					addMapping(j, i);
		}
	}
	
	//Adds all ancestors of the given entity to the graph
	private void addAncestors(int id)
	{
		HashSet<Integer> ancestors = new HashSet<Integer>();
		ancestors.add(id);
		HashSet<Integer> descendants;
		if(t.equals(EntityType.CLASS))
		{
			for(int i = 0; i < classDistance; i++)
			{
				descendants = new HashSet<Integer>(ancestors);
				ancestors = new HashSet<Integer>();
				for(int j : descendants)
				{
					Set<Integer> parents = rm.getParents(j);
					for(int k : parents)
					{
						if(directedGraph.getNode("" + k) == null)
							addNode(k, 6);
						if(!edges.contains(j,k) && !edges.contains(k,j))
							addEdge(j, k, rm.getRelationship(j, k).getProperty());
					}
					ancestors.addAll(parents);
				}
				nodes.addAll(ancestors);
			}
		}
		else if(t.equals(EntityType.INDIVIDUAL))
		{
			for(int k : rm.getIndividualActiveRelations(id))
			{
				Set<Integer> rels = rm.getIndividualProperties(id, k);
				if(rels.size() > 0)
				{
					if(directedGraph.getNode("" + k) == null)
						addNode(k, 6);
					if(!edges.contains(id,k) && !edges.contains(k,id))
						addEdge(id, k, rels.iterator().next());
				}
				nodes.add(k);
			}
		}
	}
	
	//Adds all descendants of the given entity to the graph
	private void addDescendants(int id)
	{
		HashSet<Integer> descendants = new HashSet<Integer>();
		descendants.add(id);
		HashSet<Integer> ancestors;
		if(t.equals(EntityType.CLASS))
		{
			for(int i = 0; i < classDistance; i++)
			{
				ancestors = new HashSet<Integer>(descendants);
				descendants = new HashSet<Integer>();
				for(int j : ancestors)
				{
					Set<Integer> children = rm.getChildren(j);
					for(int k : children)
					{
						if(directedGraph.getNode("" + k) == null)
							addNode(k, 6);
						if(!edges.contains(j,k) && !edges.contains(k,j))
							addEdge(k, j, rm.getRelationship(k, j).getProperty());
					}
					descendants.addAll(children);
				}
				nodes.addAll(descendants);
			}
		}
	}
	
	//Adds an anonymous edge between two entities to the graph (for subclass relations)
	private void addEdge(int child, int parent)
	{
		edges.add(child, parent);
		Node c = directedGraph.getNode("" + child);
		Node p = directedGraph.getNode("" + parent);
		Edge e = model.factory().newEdge(c, p, 3, true);
		if(source.contains(child) && !target.contains(child))
			e.getEdgeData().setColor(sourceColor[0], sourceColor[1], sourceColor[2]);
		else if(!source.contains(child) && target.contains(child))
			e.getEdgeData().setColor(targetColor[0], targetColor[1], targetColor[2]);
		directedGraph.addEdge(e);
	}
	
	//Adds an edge between two entities to the graph with the given property's label
	private void addEdge(int child, int parent, int prop)
	{
		edges.add(child, parent);
		Node c = directedGraph.getNode("" + child);
		Node p = directedGraph.getNode("" + parent);
		Edge e = model.factory().newEdge(c, p, 3, true);
		if(source.contains(child))
		{
			if(prop > -1)
				e.getEdgeData().setLabel(source.getName(prop));
			if(!target.contains(child))
				e.getEdgeData().setColor(sourceColor[0], sourceColor[1], sourceColor[2]);
		}
		else if(target.contains(child))
		{
			if(prop > -1)
				e.getEdgeData().setLabel(target.getName(prop));
			e.getEdgeData().setColor(targetColor[0], targetColor[1], targetColor[2]);
		}
		directedGraph.addEdge(e);
		if(rm.isSymmetric(prop) || (prop == -1 && rm.getDistance(child, parent) == 0))
		{
			Edge f = model.factory().newEdge(p, c, 3, true);
			f.getEdgeData().setColor(sourceColor[0], sourceColor[1], sourceColor[2]);
			directedGraph.addEdge(f);
		}
	}
	
	//Adds an edge between two entities to the graph with the given label
	private void addEdge(int child, int parent, String label)
	{
		edges.add(child, parent);
		Node c = directedGraph.getNode("" + child);
		Node p = directedGraph.getNode("" + parent);
		Edge e = model.factory().newEdge(c, p, 3, true);
		if(source.contains(child) && !target.contains(child))
			e.getEdgeData().setColor(sourceColor[0], sourceColor[1], sourceColor[2]);
		else if(!source.contains(child) && target.contains(child))
			e.getEdgeData().setColor(targetColor[0], targetColor[1], targetColor[2]);
		e.getEdgeData().setLabel(label);
		directedGraph.addEdge(e);
	}
	
	//Adds all equivalent classes of the given class to the graph
	private void addEquiv(int id)
	{
		Set<Integer> eq = rm.getEquivalences(id);
		for(int i : eq)
		{
			if(directedGraph.getNode("" + i) == null)
				addNode(i, 6);
			addEdge(i, id);
		}
		nodes.addAll(eq);
	}
	
	//Adds all classes instanced by the listed individuals to the graph
	//plus their ancestors up to the classDistance limit
	private void addInstancedClasses()
	{
		Set<Integer> classes = new HashSet<Integer>();
		Set<Integer> individuals = new HashSet<Integer>(nodes);
		for(int i : individuals)
		{
			for(int c : rm.getIndividualClasses(i))
			{
				nodes.add(c);
				classes.add(c);
				if(directedGraph.getNode("" + c) == null)
					addNode(c,10);
				if(!edges.contains(i,c) && !edges.contains(c,i))
					addEdge(i, c, "instanceOf");
			}
		}
		HashSet<Integer> ancestors = new HashSet<Integer>(classes);
		HashSet<Integer> descendants;
		for(int i = 0; i < classDistance; i++)
		{
			descendants = new HashSet<Integer>(ancestors);
			ancestors = new HashSet<Integer>();
			for(int j : descendants)
			{
				Set<Integer> parents = rm.getParents(j);
				for(int k : parents)
				{
					if(directedGraph.getNode("" + k) == null)
						addNode(k,10);
					if(!edges.contains(j,k) && !edges.contains(k,j))
						addEdge(j, k);
				}
				ancestors.addAll(parents);
			}
			nodes.addAll(ancestors);
		}
	}
	
	//Adds a mapping between two classes to the graph
	private void addMapping(int sId, int tId)
	{
		Node n1 = directedGraph.getNode("" + sId);
		Node n2 = directedGraph.getNode("" + tId);
		if(directedGraph.getEdge(n1,n2) != null)
			return;
		MappingRelation r = a.getRelationship(sId,tId);
		MappingStatus s = a.get(sId,tId).getStatus();
		float[] edgeColor = new float[3];
		if(s.equals(MappingStatus.CORRECT))
			edgeColor = Color.GREEN.getRGBColorComponents(edgeColor);
		else if(s.equals(MappingStatus.INCORRECT))
			edgeColor = Color.RED.getRGBColorComponents(edgeColor);
		else if(s.equals(MappingStatus.FLAGGED))
			edgeColor = Color.ORANGE.getRGBColorComponents(edgeColor);
		else
			edgeColor = Color.GRAY.getRGBColorComponents(edgeColor);
		if(!r.equals(MappingRelation.SUPERCLASS))
		{
			Edge e1 = model.factory().newEdge(n1, n2, 3, true);
			e1.getEdgeData().setColor(edgeColor[0], edgeColor[1], edgeColor[2]);
			e1.getEdgeData().setLabel(a.getSimilarityPercent(sId,tId));
			directedGraph.addEdge(e1);
		}
		if(!r.equals(MappingRelation.SUBCLASS))
		{
			Edge e2 = model.factory().newEdge(n2, n1, 3, true);
			e2.getEdgeData().setColor(edgeColor[0], edgeColor[1], edgeColor[2]);
			if(r.equals(MappingRelation.SUPERCLASS))
				e2.getEdgeData().setLabel(a.getSimilarityPercent(sId,tId));
			directedGraph.addEdge(e2);
		}
	}
	
	//Adds a node to the graph
	private void addNode(int id, int size)
	{
		Node n = model.factory().newNode("" + id);
		if(source.contains(id))
		{
			n.getNodeData().setLabel(source.getName(id));
			if(!target.contains(id))
				n.getNodeData().setColor(sourceColor[0], sourceColor[1], sourceColor[2]);
		}
		else if(target.contains(id))
		{
			n.getNodeData().setLabel(target.getName(id));
			n.getNodeData().setColor(targetColor[0], targetColor[1], targetColor[2]);			
		}
		n.getNodeData().setSize(size);
		directedGraph.addNode(n);
	}
	
	//Adds other entities mapped to the entities in the central mapping
	private void addOtherMappings(int sId, int tId)
	{
		Set<Integer> sourceMappings = a.getSourceMappings(sId);
		for(Integer i : sourceMappings)
		{
			if(i == tId)
				continue;
			if(!nodes.contains(i))
			{
				nodes.add(i);
				addNode(i,6);
			}
			addMapping(sId,i);
			if(aml.showAncestors())
				addAncestors(i);
			if(aml.showDescendants())
				addDescendants(i);
		}
		Set<Integer> targetMappings = a.getTargetMappings(tId);
		for(Integer i : targetMappings)
		{
			if(i == sId)
				continue;
			if(!nodes.contains(i))
			{
				nodes.add(i);
				addNode(i,6);
			}
			addMapping(i,tId);
			if(AML.getInstance().showAncestors())
				addAncestors(i);
			if(AML.getInstance().showDescendants())
				addDescendants(i);
		}
	}
	
	//Graph rendering procedure with placement heuristic
	private void renderGraph()
	{
		//Finally, try to render the graph
		for(int i = 0; i < MAX_RETRIES; i++)
		{
			try
			{
				//Run YifanHuLayout for 100 passes - The layout always takes the current visible view
				YifanHuLayout layout = new YifanHuLayout(null, new StepDisplacement(1f));
				layout.setGraphModel(model);
				layout.resetPropertiesValues();
				layout.setOptimalDistance(300f);
				layout.setBarnesHutTheta(0.2f);
				layout.initAlgo();
				for (int j = 0; j < 100 && layout.canAlgo(); j++)
					layout.goAlgo();
			
				//Run LabelAdjust to stop labels from overlapping
				LabelAdjust labela = new LabelAdjust(null);
				labela.resetPropertiesValues();
				labela.initAlgo();
				for (int j = 0; j < 30 && labela.canAlgo(); j++)
					labela.goAlgo();
				
				//Initialize and configure preview
				PreviewController previewController = Lookup.getDefault().lookup(PreviewController.class);
				PreviewModel previewModel = previewController.getModel();
				//Configure node labels
				previewModel.getProperties().putValue(PreviewProperty.SHOW_NODE_LABELS, true);
				previewModel.getProperties().putValue(PreviewProperty.NODE_LABEL_OUTLINE_COLOR, new DependantColor(AMLColor.WHITE));
				previewModel.getProperties().putValue(PreviewProperty.NODE_LABEL_OUTLINE_SIZE, 5f);
				previewModel.getProperties().putValue(PreviewProperty.NODE_LABEL_COLOR, new DependantOriginalColor(AMLColor.BLACK));
				//Configure edges
				previewModel.getProperties().putValue(PreviewProperty.EDGE_CURVED, false);
				previewModel.getProperties().putValue(PreviewProperty.EDGE_OPACITY, 100);
				previewModel.getProperties().putValue(PreviewProperty.EDGE_RADIUS, 5f);
				previewModel.getProperties().putValue(PreviewProperty.EDGE_COLOR, new EdgeColor(EdgeColor.Mode.ORIGINAL));
				//Configure edge labels
				previewModel.getProperties().putValue(PreviewProperty.SHOW_EDGE_LABELS, true);
				previewModel.getProperties().putValue(PreviewProperty.EDGE_LABEL_COLOR, new DependantOriginalColor(AMLColor.DARK_GRAY));
				previewModel.getProperties().putValue(PreviewProperty.EDGE_LABEL_OUTLINE_COLOR, new DependantColor(AMLColor.WHITE));
				previewModel.getProperties().putValue(PreviewProperty.EDGE_LABEL_OUTLINE_SIZE, 2f);
				//Configure background color
				previewModel.getProperties().putValue(PreviewProperty.BACKGROUND_COLOR, AMLColor.WHITE);
				previewController.refreshPreview();
				//Initialize the processing target and the PApplet
				ProcessingTarget target = (ProcessingTarget) previewController.getRenderTarget(RenderTarget.PROCESSING_TARGET);
				mappingViewer = target.getApplet();
				mappingViewer.init();
				//Refresh the preview and reset the zoom
				previewController.render(target);
				target.refresh();
				target.resetZoom();
				//If successful, return
				return;
			}
			catch(Exception e)
			{
				//Otherwise keep trying
				continue;
			}
		}
		//If not successful after the retry limit, set mappingViewer to null
		mappingViewer = null;
	}
	
//Auxiliary Methods for buildDetailPanel()
	
	private JPanel buildClassDetailPanel(int id)
	{
		JPanel p = new JPanel();
		p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));

		Ontology o;
		if(source.contains(id))
			o = source;
		else
			o = target;
		Lexicon lex = o.getLexicon();
		
		//Get the local name
        JLabel localName = new JLabel("<html>Local Name: <i>" +
        		uris.getLocalName(id) + "</i></html>");
        p.add(localName);
        //Labels
        String lab = "<html>Label(s): ";
        Set<String> names = lex.getNames(id,LexicalType.LABEL);
		for(String s : names)
			lab += "<i>" + s + "</i>; ";
		if(names.size() == 0)
			lab += "N/A</html>";
		else
			lab = lab.substring(0, lab.length()-2) + "</html>";
        JLabel label = new JLabel(lab);
        p.add(label);
        //Synonyms
        names = lex.getNames(id,LexicalType.EXACT_SYNONYM);
		if(names.size() > 0)
		{
	        lab = "<html>Exact Synonyms(s): ";
			for(String s : names)
				lab += "<i>" + s + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
	        JLabel exact = new JLabel(lab);
	        p.add(exact);
		}
        names = lex.getNames(id,LexicalType.OTHER_SYNONYM);
		if(names.size() > 0)
		{
	        lab = "<html>Other Synonyms(s): ";
			for(String s : names)
				lab += "<i>" + s + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
	        JLabel other = new JLabel(lab);
	        p.add(other);
		}
        //Formulas
        names = lex.getNames(id,LexicalType.FORMULA);
		if(names.size() > 0)
		{
	        lab = "<html>Formula(s): ";
			for(String s : names)
				lab += "<i>" + s + "</i>; ";
				lab = lab.substring(0, lab.length()-2) + "</html>";
	        JLabel form = new JLabel(lab);
	        p.add(form);
		}
		
		//High Level Ancestors
		Set<Integer> highSet = rm.getHighLevelAncestors(id);
		lab = "<html>Ontology Branch(es): ";
		for(Integer i : highSet)
			lab += "<i>" + o.getName(i) + "</i>; ";
		if(highSet.size() == 0)
			lab += "N/A</html>";
		else
			lab = lab.substring(0, lab.length()-2) + "</html>";
		JLabel high = new JLabel(lab);
		p.add(high);
		
		//Parents
		Set<Integer> parents = rm.getParents(id);
		Table2Set<Integer,Integer> relParents = new Table2Set<Integer,Integer>();
		for(Integer i : parents)
			relParents.add(rm.getRelationship(id, i).getProperty(), i);
		Vector<Integer> rels = new Vector<Integer>(relParents.keySet());
		Collections.sort(rels);
		for(Integer r : rels)
		{
			if(r == -1)
				lab = "<html>subClassOf: ";
			else
				lab = "<html>" + o.getName(r) + ": ";
			for(Integer a : relParents.get(r))
				lab += "<i>" + o.getName(a) + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
			JLabel ancs = new JLabel(lab);
			p.add(ancs);
		}
		
		//And Disjoints
		Set<Integer> disjointSetSource = rm.getDisjointTransitive(id);
		if(disjointSetSource.size() != 0)
		{
			lab = "<html>disjointWith: ";
			for(Integer i : disjointSetSource)
				lab += "<i>" + o.getName(i) + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
			JLabel disjointsS = new JLabel(lab);
			p.add(disjointsS);
		}
		if(o.isObsoleteClass(id))
		{
			JLabel obsS = new JLabel("<html><b><font color=\"red\"> Obsolete Class!</font></b></html>");
			p.add(obsS);
		}
		return p;
	}
	
	private JPanel buildIndivDetailPanel(int id)
	{
		JPanel p = new JPanel();
		p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
		
		Ontology o;
		if(source.contains(id))
			o = source;
		else
			o = target;
		//Get the local name
        JLabel localName = new JLabel("<html>Local Name: <i>" +
        		uris.getLocalName(id) + "</i></html>");
        p.add(localName);
        //Labels
        String lab = "<html>Label: " + o.getName(id);
        JLabel label = new JLabel(lab);
        p.add(label);
		
		//Classes
		Set<Integer> classes = rm.getIndividualClasses(id);
		lab = "<html>instanceOf: ";
		for(Integer i : classes)
			lab += "<i>" + o.getName(i) + "</i>; ";
		if(classes.size() == 0)
			lab += "N/A</html>";
		else
			lab = lab.substring(0, lab.length()-2) + "</html>";
		JLabel high = new JLabel(lab);
		p.add(high);
		
		//Relations
		Table2Set<String,String> relMap = new Table2Set<String,String>();
		for(Integer i : rm.getIndividualActiveRelations(id))
			for(Integer j : rm.getIndividualProperties(id, i))
				relMap.add(o.getName(j),o.getName(i));
		Vector<String> rels = new Vector<String>(relMap.keySet());
		Collections.sort(rels);
		for(String r : rels)
		{
			lab = "<html>" + r + ": ";
			for(String s : relMap.get(r))
				lab += "<i>" + s + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
			JLabel ancs = new JLabel(lab);
			p.add(ancs);
		}
		
		//Data Properties
		ValueMap vm = o.getValueMap();
		for(Integer v : vm.getProperties(id))
		{
			lab = "<html>" + o.getName(v) + ": ";
			for(String s : vm.getValues(id, v))
				lab += "<i>" + s + "</i>; ";
			lab = lab.substring(0, lab.length()-2) + "</html>";
			JLabel ancs = new JLabel(lab);
			p.add(ancs);
		}
		return p;
	}
	
	private JPanel buildPropDetailPanel(int id)
	{
		JPanel p = new JPanel();
		p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));

		Ontology o;
		if(source.contains(id))
			o = source;
		else
			o = target;
		Lexicon lex = o.getLexicon();
		
		//Get the local name
        JLabel localName = new JLabel("<html>Local Name: <i>" +
        		uris.getLocalName(id) + "</i></html>");
        p.add(localName);
        //Labels
        String lab = "<html>Label(s): ";
        Set<String> names = lex.getNames(id,LexicalType.LABEL);
		for(String s : names)
			lab += "<i>" + s + "</i>; ";
		if(names.size() == 0)
			lab += "N/A</html>";
		else
			lab = lab.substring(0, lab.length()-2) + "</html>";
        JLabel label = new JLabel(lab);
        p.add(label);
        
		if(t.equals(EntityType.OBJECT))
		{
			Set<Integer> domain = rm.getDomains(id);
			lab = "<html>Domain: ";
			for(Integer i : domain)
				lab += "<i>" + o.getName(i) + "</i>; ";
			if(domain.size() > 0)
				lab = lab.substring(0, lab.length()-2) + "</html>";
			else
				lab += "N/A</html>";
			JLabel domainS = new JLabel(lab);
			p.add(domainS);
			
			Set<Integer> range = rm.getObjectRanges(id);
			lab = "<html>Range: ";
			for(Integer i : range)
				lab += "<i>" + o.getName(i) + "</i>; ";
			if(range.size() > 0)
				lab = lab.substring(0, lab.length()-2) + "</html>";
			else
				lab += "N/A</html>";
			JLabel rangeS = new JLabel(lab);
			p.add(rangeS);
			if(rm.isFunctional(id))
			{
				JLabel funS = new JLabel("Functional Property");
				p.add(funS);
			}
		}
		else if(t.equals(EntityType.DATA))
		{
			Set<Integer> domain = rm.getDomains(id);
			lab = "<html>Domain: ";
			for(Integer i : domain)
				lab += "<i>" + o.getName(i) + "</i>; ";
			if(domain.size() > 0)
				lab = lab.substring(0, lab.length()-2) + "</html>";
			else
				lab += "N/A</html>";
			JLabel domainS = new JLabel(lab);
			p.add(domainS);
			
			Set<String> range = rm.getDataRanges(id);
			lab = "Range: ";
			for(String s : range)
				lab += "<i>" + s + "</i>; ";
			if(range.size() > 0)
				lab = lab.substring(0, lab.length()-2) + "</html>";
			else
				lab += "N/A</html>";
			JLabel rangeS = new JLabel(lab);
			p.add(rangeS);
			if(rm.isFunctional(id))
			{
				JLabel funS = new JLabel("Functional Property");
				p.add(funS);
			}
		}
		return p;
	}
}