package eu.dnetlib.miscutils.functional.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;

import eu.dnetlib.miscutils.functional.UnaryFunction;

/**
 * This function applies a stylesheet to something which can be transformed to a javax.xml.Source.
 * 
 * <p>Subclasses are specialized and know how to transform K into a Source</p>
 * 
 * @author marko
 *
 * @param <K>
 */
public abstract class AbstractApplyXslt<K> implements UnaryFunction<String, K> {
	private static final String UNKNOWN_XSLT_NAME = "unknown xslt name";

	private static final Log log = LogFactory.getLog(AbstractApplyXslt.class); // NOPMD by marko on 11/24/08 5:02 PM

	private Transformer transformer;

	/**
	 * optional, useful to keep track of xslt name for debugging purposes.
	 */
	private String xsltName;

	public AbstractApplyXslt(final Resource xslt) {
		this(xslt, null);
	}

	public AbstractApplyXslt(final Resource xslt, Map<String, String> parameters) {
		this(new StreamSource(getInputStream(xslt)), getFileName(xslt), parameters);
	}

	public AbstractApplyXslt(final String xslt) {
		this(xslt, UNKNOWN_XSLT_NAME);
	}

	public AbstractApplyXslt(final String xslt, String name) {
		this(xslt, name, null);
	}

	public AbstractApplyXslt(final String xslt, String name, Map<String, String> parameters) {
		this(new StreamSource(new StringReader(xslt)), name, parameters);
	}

	public AbstractApplyXslt(final Source xslt) {
		this(xslt, UNKNOWN_XSLT_NAME);
	}

	public AbstractApplyXslt(final Source xslt, String name) {
		this(xslt, name, null);
	}

	public AbstractApplyXslt(final Source xslt, String name, Map<String, String> parameters) {
		try {
			this.xsltName = name;
			transformer = TransformerFactory.newInstance().newTransformer(xslt);
			
			if(parameters != null)
				for(Map.Entry<String, String> parameter : parameters.entrySet())
					transformer.setParameter(parameter.getKey(), parameter.getValue());
			
		} catch (final TransformerConfigurationException e) {
			throw new IllegalStateException(e);
		} catch (final TransformerFactoryConfigurationError e) {
			throw new IllegalStateException(e);
		}
	}

	@Override
	public String evaluate(final K input) {
		try {
			final StringWriter output = new StringWriter();
			transformer.transform(toStream(input), new StreamResult(output));
			return output.toString();
		} catch (final TransformerException e) {
			log.debug("cannot transform record", e);
			return "";
		}
	}

	public abstract Source toStream(K input);

	/**
	 * Used only to convert checked to unchecked exception.
	 * 
	 * @param xslt
	 * @return
	 */
	private static InputStream getInputStream(final Resource xslt) {
		try {
			return xslt.getInputStream();
		} catch (final IOException e) {
			throw new IllegalArgumentException(e);
		}
	}

	private static String getFileName(Resource xslt) {
		try {
			return xslt.getURL().getPath();
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}

	public Transformer getTransformer() {
		return transformer;
	}

	public void setTransformer(final Transformer transformer) {
		this.transformer = transformer;
	}

	public String getXsltName() {
		return xsltName;
	}

	public void setXsltName(String xsltName) {
		this.xsltName = xsltName;
	}

}
