/************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * Licensed 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. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 *
 * 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.dom.element;

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

import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.element.style.StyleStyleElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.OdfStylePropertySet;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.type.StyleNameRef;
import org.w3c.dom.DOMException;

// ToDo: change modifier public to package after refactoring
abstract public class OdfStylableElement extends OdfElement implements
		OdfStylePropertySet {

	private static final long serialVersionUID = -7828513537641758879L;
	// ToDo: Overall StyleRefactoring: DOM Layer reaches to upper layer here...
	private OdfStyle mAutomaticStyle;
	protected OdfStyleFamily mFamily;
	protected OdfName mStyleNameAttrib;
	private OdfSchemaDocument mOdfSchemaDocument;

	/**
	 * Creates a new instance of OdfElementImpl
	 *
	 * @param ownerDocument
	 * @param name
	 * @param family
	 * @param styleNameAttrib
	 * @throws DOMException
	 */
	public OdfStylableElement(OdfFileDom ownerDocument, OdfName name,
			OdfStyleFamily family, OdfName styleNameAttrib) throws DOMException {
		super(ownerDocument, name.getUri(), name.getQName());
		mFamily = family;
		mStyleNameAttrib = styleNameAttrib;
		mOdfSchemaDocument = (OdfSchemaDocument) ownerDocument.getDocument();
	}

	/**
	 * Retrieve or create uniqiue ODF AutomaticStyle
	 *
	 * @return The <code>StyleStyleElement</code> element
	 */
	public StyleStyleElement getOrCreateUnqiueAutomaticStyle() {
		if ((mAutomaticStyle == null)
				|| (mAutomaticStyle.getStyleUserCount() > 1)) {
			// we need a new automatic style
			OdfOfficeAutomaticStyles automatic_styles = getAutomaticStyles();
			if (automatic_styles != null) {
				StyleNameRef sParentStyleName = null;
				if (mAutomaticStyle == null) {
					mAutomaticStyle = automatic_styles.newStyle(getStyleFamily());
					sParentStyleName = new StyleNameRef(getStyleName());
				} else {
					String nameAttr = mAutomaticStyle.getStyleParentStyleNameAttribute();
					sParentStyleName = new StyleNameRef(nameAttr == null ? "" : nameAttr);
					mAutomaticStyle.removeStyleUser(this);
					mAutomaticStyle = automatic_styles.makeStyleUnique(mAutomaticStyle);
				}

				mAutomaticStyle.addStyleUser(this);

				if ((sParentStyleName != null)
						&& (sParentStyleName.toString().length() != 0)) {
					mAutomaticStyle.setStyleParentStyleNameAttribute(sParentStyleName.toString());
				}
				setStyleName(mAutomaticStyle.getStyleNameAttribute());
			}
		}
		return mAutomaticStyle;
	}

	/**
	 * Retrieve ODF OfficeAutomaticStyles
	 *
	 * @return the <code>OdfOfficeAutomaticStyles</code> element that contains
	 *         the automatic style for this element, or null if not available.
	 */
	public OdfOfficeAutomaticStyles getAutomaticStyles() {
		OdfFileDom fileDom = (OdfFileDom) this.ownerDocument;
		if (fileDom != null) {
			if(fileDom instanceof OdfContentDom){
				return ((OdfContentDom) fileDom).getAutomaticStyles();
			}else if(fileDom instanceof OdfStylesDom){
				return ((OdfStylesDom) fileDom).getAutomaticStyles();
			}else
			return null;
		} else {
			return null;
		}
	}

	/**
	 * Set style attribute value with uri and name
	 *
	 * @param uri
	 *            The namespace uri
	 * @param qname
	 *            The qualified name of the attribute
	 * @param value
	 *            The attribute value
	 */
	@Override
	public void setAttributeNS(String uri, String qname, String value) {
		super.setAttributeNS(uri, qname, value);

		// check if style has changed
		if (mStyleNameAttrib.equals(uri, qname)) {
			OdfStyle autoStyle = null;

			// optimization: check if we already know this automatic style
			if ((mAutomaticStyle != null)
					&& (mAutomaticStyle.getStyleNameAttribute().equals(value))) {
				// nothing todo
			} else {
				// register new automatic style
				OdfOfficeAutomaticStyles automatic_styles = getAutomaticStyles();
				if (automatic_styles != null) {
					autoStyle = automatic_styles.getStyle(value,
							getStyleFamily());
				}

				if (mAutomaticStyle != null) {
					mAutomaticStyle.removeStyleUser(this);
				}

				mAutomaticStyle = autoStyle;

				if (mAutomaticStyle != null) {
					mAutomaticStyle.addStyleUser(this);
				}
			}
		}
	}

	/**
	 * Retrieve style name
	 *
	 * @return the style name
	 */
	public String getStyleName() {
		return getAttributeNS(mStyleNameAttrib.getUri(), mStyleNameAttrib.getLocalName());
	}

	/**
	 * Set style name
	 *
	 * @param name
	 *            The style name
	 */
	public void setStyleName(String name) {
		setAttributeNS(mStyleNameAttrib.getUri(), mStyleNameAttrib.getQName(),
				name);
	}

	/**
	 * Retrieve ODF AutomaticStyle
	 *
	 * @return the <code>OdfStyle</code> element
	 */
	public OdfStyle getAutomaticStyle() {
		return mAutomaticStyle;
	}

	/**
	 * Judge if there is an automatic style
	 *
	 * @return true if there is an automatic style
	 */
	public boolean hasAutomaticStyle() {
		return mAutomaticStyle != null;
	}

	/*
	 * public void setLocalStyleProperties(OdfStyle style) { mAutomaticStyle =
	 * style.getAsLocalStyle(); setStyleName(style.getName()); }
	 */
	/**
	 * Returns a DocumentStyle if there is no local style
	 *
	 * @return The <code>OdfStyle</code> element
	 *
	 * */
	public OdfStyle reuseDocumentStyle(String styleName) {
		OdfStyle style = null;
		if (styleName != null) {
			style = mOdfSchemaDocument.getDocumentStyles().getStyle(styleName,
					getStyleFamily());
			if (style != null) {
				setDocumentStyle(style);
			}
		}
		return style;
	}

	/**
	 * Set ODF DocumentStyle
	 *
	 * @param style
	 *            The document style
	 */
	public void setDocumentStyle(OdfStyle style) {
		// when there is a local style, the document style becomes the parent
		// of the local style
		if (mAutomaticStyle != null) {
			mAutomaticStyle.setStyleParentStyleNameAttribute(style.getStyleNameAttribute());
		} else {
			setStyleName(style.getStyleNameAttribute());
		}
	}

	// protected static final String LOCAL_STYLE_PREFIX = "#local-style";

	/*
	 * public OdfStyle newDocumentStyle(String name) { OdfStyle newDocStyle =
	 * mFamily.newStyle(name, mOdfSchemaDocument.getDocumentStyles());
	 * setDocumentStyle(newDocStyle); return newDocStyle; }
	 */
	/**
	 * Retrieve ODF DocumentStyle
	 *
	 * @return the document style
	 */
	public OdfStyle getDocumentStyle() {
		String styleName;
		if (mAutomaticStyle != null) {
			styleName = mAutomaticStyle.getStyleParentStyleNameAttribute();
		} else {
			styleName = getStyleName();
		}

		return mOdfSchemaDocument.getDocumentStyles().getStyle(styleName,
				getStyleFamily());
	}

	/**
	 *
	 * @return true if there is a document style.
	 */
	public boolean hasDocumentStyle() {
		return getDocumentStyle() != null;
	}

	/*
	 * public OdfStyle getAutomaticStyle() { if (mAutomaticStyle == null) {
	 * mAutomaticStyle = mFamily.newStyle(LOCAL_STYLE_PREFIX, null); // if there
	 * is already a document style, but no local style String styleName = null;
	 * if ((styleName = getStyleName()) != null) {
	 * mAutomaticStyle.setParentName(styleName); } } return mAutomaticStyle; }
	 */
	/**
	 * Retrieve ODF style family
	 *
	 * @return the style family.
	 */
	public OdfStyleFamily getStyleFamily() {
		return mFamily;
	}

	/*
	 * public OdfStyle getMergedStyle() { OdfStyle merged = new
	 * OdfStyle("#merged-style", getStyleFamily()); OdfStyle docStyle =
	 * getDocumentStyle(); if (mAutomaticStyle != null) { // a document style
	 * may be referenced indirectly from the local style... if (docStyle ==
	 * null) { docStyle =
	 * mOdfSchemaDocument.getDocumentStyles().getStyle(mAutomaticStyle
	 * .getParentName()); } // copy local style to merged style
	 * mAutomaticStyle.copyTo(merged, true,false); }
	 *
	 * // copy doc style to merged style // copyTo only copies properties that
	 * are not already set at the // target style if (docStyle != null) {
	 * docStyle.copyTo(merged, true,false); }
	 *
	 * return merged; }
	 */
	/**
	 * Retrieve ODF style property
	 *
	 * @param property
	 *            The style property
	 * @return string for a property.
	 */
	public String getProperty(OdfStyleProperty property) {
		// first try automatic style
		StyleStyleElement style = mAutomaticStyle;

		if (style == null) {
			style = getOfficeStyle();
		}

		if (style != null) {
			return style.getProperty(property);
		}

		return null;
	}

	/**
	 * Retrieve the set of ODF style proerties
	 *
	 * @param properties
	 * @return a map of all the properties.
	 */
	public Map<OdfStyleProperty, String> getProperties(
			Set<OdfStyleProperty> properties) {
		HashMap<OdfStyleProperty, String> map = new HashMap<OdfStyleProperty, String>();
		for (OdfStyleProperty property : properties) {
			map.put(property, getProperty(property));
		}

		return map;
	}

	/**
	 * Retrieve the set of strict ODF properties
	 *
	 * @return a set of all the properties from the style family.
	 */
	public Set<OdfStyleProperty> getStrictProperties() {
		return getStyleFamily().getProperties();
	}

	/**
	 * Judge if there is an automatic style with this property
	 *
	 * @param property
	 * @return true if there is an automatic style with this property.
	 */
	public boolean hasProperty(OdfStyleProperty property) {
		return (mAutomaticStyle != null)
				&& mAutomaticStyle.hasProperty(property);
	}

	/**
	 * Remove the ODF property
	 *
	 * @param property
	 */
	public void removeProperty(OdfStyleProperty property) {
		if (mAutomaticStyle != null) {
			mAutomaticStyle.removeProperty(property);
		}
	}

	/**
	 * Set ODF properties
	 *
	 * @param properties
	 */
	public void setProperties(Map<OdfStyleProperty, String> properties) {
		for (Map.Entry<OdfStyleProperty, String> entry : properties.entrySet()) {
			setProperty(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * Set ODF style property with value
	 *
	 * @param property
	 * @param value
	 */
	public void setProperty(OdfStyleProperty property, String value) {
		getOrCreateUnqiueAutomaticStyle().setProperty(property, value);
	}

	@Override
	protected void onInsertNode() {
		super.onInsertNode();

		String stylename = getStyleName();
		if (stylename.length() != 0) {
			if (mAutomaticStyle != null) {
				if (mAutomaticStyle.getStyleNameAttribute().equals(stylename)) {
					return;
				}

				mAutomaticStyle.removeStyleUser(this);
				mAutomaticStyle = null;
			}

			OdfOfficeAutomaticStyles automatic_styles = getAutomaticStyles();
			if (automatic_styles != null) {
				mAutomaticStyle = automatic_styles.getStyle(stylename,
						getStyleFamily());

				if (mAutomaticStyle != null) {
					mAutomaticStyle.addStyleUser(this);
				}
			}
		}
	}

	/**
	 *
	 */
	@Override
	protected void onRemoveNode() {
		super.onInsertNode();

		if (this.mAutomaticStyle != null) {
			this.mAutomaticStyle.removeStyleUser(this);
			this.mAutomaticStyle = null;
		}
	}

	// todo: rename after newName rid of deprecated getDocumentStyle()
	private OdfStyle getOfficeStyle() {
		OdfOfficeStyles styles = this.mOdfSchemaDocument.getDocumentStyles();
		if (styles != null) {
			return styles.getStyle(getStyleName(), getStyleFamily());
		} else {
			return null;
		}
	}
}
