package at.ac.tuwien.dsg.scaledom;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

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

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import at.ac.tuwien.dsg.scaledom.io.ReaderFactory;
import at.ac.tuwien.dsg.scaledom.lazy.LazyLoadingStrategy;
import at.ac.tuwien.dsg.scaledom.parser.XmlParser;
import at.ac.tuwien.dsg.scaledom.util.ComponentFactory;

/**
 * ScaleDOM implementation of the <code>DocumentBuilderFactory</code>. Utilizes the <code>attributes</code> map for
 * configuration, see {@link ScaleDom} for possible configuration options.<br/>
 * Warnings:
 * <ul>
 * <li>DocumentBuilderFactory.isXIncludeAware is not supported</li>
 * <li>DocumentBuilderFactory.schema is not supported</li>
 * <li>http://javax.xml.XMLConstants/feature/secure-processing feature has no impact</li>
 * </ul>
 * 
 * @author Dominik Rauch
 * @see DocumentBuilderFactory
 * @see ScaleDom
 */
public class ScaleDomDocumentBuilderFactory extends DocumentBuilderFactory {

	private final Map<String, Object> attributes;
	private final Map<String, Boolean> features;

	/**
	 * Default constructor.
	 */
	public ScaleDomDocumentBuilderFactory() {
		attributes = new HashMap<String, Object>();
		features = new HashMap<String, Boolean>();

		// Add features which are required to be supported
		features.put(XMLConstants.FEATURE_SECURE_PROCESSING, false);

		// Add all configuration options using defaults as initial values
		attributes.put(ScaleDom.ATTRIBUTE_XMLPARSER_IMPLEMENTATION, ScaleDom.DEFAULT_XMLPARSER_IMPLEMENTATION);
		attributes.put(ScaleDom.ATTRIBUTE_READERFACTORY_IMPLEMENTATION, ScaleDom.DEFAULT_READERFACTORY_IMPLEMENTATION);
		attributes.put(ScaleDom.ATTRIBUTE_LAZYLOADINGSTRATEGY_IMPLEMENTATION,
				ScaleDom.DEFAULT_LAZYLOADINGSTRATEGY_IMPLEMENTATION);
		attributes.put(ScaleDom.ATTRIBUTE_DEFAULTENCODING, ScaleDom.DEFAULT_DEFAULTENCODING);
	}

	@Override
	@SuppressWarnings("unchecked")
	public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
		try {
			final ComponentFactory componentFactory = new ComponentFactory();

			// Bind XmlParser instance
			componentFactory.bind(XmlParser.class,
					(Class<? extends XmlParser>) attributes.get(ScaleDom.ATTRIBUTE_XMLPARSER_IMPLEMENTATION), this);
			// Bind ReaderFactory implementation type
			componentFactory.bind(ReaderFactory.class,
					(Class<? extends ReaderFactory>) attributes.get(ScaleDom.ATTRIBUTE_READERFACTORY_IMPLEMENTATION));
			// Bind LazyLoadingStrategy implementation type
			componentFactory.bind(LazyLoadingStrategy.class, (Class<? extends LazyLoadingStrategy>) attributes
					.get(ScaleDom.ATTRIBUTE_LAZYLOADINGSTRATEGY_IMPLEMENTATION));

			final String defaultEncoding = (String) attributes.get(ScaleDom.ATTRIBUTE_DEFAULTENCODING);

			return new ScaleDomDocumentBuilder(componentFactory, defaultEncoding);
		} catch (final InstantiationException ex) {
			throw new ParserConfigurationException("Component 'XmlParser' could not be instantiated.");
		}
	}

	@Override
	public Object getAttribute(final String name) throws IllegalArgumentException {
		checkNotNull(name, "Argument name must not be null.");
		checkArgument(attributes.containsKey(name), "Attribute '%s' is not recognized.", name);

		return attributes.get(name);
	}

	@Override
	public void setAttribute(final String name, final Object value) throws IllegalArgumentException {
		checkNotNull(name, "Argument name must not be null.");
		checkArgument(attributes.containsKey(name), "Attribute '%s' is not recognized.", name);
		checkNotNull(value, "Argument value must not be null.");

		attributes.put(name, value);
	}

	@Override
	public boolean getFeature(final String name) throws ParserConfigurationException {
		checkNotNull(name, "Argument name must not be null.");
		if (!features.containsKey(name)) {
			throw new ParserConfigurationException("Feature '" + name + "' is not supported.");
		}

		return features.get(name);
	}

	@Override
	public void setFeature(final String name, final boolean value) throws ParserConfigurationException {
		checkNotNull(name, "Argument name must not be null.");
		if (!features.containsKey(name)) {
			throw new ParserConfigurationException("Feature '" + name + "' is not supported.");
		}
		checkNotNull(value, "Argument value must not be null.");

		features.put(name, value);
	}
}
