package edu.fiu.jtlex.data;

import java.util.Set;
import edu.fiu.jtlex.data.ILink.LinkTag;
import edu.fiu.jtlex.data.ILink.Origintype;
import java.util.HashMap;
import java.util.HashSet;

public class Graph implements IGraph {

	/**
	 * Represents a TimeML Graph: A TimeML temporal graph is a graph T = (V,E)
	 * where: V is a set of temporal events and time expressions. E is a set of
	 * TimeML links: a tuple (u,v,w) where u, v belong to V and w is a link type.
	 * There are 25 link types, refer to @see ILink.java
	 * 
	 * @author Luis Robaina
	 * 
	 *         A graph is represented with two Hash Tables, one for Nodes and one
	 *         for Links.
	 */

	private int LinksCount;
	private int NodesCount;
	// Each Node in the graph is identified by a unique ID number
	private HashMap<Integer, INode> Nodes;
	// Each Link in the graph is identified by a unique ID number
	private HashMap<Integer, ILink> Links;
	// There are two types of subgraphs: MainGraphs and Subordination graphs
	private GraphType graphType;

	public Graph() {

		LinksCount = 0;
		NodesCount = 0;

		Nodes = new HashMap<>();
		Links = new HashMap<>();

	}

	public int getLinksCount() {
		return LinksCount;
	}

	public int getNodesCount() {
		return NodesCount;
	}

	/**
	 * Returns a INode reference if a node with ID NodeID exists in the graph
	 * returns null otherwise.
	 * 
	 * @param NodeID: An Integer ID for a given node.
	 * @return A INode reference if a node with ID NodeID exists in the graph
	 *         returns null otherwise.
	 */
	public INode getNodeByID(int NodeID) {
		return Nodes.get(NodeID);
	}

	/**
	 * 
	 * @param link: The ILink to be added to the TimeML graph.
	 * @return True if the node was added. False if node was not added.
	 */
	public boolean addLink(ILink link) {

		link.getEventInstance().addOutLink(link);
		link.getRelatedToEvent().addInLink(link);

		if (Links.containsKey(link.getLinkID())) {
			// There exists a Link associated with this ID.
			return false;
		}

		Links.put(link.getLinkID(), link);
		LinksCount += 1;
		return true;
	}

	/**
	 * 
	 * @param one      First Node in the Link
	 * @param two      Second Node in the Link
	 * @param linkType The type of Link (There are 25 link types).
	 * @return True if the Link was added to the TimeML graph. False if the Link was
	 *         not added.
	 */
	public boolean addLink(INode NodeOne, INode NodeTwo, LinkTag linktag, ILinkType linkType, String syntax,
			ISignal signal, Origintype orig) {

		// Assert that nodes with IDs NodeOneID and NodeTwoID exists.
		if (NodeOne == null || NodeTwo == null) {
			return false; // Cannot create a Link.
		}

		int newLinkID = (NodeOne.getIdStr() + NodeTwo.getIdStr()).hashCode();

		ILink newLink = new Link(linktag, newLinkID, syntax, NodeOne, signal, NodeTwo, linkType, orig);

		if (Links.containsKey(newLinkID)) {
			// There exists a Link associated with this ID.
			return false;
		}
		Links.put(newLinkID, newLink);
		LinksCount += 1;

		newLink.getEventInstance().addOutLink(newLink);
		newLink.getRelatedToEvent().addInLink(newLink);

		return true;
	}

	/**
	 * Add a Link based of the IDs of the adjacent Nodes.
	 * 
	 * @param one      The ID of the first Node in the Link
	 * @param two      The ID of the second Node in the Link
	 * @param linkType The type of Link (There are 25 link types).
	 * @return True if the Link was added to the TimeML graph. False if the Link was
	 *         not added.
	 */
	public boolean addLink(int NodeOneID, int NodeTwoID, LinkTag linktag, ILinkType linkType, String syntax,
			ISignal signal, Origintype orig) {

		INode NodeOne = getNodeByID(NodeOneID);
		INode NodeTwo = getNodeByID(NodeTwoID);

		return addLink(NodeOne, NodeTwo, linktag, linkType, syntax, signal, orig);
	}

	/**
	 * 
	 * @param link A Link of interest to be checked for in the TimeML graph.
	 * @return True if link exists in the TimeML graph
	 */
	public boolean containsLink(ILink link) {
		if (link.getEventInstance() == null || link.getRelatedToEvent() == null) {
			return false;
		}
		return Links.containsKey(link.getLinkID());

	}

	/**
	 * 
	 * @param link: An ILink to be removed from the TimeML graph.
	 * @return The ILink removed from the TimeML graph, null if there is no matching
	 *         link or if the graph is empty.
	 */
	public ILink removeLink(ILink link) {
		INode source = link.getEventInstance();
		INode destination = link.getRelatedToEvent();
		if (LinksCount == 0) {
			return null;
		}
		// TODO: If the link is removed and the degree of one of the adjacent vertices
		// drops to zero, remove the vertex?
		source.removeOutLink(link);
		destination.removeInLink(link);
		ILink removedLink = Links.remove(link.getLinkID());

		if (removedLink == null) {
			return null;
		}
		LinksCount -= 1;
		return removedLink;
	}

	/**
	 * 
	 * @param node: The INode to be added to the TimeML graph.
	 * @return True if the node was added. False if node was not added.
	 */
	public boolean addNode(INode node) {
		if (node == null) {
			return false;
		}
		// Node already exists.
		if (Nodes.containsKey(node.getId())) {
			return false;
		}
		Nodes.put(node.getId(), node);
		NodesCount += 1;
		return true;
	}

	/**
	 * 
	 * @param ID: Each node in the TimeML graph has to be identified by a unique ID
	 *            number.
	 * @return The INode removed from the TimeML graph, (null) if the INode with ID
	 *         is not in TimeML graph.
	 */
	public INode removeNode(INode node) {
		if (NodesCount == 0) {
			return null;
		}
		NodesCount -= 1;
		return Nodes.remove(node.getId());
	}

	/**
	 * 
	 * @param ID: Each node in the TimeML graph has to be identified by a unique ID
	 *            number.
	 * @return The INode removed from the TimeML graph, (null) if the INode with ID
	 *         is not in TimeML graph or the graph is empty.
	 */
	public INode removeNodeByID(int ID) {
		if (NodesCount == 0) {
			return null;
		}
		INode node = getNodeByID(ID);
		if (node == null) {
			return null;
		}
		NodesCount -= 1;
		return this.Nodes.remove(node.hashCode());

	}

	/**
	 * 
	 * @return A set with all the Links in the TimeML graph, (null) if the TimeML
	 *         graph is empty.
	 */
	public Set<ILink> getLinks() {
		if (LinksCount == 0) {
			return null;
		}
		return new HashSet<ILink>(Links.values());
	}

	/**
	 * @return A set with all the Nodes in the TimeML graph, (null) if the TimeML
	 *         graph is empty.
	 */
	public Set<INode> getNodes() {
		if (NodesCount == 0) {
			return null;
		}
		return new HashSet<INode>(Nodes.values());
	}

	/**
	 * @return A HashMap with keys ALinkType, SLinkType, TLinkType and the
	 *         corresponding number of occurrences of these types in the graph.
	 */
	public HashMap<String, Integer> getLinkTypesCount() {
		HashMap<String, Integer> LinkTypes = new HashMap<String, Integer>();

		int ALinks = 0;
		int SLinks = 0;
		int TLinks = 0;

		for (ILink L : Links.values()) {
			if (L.getRelType() instanceof ILink.ALinkType) {
				ALinks++;
			} else if (L.getRelType() instanceof ILink.SLinkType) {
				SLinks++;
			} else if (L.getRelType() instanceof ILink.TLinkType) {
				TLinks++;
			}
		}
		LinkTypes.put("ALinkType", ALinks);
		LinkTypes.put("SLinkType", SLinks);
		LinkTypes.put("TLinkType", TLinks);

		return LinkTypes;
	}

	public GraphType getGraphType() {
		return graphType;
	}

	public void setGraphType(GraphType graphType) {
		this.graphType = graphType;
	}
	/**
	 * 
	 * @return A clone of the current graph. The purpose of this method is to minimize side effects in methods
	 * implemented in the other helper classes.
	 */
	public Graph clone() {
		Graph TimeMLGraph = new Graph();
		Set<INode> events = this.getNodes();
		Set<ILink> time_links = this.getLinks();
		
		for (INode event: events) {
			TimeMLGraph.addNode(event);
		}
		for (ILink time_link: time_links) {
			TimeMLGraph.addLink(time_link);
		}
		
		return TimeMLGraph;
	}

}

enum GraphType {
	MainGraph, SubbordinationGraph
}
