package de.visone.nlpext.network;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import atomicwheel.Node;
import edu.stanford.nlp.parser.lexparser.LexicalizedParser;
import edu.stanford.nlp.trees.Tree;

/**
 * Creates a text network using Word Resonance Analysis (CRA). Nominalphrases
 * (NPs) of each sentence are retrieved (on the highest or the lowest level).
 * All words with important word-tags (nouns and adjectives) within a NP get
 * connected to one another by an edge. Also, within a sentence, the last node
 * of an NP gets connected to the first node of the next NP.
 * 
 * @author nick
 */
public class CRANetwork extends ParserNetwork {

	public static final int NP_MODE_TOP = 0;
	public static final int NP_MODE_BOTTOM = 1;
	public static final int NP_MODE_DEFAULT = NP_MODE_BOTTOM;
	private static final String[] NP_MODE_DESCR = { "Top level", "Lowest level" };

	private int npMode;

	private final Set<String> tagFilter = getDefaultTagSet();

	/** root labels that are used to extract resonance centers */
	private HashSet<String> rootLabels;

	private List<Tree> phrases;

	private Node lastNode;

	// ========== constructor ==========
	public CRANetwork(final LexicalizedParser parser, final String[] labels,
			final String[] stopWords, final boolean levelMode,
			final int wordLength, final boolean layout) {
		this(parser, "no parser description", layout);
		setRootLabels(labels);
		setStopWords(stopWords);
		setNPMode(levelMode ? NP_MODE_BOTTOM : NP_MODE_TOP);
		setMinWordLength(wordLength);
	}

	public CRANetwork(final LexicalizedParser parser, final boolean layout) {
		this(parser, "no parser description", layout);
	}

	/**
	 * Initializes a CRANetwork with a default NP selection strategy.
	 * 
	 * @param layout
	 *            <code>true</code> to do a quick layout after creating the
	 *            network
	 */
	public CRANetwork(final LexicalizedParser parser, final String parserDescr,
			final boolean layout) {
		this(parser, parserDescr, NP_MODE_DEFAULT, layout);
	}

	/**
	 * Initializes a CRANetwork with the specified NP selection strategy.
	 * 
	 * @param NPMode
	 *            one of NP_MODE_TOP, NP_MODE_BOTTOM
	 * @param layout
	 *            <code>true</code> to do a quick layout after creating the
	 *            network
	 * 
	 * @author endele
	 */
	public CRANetwork(final LexicalizedParser parser, final String parserDescr,
			final int npMode, final boolean layout) {
		super(parser, parserDescr, layout);

		if (npMode != NP_MODE_TOP && npMode != NP_MODE_BOTTOM) {
			throw new IllegalArgumentException("NP selection mode " + npMode
					+ " not allowed!");
		}
		if (parser == null)
			throw new IllegalArgumentException("Parser must not be null!");
		this.npMode = npMode;
	}

	@Override
	public void processSentence(final Tree tree) {

		// Get NPs in sentence as root of tree (according to current NP-mode)
		// The reason for not returning the leaves right here is that
		// edu.stanford.nlp.trees.Tree doesn't provide its own parent()-method,
		// i.e. parent(Tree) is a inefficient root-DFS...
		// The parent of a leaf (word) is needed because it contains the tag.

		phrases = new Vector<Tree>();
		switch (npMode) {
		case NP_MODE_TOP:
			getTopNPs(tree);
			break;
		case NP_MODE_BOTTOM:
			getBottomNPs(tree);
			break;
		}

		// pointer to the last node of the previous NP
		// TODO also connect with previous sentence ?
		final boolean connectToPrevSentence = false;
		if (!connectToPrevSentence)
			lastNode = null;

		// iterate over all NP phrases
		for (final Tree phrase : phrases) {

			// get the leaves (words) of this NP-subtree
			final List<Tree> leaves = ParserUtil.getLeaves(phrase,
					new Vector<Tree>(), tagFilter);

			// list of nodes corresponding to these words
			final List<Node> nodes = new Vector<Node>();

			for (final Tree leaf : leaves) {
				final String currentTerm = leaf.toString();
				// if (!ParserUtil.isStopword(currentTerm) &&
				// (currentTerm.length() > 2)) {
				if (!isStopword(currentTerm)
						&& currentTerm.length() >= getMinWordLength()) {

					final Node node = addNode(leaf);
					nodes.add(node);
				}
			}

			if (!nodes.isEmpty()) {

				// TODO should repeated word in a NP contribute only once?
				// ParserUtil.makeUnique(nodes);
				buildClique(nodes);

				// connect last Node of previous NP to first Node of this NP
				// TODO maybe better to connect whole NPs ?
				if (lastNode != null) {
					addEdge(lastNode, nodes.get(0));
				}
				// reassign lastNode
				lastNode = nodes.get(nodes.size() - 1);
			}
		}

	}

	@Override
	protected void finalizeCreation() {
	}

	@Override
	public String getDescription() {
		String descr = "CRA-network with NP selection strategy "
				+ NP_MODE_DESCR[npMode];
		if (parserDescription != null)
			descr = descr + ", used parser: " + parserDescription;
		return descr;
	}

	/**
	 * Sets the mode how to choose the nominal phrases in a sentence.
	 * 
	 * @param mode
	 *            one of NP_MODE_TOP, NP_MODE_BOTTOM
	 */
	public void setNPMode(final int mode) {
		if (mode != NP_MODE_TOP && mode != NP_MODE_BOTTOM) {
			throw new IllegalArgumentException("Illegal NP-mode identifier!");
		}
		npMode = mode;
	}

	/**
	 * sets the labels that are used to extract the resonance centers
	 * 
	 * @param labels
	 */
	public void setRootLabels(final String[] labels) {

		final HashSet<String> newLabels = new HashSet<String>();

		if (labels == null)
			newLabels.add("NP");
		else {
			for (final String label : labels)
				newLabels.add(label);
		}
		rootLabels = newLabels;
	}

	/**
	 * Returns the mode how the nominal phrases in a sentence are chosen.
	 * 
	 * @return one of NP_MODE_TOP, NP_MODE_BOTTOM
	 */
	public int setNPMode() {
		return npMode;
	}

	/**
	 * Returns a HashSet containing standard word-tags. Only words with these
	 * word-tags can become a node label.
	 */
	public static HashSet<String> getDefaultTagSet() {
		final HashSet<String> filter = new HashSet<String>();

		filter.add("FW"); // foreign word
		filter.add("JJ"); // adjective
		filter.add("JJR"); // adjective, comparative
		filter.add("JJS"); // adjective, superlative

		filter.add("NN"); // noun, singular or mass
		filter.add("NNS"); // noun, plural
		filter.add("NNP"); // proper noun, singular
		filter.add("NNPS"); // proper noun, plural

		filter.add("VB"); // Verb, base form
		filter.add("VBD");// Verb, past tense
		filter.add("VBG");// Verb, gerund or present participle
		filter.add("VBN");// Verb, past participle
		filter.add("VBP");// Verb, non-3rd person singular present
		filter.add("VBZ");// Verb, 3rd person singular present

		return filter;
	}

	/**
	 * recursively adds roots of all nominal-phrases (NPs) on the highest level
	 * to phrases
	 */
	private void getTopNPs(final Tree tree) {

		if (tree != null) {
			if (isRightKindOfPhrase(tree.label().value())) {
				phrases.add(tree);
			} else {
				for (final Tree child : tree.children()) {
					getTopNPs(child);
				}
			}
		}
	}

	/**
	 * recursively adds roots of all nominal-phrases (NPs) on the lowest level
	 * to phrases
	 */
	public boolean getBottomNPs(final Tree tree) {
		if (tree == null) {
			return false;
		} else {
			boolean existsLowerNP = false;
			for (final Tree child : tree.children()) {
				existsLowerNP |= getBottomNPs(child);
			}
			final boolean isThisNP = isRightKindOfPhrase(tree.label().value());
			if (isThisNP && !existsLowerNP) {
				// tree.pennPrint(System.out);
				phrases.add(tree);
			}
			return isThisNP || existsLowerNP;
		}
	}

	public boolean isRightKindOfPhrase(final String label) {
		if (rootLabels.contains(label))
			return true;
		else
			return false;
	}
}