/************************************************************************
*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*
************************************************************************/
package org.odftoolkit.odfdom.incubator.search;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.style.OdfDefaultStyle;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextHeading;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A derived Navigation class used for navigate the mText content it is used to
 * search the document and find the matched style properties and would return
 * TextSelection instance
 *
 */
public class TextStyleNavigation extends Navigation {

	private OdfTextDocument mTextDocument;
	private TextSelection mCurrentSelectedItem;
	private int mCurrentIndex;
	private Map<OdfStyleProperty, String> mProps;
	private String mText;
	private Node mPhNode;
	private int mIndex;
	private Node mNode;

	/**
	 * Construct TextStyleNavigation with style properties condition and navigation scope
	 * @param props	the matched style properties conditions
	 * @param doc	the navigation search scope
	 */
	public TextStyleNavigation(Map<OdfStyleProperty, String> props, OdfTextDocument doc) {
		mTextDocument = doc;
		mCurrentSelectedItem = null;
		this.mProps = props;
	}

	/*
	 * Find next TextSelection which match specified style
	 */
	private TextSelection findnext(TextSelection selected) {
		OdfElement element = null;
		if (selected == null) {

			try {
				mNode = getNextMatchElement((Node) mTextDocument.getContentRoot());
			} catch (Exception ex) {
				Logger.getLogger(TextStyleNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
			}
		} else {
			try {
				mNode = getNextMatchElement(mNode);
			} catch (Exception ex) {
				Logger.getLogger(TextStyleNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
			}
		}
		if (mNode != null) {
			element = (OdfElement) getPHElement(mNode);
			TextSelection item = new TextSelection(mText, element, mCurrentIndex);
			return item;
		}
		return null;
	}

	private Node getPHElement(Node node) {

		//get paragraph or heading element
		if (node instanceof OdfTextParagraph) {
			mPhNode = node;
		} else if (node instanceof OdfTextHeading) {
			mPhNode = node;
		} else {
			getPHElement(node.getParentNode());
		}
		return mPhNode;
	}

	/* (non-Javadoc)
	 * get current TextSelection
	 * @see org.odftoolkit.odfdom.incubator.search.Navigation#getCurrentItem()
	 */
	@Override
	public Selection getCurrentItem() {
		Selection.SelectionManager.registerItem(mCurrentSelectedItem);
		return mCurrentSelectedItem;
	}

	/* (non-Javadoc)
	 * check if has next TextSelection with satisfied style
	 * @see org.odftoolkit.odfdom.incubator.search.Navigation#hasNext()
	 */
	@Override
	public boolean hasNext() {
		mCurrentSelectedItem = findnext(mCurrentSelectedItem);
		return (mCurrentSelectedItem != null);
	}

	/**
	 * check if the element has the specified style properties
	 * @param element	navigate this element
	 * @return true if this element has the specified style properties
	 * 		   false if not match
	 */
	@Override
	public boolean match(Node element) {
		boolean match = false;
		if (element.getNodeType() == Node.TEXT_NODE && !element.getNodeValue().trim().equals("")) {
			if (element.getParentNode() instanceof OdfStylableElement) {
				OdfStylableElement parStyleElement = (OdfStylableElement) element.getParentNode();

				String parStyleName = getStyleName(parStyleElement);

				if (getMatchStyleNames().contains(parStyleName)) {
					match = true;
					mText = element.getNodeValue();
					NodeList nodes = getPHElement(element.getParentNode()).getChildNodes();
					mIndex = 0;
					getIndex(nodes, element);
				}
			}
		}
		return match;
	}

	private void getIndex(NodeList nodes, Node element) {
		for (int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			if (node == element) {
				mCurrentIndex = mIndex;
				break;
			} else {
				if (node.getNodeType() == Node.TEXT_NODE) {
					mIndex = mIndex + node.getNodeValue().length();
				} else if (node.getNodeType() == Node.ELEMENT_NODE) {
					if (node.getLocalName().equals("s")) // mText:s
					{
						try {
							mIndex = mIndex + Integer.parseInt(((Element) node).getAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "c"));
						} catch (Exception e) {
							mIndex++;
						}

					} else if (node.getLocalName().equals("line-break")) {
						mIndex++;
					} else if (node.getLocalName().equals("tab")) {
						mIndex++;
					} else {
						getIndex(node.getChildNodes(), element);
					}
				}
			}
		}
	}

	private String getStyleName(OdfStylableElement element) {
		String stylename = element.getStyleName();
		if (stylename == null) {
			if (element.getParentNode() instanceof OdfStylableElement) {
				getStyleName((OdfStylableElement) element.getParentNode());
			} else {
				stylename = "defaultstyle";
			}
		}
		return stylename;
	}

	private Set<String> getMatchStyleNames() {
		Set<String> styleNames = new HashSet<String>();
		String sname;
		HashMap<String, OdfDefaultStyle> defaultStyles = new HashMap<String, OdfDefaultStyle>();
		try {

			NodeList defStyleList = mTextDocument.getDocumentStyles().getElementsByTagName("style:default-style");
			for (int i = 0; i < defStyleList.getLength(); i++) {
				OdfDefaultStyle defStyle = (OdfDefaultStyle) defStyleList.item(i);
				defaultStyles.put(defStyle.getFamilyName(), defStyle);
			}

			NodeList styleList = mTextDocument.getDocumentStyles().getElementsByTagName("style:style");
			for (int i = 0; i < styleList.getLength(); i++) {
				OdfStyle sStyle = (OdfStyle) styleList.item(i);
				//get default properties and style properties
				Map<OdfStyleProperty, String> map = sStyle.getStylePropertiesDeep();
				//check if properties include all search properties and value equal
				Iterator<OdfStyleProperty> pIter = mProps.keySet().iterator();
				boolean isStyle = false;
				while (pIter.hasNext()) {
					isStyle = false;
					OdfStyleProperty p = pIter.next();
					if (map.containsKey(p)) {
						if (map.get(p).equals(mProps.get(p))) {
							isStyle = true;
						} else {
							break;
						}
					} else {
						break;
					}
				}
				//put all match style names
				if (isStyle) {
					sname = sStyle.getStyleNameAttribute();
					//if(sname.contains("default"))sname="defaultstyle";
					styleNames.add(sname);
				}
			}
			//get all automatic styles
			Iterator<OdfStyle> cStyles = mTextDocument.getContentDom().getAutomaticStyles().getAllStyles().iterator();
			while (cStyles.hasNext()) {
				OdfStyle cStyle = cStyles.next();
				//get default properties and style properties
				Map<OdfStyleProperty, String> map = cStyle.getStylePropertiesDeep();

				if (cStyle.getParentStyle() == null) {
					if (cStyle.getFamilyName().equals("text")) {
						if (defaultStyles.containsKey("text")) {
							getTextDefaultProperties("text", defaultStyles, map);
						} else {
							getTextDefaultProperties("paragraph", defaultStyles, map);
						}
					}
				}

				//check if the search properties is in properties
				Iterator<OdfStyleProperty> pIter = mProps.keySet().iterator();
				boolean isStyle = false;
				while (pIter.hasNext()) {
					isStyle = false;
					OdfStyleProperty p = pIter.next();
					if (map.containsKey(p)) {
						if (map.get(p).equals(mProps.get(p))) {
							isStyle = true;
						} else {
							break;
						}
					} else {
						break;
					}
				}
				//put all match style names
				if (isStyle) {
					styleNames.add(cStyle.getStyleNameAttribute());
				}
			}

		} catch (Exception e1) {
			Logger.getLogger(TextStyleNavigation.class.getName()).log(Level.SEVERE, e1.getMessage(), e1);
		}
		return styleNames;
	}

	private void getTextDefaultProperties(
			String familyName,
			HashMap<String, OdfDefaultStyle> defaultStyles,
			Map<OdfStyleProperty, String> map) {
		OdfDefaultStyle defStyle = defaultStyles.get(familyName);
		if (defStyle != null) {
			OdfStyleFamily family = defStyle.getFamily();
			if (family != null) {
				for (OdfStyleProperty property : family.getProperties()) {
					if (!map.containsKey(property) && defStyle.hasProperty(property)) {
						map.put(property, defStyle.getProperty(property));
					}
				}
			}
		}

	}
}
