/**
 * 
 */
package org.zkoss.zk.ui.select.impl;

import java.util.List;
import java.util.Map;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.sys.ComponentCtrl;

/**
 * A wrapper of Component, providing a context for selector matching algorithm.
 * @since 6.0.0
 * @author simonpai
 */
public class ComponentMatchCtx {

	private ComponentMatchCtx _parent;
	private Component _comp;

	// qualified positions
	private final boolean[][] _qualified;

	// pseudo-class support
	private int _compChildIndex = -1;

	// ::shadow support
	private boolean _isShadowHost = false;

	/*package*/ ComponentMatchCtx(Component component) { // used by root jumping
		_comp = component;
		_qualified = new boolean[0][0];
		_compChildIndex = getComponentIndex();
		_isShadowHost = !((ComponentCtrl) component).getShadowRoots().isEmpty();
	}

	/*package*/ ComponentMatchCtx(Component component, List<Selector> selectorList) { // root
		_comp = component;
		_qualified = new boolean[selectorList.size()][];

		for (Selector selector : selectorList)
			_qualified[selector.getSelectorIndex()] = new boolean[selector.size()];

		_compChildIndex = getComponentIndex();

		_isShadowHost = !((ComponentCtrl) component).getShadowRoots().isEmpty();
	}

	/*package*/ ComponentMatchCtx(Component component, ComponentMatchCtx parent) { // first child
		_comp = component;
		int selectorListSize = parent._qualified.length;
		_qualified = new boolean[selectorListSize][];
		for (int i = 0; i < selectorListSize; i++)
			_qualified[i] = new boolean[parent._qualified[i].length];
		_parent = parent;
		_compChildIndex = 0;

		_isShadowHost = !((ComponentCtrl) component).getShadowRoots().isEmpty();
	}

	// operation //
	/*package*/ void moveToNextSibling() {
		_comp = _comp.getNextSibling();
		_compChildIndex++;
		// ZK-2944: status clearing is conditional, let the caller handle it
		// reset the status when moving to siblings 
		_isShadowHost = !((ComponentCtrl) _comp).getShadowRoots().isEmpty();
	}

	/*package*/ void moveToNextShadowSibling(Component next) {
		_comp = next;
		_compChildIndex++;
		// ZK-2944: status clearing is conditional, let the caller handle it
		// reset the status when moving to siblings 
		_isShadowHost = !((ComponentCtrl) next).getShadowRoots().isEmpty();
	}

	// getter //
	/**
	 * Return the parent context
	 */
	public ComponentMatchCtx getParent() {
		return _parent;
	}

	/**
	 * Return the component.
	 */
	public Component getComponent() {
		return _comp;
	}

	/**
	 * Return the child index of the component. If the component is one of the 
	 * page roots, return -1.
	 */
	public int getComponentChildIndex() {
		if (_compChildIndex > -1)
			return _compChildIndex;
		Component parent = _comp.getParent();
		return parent == null ? -1 : parent.getChildren().indexOf(_comp);
	}

	/**
	 * Return the count of total siblings of the component, including itself.
	 */
	public int getComponentSiblingSize() {
		Component parent = _comp.getParent();
		return parent == null ? _comp.getPage().getRoots().size() : parent.getChildren().size();
	}

	// match position //
	/**
	 * Return true if the component matched the given position of the given 
	 * selector.
	 * @param selectorIndex
	 * @param position
	 */
	public boolean isQualified(int selectorIndex, int position) {
		if (selectorIndex < 0 || selectorIndex >= _qualified.length)
			return false;
		boolean[] posq = _qualified[selectorIndex];
		return position > -1 && position < posq.length && posq[position];
	}

	/*package*/ void setQualified(int selectorIndex, int position) {
		setQualified(selectorIndex, position, true);
	}

	/*package*/ void setQualified(int selectorIndex, int position, boolean qualified) {
		_qualified[selectorIndex][position] = qualified;
	}

	/**
	 * Return true if the component matched the last position of any selectors
	 * in the list. (i.e. the one we are looking for)
	 */
	public boolean isMatched() {
		for (int i = 0; i < _qualified.length; i++)
			if (isMatched(i))
				return true;
		return false;
	}

	/**
	 * Return true if the component matched the last position of the given
	 * selector.
	 * @param selectorIndex
	 */
	public boolean isMatched(int selectorIndex) {
		if (selectorIndex < 0 || selectorIndex >= _qualified.length)
			return false;
		boolean[] quals = _qualified[selectorIndex];
		return quals[quals.length - 1];
	}

	// match local property //
	/**
	 * Return true if the component qualifies the local properties of a given
	 * SimpleSelectorSequence.
	 * @param seq 
	 * @param defs 
	 */
	public boolean match(SimpleSelectorSequence seq, Map<String, PseudoClassDef> defs) {
		return ComponentLocalProperties.match(this, seq, defs);
	}

	/**
	 * Return true if the component is a shadow host, i.e. component's seRoots is not empty
	 */
	public boolean isShadowHost() {
		return this._isShadowHost;
	}

	// helper //
	private int getComponentIndex() {
		Component curr = _comp;
		int index = -1;
		while (curr != null) {
			curr = curr.getPreviousSibling();
			index++;
		}
		return index;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		str(sb, _qualified);
		sb.append(' ');
		for (ComponentMatchCtx c = this; (c = c.getParent()) != null;)
			sb.append("  ");
		sb.append(_comp);
		return sb.toString();
	}

	private static void str(StringBuilder sb, boolean[][] arr) {
		if (arr.length > 1)
			sb.append('[');
		for (int i = 0; i < arr.length; i++) {
			if (i > 0)
				sb.append(',');
			str(sb, arr[i]);
		}
		if (arr.length > 1)
			sb.append(']');
	}

	private static void str(StringBuilder sb, boolean[] arr) {
		sb.append('[');
		for (boolean b : arr)
			sb.append(b ? '1' : '0');
		sb.append(']');
	}

}
