package eu.dnetlib.springutils.template;

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

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * Represents a expandable template instantiation. Used to store all the necessary information for the instantiation
 * while waiting a suitable template definition to be parsed.
 * 
 * @author marko
 * 
 */
public class TemplateInstance {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(TemplateInstance.class); // NOPMD by marko on 11/24/08 5:02 PM

	/**
	 * property namespace.
	 */
	private static final String NAMESPACE_P = "http://www.springframework.org/schema/p";

	/**
	 * template short namespace.
	 */
	static final String NAMESPACE_T = "http://dnetlib.eu/springbeans/t";

	/**
	 * template instance spring definition element.
	 */
	private Element element;

	/**
	 * spring parser context.
	 */
	private ParserContext parserContext;

	/**
	 * register a template instantiation.
	 * 
	 * @param element
	 *            element
	 * @param parserContext
	 *            parserContext
	 */
	public TemplateInstance(final Element element, final ParserContext parserContext) {
		super();
		this.element = element;
		this.parserContext = parserContext;
	}
	
	public String getTemplateName() {
		return element.getAttribute("name");
	}

	public void instantiate(BeanTemplate template) {
		log.debug("instantiating template " + template.getName());
		
		final BeanDefinitionParserDelegate delegate = parserContext.getDelegate();

		@SuppressWarnings("unchecked")
		List<Element> beans = (List<Element>) DomUtils.getChildElementsByTagName(template.getRoot(), "bean");
		log.debug("found child elements beans: " + beans);

		for (Element el : beans) {
			Element cel = (Element) el.cloneNode(true);

			expandTemplateParameters(cel, element, template);

			BeanDefinitionHolder holder;
			holder = delegate.parseBeanDefinitionElement(cel);
			BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());

			// necessary because of http://jira.springframework.org/browse/SPR-2955
			delegate.decorateBeanDefinitionIfRequired(cel, holder);
		}

	}

	private void expandTemplateParameters(Element el, Element instance, BeanTemplate template) {
		NamedNodeMap attrs = el.getAttributes();
		final Map<String, String> templateParameters = getTemplateParameters(instance, template);

		for (int i = 0; i < attrs.getLength(); i++) {
			Node node = attrs.item(i);
			Attr attr = (Attr) node;

			if (NAMESPACE_T.equals(attr.getNamespaceURI())) {
				log.debug("found template attribute: " + attr.getLocalName());

				StringTemplate stp = new StringTemplate(attr.getValue());
				stp.setArgumentContext(templateParameters);

				el.setAttribute(attr.getLocalName(), stp.toString());

			} else if (NAMESPACE_P.equals(attr.getNamespaceURI())) {
				log.debug("found potential template property: " + attr.getLocalName());

				if (attr.getValue().contains("$")) {
					StringTemplate stp = new StringTemplate(attr.getValue());
					stp.setArgumentContext(templateParameters);

					el.setAttributeNS(NAMESPACE_P, attr.getLocalName(), stp.toString());
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private Map<String, String> getTemplateParameters(Element instance, BeanTemplate template) {
		Map<String, String> map = new HashMap<String, String>();

		fillParameterMap(map, template.getRoot()); // default
		fillParameterMap(map, instance);

		List<Element> params = (List<Element>) DomUtils.getChildElementsByTagName(instance, "parameter");
		for (Element param : params)
			map.put(param.getAttribute("name"), param.getAttribute("value"));

		return map;
	}

	private void fillParameterMap(Map<String, String> map, Element element) {
		NamedNodeMap attrs = element.getAttributes();

		for (int i = 0; i < attrs.getLength(); i++) {
			Node node = attrs.item(i);
			Attr attr = (Attr) node;
			if (NAMESPACE_T.equals(attr.getNamespaceURI()))
				map.put(attr.getLocalName(), attr.getValue());
		}
	}

	public Element getElement() {
		return element;
	}

	public void setElement(Element element) {
		this.element = element;
	}

	public ParserContext getParserContext() {
		return parserContext;
	}

	public void setParserContext(ParserContext parserContext) {
		this.parserContext = parserContext;
	}
	
}
