package cytoscape.data.readers;

import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;

import cytoscape.CyEdge;
import cytoscape.CyNetwork;
import cytoscape.CyNode;
import cytoscape.Cytoscape;
import cytoscape.data.Semantics;


/**
 * Parser for NNF files.
 * 
 * @author kono, ruschein
 */
public class NNFParser {
	// For performance, these fields will be reused.
	private String[] parts;
	private int length;

	// List of root network plus all nested networks.
	private final List<CyNetwork> networks;

	// Hash map from title to actual network
	private Map<String, CyNetwork> networkMap;


	public NNFParser() {
		networkMap = new HashMap<String, CyNetwork>();
		networks = new ArrayList<CyNetwork>();
	}


	/** Returns the first network with title "networkTitle" or null, if there is no network w/ this title.
	 */
	private CyNetwork getNetworkByTitle(final String networkTitle) {
		Set<CyNetwork> networks = Cytoscape.getNetworkSet();
		for (final CyNetwork network : networks) {
			if (network.getTitle().equals(networkTitle))
				return network;
		}

		return null;
	}
	

	/**
	 * Parse an entry/line in an NNF file.
	 * 
	 * @param line
	 */
	public boolean parse(final String line) {
		// Split with white space chars
		parts = splitLine(line);
		length = parts.length;

		CyNetwork network = networkMap.get(parts[0]);
		if (network == null) {
			// Reuse existing networks, if possible:
			network = getNetworkByTitle(parts[0]);
			if (network == null) {
				// Create network without view.  View will be created later in class Cytoscape.
				network = Cytoscape.createNetwork(parts[0], /* create_view = */false);
			}

			networkMap.put(parts[0], network);
			networks.add(network);

			// Attempt to nest network within the node with the same name
			final CyNode parent = Cytoscape.getCyNode(parts[0]);
			if (parent != null) {
				parent.setNestedNetwork(network);
			}
		}

		if (length == 2) {
			final CyNode node = Cytoscape.getCyNode(parts[1], true);
			network.addNode(node);
			final CyNetwork nestedNetwork = networkMap.get(parts[1]);
			if (nestedNetwork != null) {
				node.setNestedNetwork(nestedNetwork);
			}

		} else if (length == 4) {
			final CyNode source = Cytoscape.getCyNode(parts[1], true);
			network.addNode(source);
			CyNetwork nestedNetwork = networkMap.get(parts[1]);
			if (nestedNetwork != null) {
				source.setNestedNetwork(nestedNetwork);
			}

			final CyNode target = Cytoscape.getCyNode(parts[3], true);
			network.addNode(target);
			nestedNetwork = networkMap.get(parts[3]);
			if (nestedNetwork != null) {
				target.setNestedNetwork(nestedNetwork);
			}

			final CyEdge edge = Cytoscape.getCyEdge(source, target, Semantics.INTERACTION, parts[2], /* create = */true,
								/* directed = */true);
			network.addEdge(edge);
		} else {
			// Invalid number of columns.
			return false;
		}

		return true;
	}
	
	private String[] splitLine(final String line) {
		final List<String> parts = new ArrayList<String>();
		boolean escaped = false;
		StringBuilder part = null;
		for (int i = 0; i < line.length(); ++i) {
			final char ch = line.charAt(i);
			if (escaped) {
				escaped = false;
				if (part == null)
					part = new StringBuilder();
				part.append(ch);
			} else if (ch == '\\')
				escaped = true;
			else if (ch == ' ' || ch == '\t') {
				if (part != null) {
					parts.add(part.toString());
					part = null;
				}
			} else {
				if (part == null)
					part = new StringBuilder();
				part.append(ch);
			}
		}

		if (part != null)
			parts.add(part.toString());

		final String[] array = new String[parts.size()];
		return parts.toArray(array);
	}

	protected List<CyNetwork> getNetworks() {
		return networks;
	}
}
