/*
 * Copyright (c) 2009-2010 Clark & Parsia, LLC. <http://www.clarkparsia.com>
 *
 * 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
 *
 * 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 com.clarkparsia.empire.config.io.impl;

import com.clarkparsia.empire.config.io.ConfigReader;
import com.clarkparsia.empire.config.EmpireConfiguration;
import com.clarkparsia.empire.EmpireException;
import com.clarkparsia.empire.util.EmpireAnnotationProvider;
import com.clarkparsia.empire.util.BeanReflectUtil;

import java.io.InputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

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

/**
 * <p>ConfigReader implementation for reading configuration information from simple XML files.</p>
 *
 * @author 	Michael Grove
 * @since 	0.6.4
 * @version 0.6.5
 */
public class XmlConfigReader implements ConfigReader {
	/**
	 * The logger
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(ConfigReader.class.getName());

	private static final String NODE_ANNOTATION_PROVIDER = "annotationProvider";
	private static final String NODE_UNIT = "unit";

	private static final String NODE_NAME = "name";
	private static final String NODE_FACTORY = "factory";

	/**
	 * @inheritDoc
	 */
	public EmpireConfiguration read(final InputStream theStream) throws IOException, EmpireException {
		return read(new InputStreamReader(theStream));
	}

	/**
	 * @inheritDoc
	 */
	public EmpireConfiguration read(final Reader theStream) throws IOException, EmpireException {
		try {
			Document aDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(theStream));

			Map<String, String> aGeneralConfig = new HashMap<String, String>();
			Map<String, Map<String, String>> aUnitConfig = new HashMap<String, Map<String, String>>();

			Class<EmpireAnnotationProvider> aProvider = null;

			NodeList aEmpireChildren = aDoc.getDocumentElement().getChildNodes();
			for (int aIndex = 0; aIndex < aEmpireChildren.getLength(); aIndex++) {
				Node aChild = aEmpireChildren.item(aIndex);

				if (aChild.getNodeName().equals(NODE_ANNOTATION_PROVIDER)) {
					String aValue = aChild.getTextContent();

					try {
						aProvider = (Class<EmpireAnnotationProvider>) BeanReflectUtil.loadClass(aValue);
					}
					catch (ClassNotFoundException e) {
						LOGGER.warn("Annotation provider implementation '{}' cannot be found, please check your classpath.", aValue);
					}
					catch (ClassCastException e) {
						LOGGER.warn("Specified annotation provider implementation '{}' is not a valid EmpireAnnotationProvider.", aValue);
					}
				}
				else if (aChild.getNodeName().equals(NODE_UNIT)) {
					Map<String, String> aUnit = unitFromNode(aChild);

					aUnitConfig.put(aUnit.get(NODE_NAME), aUnit);
				}
				else {
					aGeneralConfig.put(aChild.getNodeName(), aChild.getTextContent());
				}
			}

			EmpireConfiguration aConfig = new EmpireConfiguration(aGeneralConfig, aUnitConfig);
			aConfig.setAnnotationProvider(aProvider);

			return aConfig;
		}
		catch (ParserConfigurationException e) {
			throw new IOException(e);
		}
		catch (SAXException e) {
			throw new IOException(e);
		}
	}

	private Map<String, String> unitFromNode(final Node theUnitNode) {
		Map<String, String> aUnit = new HashMap<String, String>();

		NodeList aChildren = theUnitNode.getChildNodes();
		for (int aIndex = 0; aIndex < aChildren.getLength(); aIndex++) {
			Node aChild = aChildren.item(aIndex);

			aUnit.put(aChild.getNodeName(), aChild.getTextContent());
		}

		return aUnit;
	}

	public static void main(String[] args) {
		String s = "<empire>\n" +
				   "	<annotationProvider>com.clarkparsia.empire.util.PropertiesAnnotationProvider</annotationProvider>\n" +
			   	   "	<foo>bar</foo>\n" +
				   "	<baz>biz</baz>\n" +
				   "\n" +
				   "	<unit>\n" +
			   	   "		<name>context1</name>\n" +
			       "		<factory>com.clarkparsia.empire.sesame.RepositoryDataSourceFactory</factory>\n" +
			       "		<url>http://localhost:8080/openrdf-sesame/</url>\n" +
			       "		<repo>mem-rdf-db</repo>\n" +
				   "	</unit>\n" +
			       "\n" +
				   "	<unit>\n" +
			       "		<name>context2</name>\n" +
			       "		<factory>com.clarkparsia.empire.jena.JenaTestDataSourceFactory</factory>\n" +
			       "		<files>foo, bar, baz</files>\n" +
			       "	</unit>\n" +
			       "</empire>";
	}
}
