package org.maltparser.parser.algorithm.twoplanar;

import java.util.Stack;

import org.maltparser.core.exception.MaltChainedException;
import org.maltparser.core.symbol.SymbolTable;
import org.maltparser.core.symbol.SymbolTableHandler;
import org.maltparser.core.syntaxgraph.DependencyGraph;
import org.maltparser.core.syntaxgraph.DependencyStructure;
import org.maltparser.core.syntaxgraph.edge.Edge;
import org.maltparser.core.syntaxgraph.node.DependencyNode;
import org.maltparser.parser.ParserConfiguration;
import org.maltparser.parser.ParsingException;
/**
 * @author Carlos Gomez Rodriguez
 *
 */
public class TwoPlanarConfig extends ParserConfiguration {

	
	//Connectedness enforcing
	/*
	public static final int NO_CONNECTEDNESS = 1;
	public static final int REDUCE_ONLY = 2; //connectedness enforced on reduce only
	public static final int FULL_CONNECTEDNESS = 3; //connectedness enforced on shift and reduce
	*/
	
	// Root Handling
	public static final int NORMAL = 1; //root tokens attached to Root with RightArc
	public static final int RELAXED = 2; //root tokens unattached
	
	//Constraints
	public final boolean SINGLE_HEAD = true; //single-head constraint
	public boolean noCoveredRoots = false; //no-covered-roots constraint
	public boolean acyclicity = true; //acyclicity constraint
	
	//public int connectedness = NO_CONNECTEDNESS; //connectedness constraint
	
	public boolean reduceAfterSwitch = false;
	
	
	private Stack<DependencyNode> firstStack;
	private Stack<DependencyNode> secondStack;

	public static final boolean FIRST_STACK = false;
	public static final boolean SECOND_STACK = true;
	
	private boolean activeStack;
	
	private Stack<DependencyNode> input;
	
	private DependencyStructure dependencyGraph;
	
	//root handling: explicitly create links to dummy root or not?
	private int rootHandling;

	//needed to disallow two consecutive switches:
	private int lastAction;
	
	
	public TwoPlanarConfig(SymbolTableHandler symbolTableHandler, String noCoveredRoots , String acyclicity , String reduceAfterSwitch , String rootHandling) throws MaltChainedException {
		super();
		firstStack = new Stack<DependencyNode>();
		secondStack = new Stack<DependencyNode>();
		activeStack = FIRST_STACK;
		input = new Stack<DependencyNode>();
		dependencyGraph = new DependencyGraph(symbolTableHandler);
		setRootHandling(rootHandling);
		setNoCoveredRoots(Boolean.valueOf(noCoveredRoots));
		setAcyclicity(Boolean.valueOf(acyclicity));
		setReduceAfterSwitch(Boolean.valueOf(reduceAfterSwitch));
	}
	
	public void switchStacks()
	{
		activeStack = !activeStack;
	}
	
	public boolean reduceAfterSwitch ()
	{
		return reduceAfterSwitch;
	}
	
	public void setReduceAfterSwitch ( boolean ras )
	{
		reduceAfterSwitch = ras;
	}
	
	public void setLastAction ( int action )
	{
		lastAction = action;
	}
	
	public int getLastAction ( )
	{
		return lastAction;
	}
	
	public boolean getStackActivityState()
	{
		return activeStack;
	}
	
	private Stack<DependencyNode> getFirstStack() {
		return firstStack;
	}
	
	private Stack<DependencyNode> getSecondStack() {
		return secondStack;
	}
	
	public Stack<DependencyNode> getActiveStack() {
		if ( activeStack == FIRST_STACK ) return getFirstStack();
		else return getSecondStack();
	}
	
	public Stack<DependencyNode> getInactiveStack() {
		if ( activeStack == FIRST_STACK ) return getSecondStack();
		else return getFirstStack();
	}
	
	public Stack<DependencyNode> getInput() {
		return input;
	}
	
	public DependencyStructure getDependencyStructure() {
		return dependencyGraph;
	}
	
	public boolean isTerminalState() {
		return input.isEmpty();
	}
	
	private DependencyNode getStackNode(Stack<DependencyNode> stack , int index) throws MaltChainedException {
		if (index < 0) {
			throw new ParsingException("Stack index must be non-negative in feature specification. ");
		}
		if (stack.size()-index > 0) {
			return stack.get(stack.size()-1-index);
		}
		return null;
	}
	
	public DependencyNode getActiveStackNode ( int index ) throws MaltChainedException {
		return getStackNode ( getActiveStack() , index );
	}
	
	public DependencyNode getInactiveStackNode ( int index ) throws MaltChainedException {
		return getStackNode ( getInactiveStack() , index );
	}
	
	public DependencyNode getInputNode(int index) throws MaltChainedException {
		if (index < 0) {
			throw new ParsingException("Input index must be non-negative in feature specification. ");
		}
		if (input.size()-index > 0) {
			return input.get(input.size()-1-index);
		}	
		return null;
	}
	
	public void setDependencyGraph(DependencyStructure source) throws MaltChainedException {
		dependencyGraph.clear();
		for (int index : source.getTokenIndices()) {
			DependencyNode gnode = source.getTokenNode(index);
			DependencyNode pnode = dependencyGraph.addTokenNode(gnode.getIndex());
			for (SymbolTable table : gnode.getLabelTypes()) {
				pnode.addLabel(table, gnode.getLabelSymbol(table));
			}
			
			if (gnode.hasHead()) {
				Edge s = gnode.getHeadEdge();
				Edge t = dependencyGraph.addDependencyEdge(s.getSource().getIndex(), s.getTarget().getIndex());
				
				for (SymbolTable table : s.getLabelTypes()) {
					t.addLabel(table, s.getLabelSymbol(table));
				}
			}
		}
	}
	
	public DependencyStructure getDependencyGraph() {
		return dependencyGraph;
	}
	
	public void initialize(ParserConfiguration parserConfiguration) throws MaltChainedException {
		if (parserConfiguration != null) {
			TwoPlanarConfig planarConfig = (TwoPlanarConfig)parserConfiguration;
			this.activeStack = planarConfig.activeStack;
			Stack<DependencyNode> sourceActiveStack = planarConfig.getActiveStack();
			Stack<DependencyNode> sourceInactiveStack = planarConfig.getInactiveStack();
			Stack<DependencyNode> sourceInput = planarConfig.getInput();
			setDependencyGraph(planarConfig.getDependencyGraph());
			for (int i = 0, n = sourceActiveStack.size(); i < n; i++) {
				getActiveStack().add(dependencyGraph.getDependencyNode(sourceActiveStack.get(i).getIndex()));
			}
			for ( int i =0, n = sourceInactiveStack.size() ; i < n ; i++ ) {
				getInactiveStack().add(dependencyGraph.getDependencyNode(sourceInactiveStack.get(i).getIndex()));
			}
			for (int i = 0, n = sourceInput.size(); i < n; i++) {
				input.add(dependencyGraph.getDependencyNode(sourceInput.get(i).getIndex()));
			}
		} else {
			getActiveStack().push(dependencyGraph.getDependencyRoot());
			getInactiveStack().push(dependencyGraph.getDependencyRoot());
			for (int i = dependencyGraph.getHighestTokenIndex(); i > 0; i--) {
				final DependencyNode node = dependencyGraph.getDependencyNode(i);
				if (node != null) { 
					input.push(node);
				}
			}
		}
	}
	
	
	public int getRootHandling() {
		return rootHandling;
	}
	
	protected void setRootHandling(String rh) throws MaltChainedException {
		if (rh.equalsIgnoreCase("relaxed")) {
			rootHandling = RELAXED;
		} else if (rh.equalsIgnoreCase("normal")) {
			rootHandling = NORMAL;
		} else {
			throw new ParsingException("The root handling '"+rh+"' is unknown");
		}
	}
	
	
	public boolean requiresSingleHead()
	{
		return SINGLE_HEAD;
	}
	
	public boolean requiresNoCoveredRoots()
	{
		return noCoveredRoots;
	}
	
	public boolean requiresAcyclicity()
	{
		return acyclicity;
	}
	
	//does not make much sense to enforce the no-covered-roots constraint in 2-planar parsing, it won't capture some 2-planar structures
	public void setNoCoveredRoots ( boolean value ) {noCoveredRoots = value;}
	
	public void setAcyclicity ( boolean value ) {acyclicity = value;}	
	
	public void clear() throws MaltChainedException {
		dependencyGraph.clear();
		getActiveStack().clear();
		getInactiveStack().clear();
		input.clear();
		historyNode = null;
	}
	
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		TwoPlanarConfig that = (TwoPlanarConfig)obj;
		
		if (getActiveStack().size() != that.getActiveStack().size()) 
			return false;
		if (getInactiveStack().size() != that.getInactiveStack().size()) 
			return false;
		if (input.size() != that.getInput().size())
			return false;
		if (dependencyGraph.nEdges() != that.getDependencyGraph().nEdges())
			return false;
		for (int i = 0; i < getActiveStack().size(); i++) {
			if (getActiveStack().get(i).getIndex() != that.getActiveStack().get(i).getIndex()) {
				return false;
			}
		}
		for (int i = 0; i < getInactiveStack().size(); i++) {
			if (getInactiveStack().get(i).getIndex() != that.getInactiveStack().get(i).getIndex()) {
				return false;
			}
		}
		for (int i = 0; i < input.size(); i++) {
			if (input.get(i).getIndex() != that.getInput().get(i).getIndex()) {
				return false;
			}
		}		
		return dependencyGraph.getEdges().equals(that.getDependencyGraph().getEdges());
	}
	
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		sb.append(getActiveStack().size());
		sb.append(", ");
		sb.append(getInactiveStack().size());
		sb.append(", ");
		sb.append(input.size());
		sb.append(", ");
		sb.append(dependencyGraph.nEdges());
		return sb.toString();
	}
}
