package eu.dnetlib.enabling.tools;

import java.io.StringWriter;
import java.util.Date;

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.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import eu.dnetlib.miscutils.datetime.DateUtils;

/**
 * OpaqueResource holding a plain old DOM document.
 *
 * @author marko
 *
 */
public class DOMOpaqueResource implements OpaqueResource {
	/**
	 * xpath expression error message.
	 */
	private static final String XPATH_ERROR = "cannot compile xpath expression";

	/**
	 * value attribute.
	 */
	private static final String VALUE_ATTR = "value";

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

	/**
	 * resource identifier.
	 */
	private String resourceId;

	/**
	 * resource type.
	 */
	private String resourceType;

	/**
	 * resource kind.
	 */
	private String resourceKind;

	/**
	 * resource uri.
	 */
	private String resourceUri;

	/**
	 * modification time stamp.
	 */
	private Date modificationDate;

	/**
	 * original document DOM.
	 */
	private Document dom;

	/**
	 * xslt transformer instance.
	 */
	private Transformer transformer;

	/**
	 * construct a DOMOpaqueInstance from a W3C DOM document.
	 *
	 * @param dom
	 *            DOM document
	 * @throws XPathExpressionException
	 *             happens
	 */
	public DOMOpaqueResource(final Document dom) throws XPathExpressionException {
		this.dom = dom;

		try {
			transformer = TransformerFactory.newInstance().newTransformer();
		} catch (TransformerConfigurationException e) {
			throw new IllegalStateException("transformer configuration", e);
		} catch (TransformerFactoryConfigurationError e) {
			throw new IllegalStateException("transformer configuration", e);
		}

		final XPath xpath = XPathFactory.newInstance().newXPath();

		this.resourceId = xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value", dom);
		this.resourceType = xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_TYPE/@value", dom);
		this.resourceKind = xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_KIND/@value", dom);
		this.resourceUri = xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_URI/@value", dom);

		String modificationDateSource = xpath.evaluate("/RESOURCE_PROFILE/HEADER/DATE_OF_CREATION/@value", dom);

		try {
			this.modificationDate = new DateUtils().parse(modificationDateSource);
		} catch (IllegalStateException e) {
			log.debug("invalid date '" + modificationDateSource + "'", e);
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#asDom()
	 */
	@Override
	public Document asDom() {
		return getDom();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#asString()
	 */
	@Override
	public String asString() {
		final StringWriter writer = new StringWriter();

		try {
			transformer.transform(new DOMSource(getDom()), new StreamResult(writer));
		} catch (TransformerException e) {
			log.fatal("cannot serialize document", e);
			return null;
		}
		return writer.toString();
	}

	public Document getDom() {
		return dom;
	}

	public void setDom(final Document dom) {
		this.dom = dom;
	}

	@Override
	public String getResourceId() {
		return resourceId;
	}

	@Override
	public String getResourceType() {
		return resourceType;
	}

	public void setResourceType(final String resourceType) {
		this.resourceType = resourceType;
	}

	@Override
	public String getResourceKind() {
		return resourceKind;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#setResourceKind(java.lang.String)
	 */
	@Override
	public void setResourceKind(final String resourceKind) {
		try {
			final XPath xpath = XPathFactory.newInstance().newXPath();
			final Element kindEl = (Element) xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_KIND", asDom(), XPathConstants.NODE);
			kindEl.setAttribute(VALUE_ATTR, resourceKind);
			this.resourceKind = resourceKind;
		} catch (XPathExpressionException e) {
			throw new IllegalStateException(XPATH_ERROR, e);
		}

	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#setResourceId(java.lang.String)
	 */
	@Override
	public void setResourceId(final String identifier) {
		try {
			final XPath xpath = XPathFactory.newInstance().newXPath();
			final Element idEl = (Element) xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER", asDom(), XPathConstants.NODE);
			idEl.setAttribute(VALUE_ATTR, identifier);
			resourceId = identifier;
		} catch (XPathExpressionException e) {
			throw new IllegalStateException(XPATH_ERROR, e);
		}
	}

	@Override
	public Date getModificationDate() {
		return modificationDate;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#setModificationDate(java.util.Date)
	 */
	@Override
	public void setModificationDate(final Date modificationDate) {
		try {
			final XPath xpath = XPathFactory.newInstance().newXPath();
			final Element idEl = (Element) xpath.evaluate("/RESOURCE_PROFILE/HEADER/DATE_OF_CREATION", asDom(), XPathConstants.NODE);
			if (idEl == null) {
				log.warn("resource with type " + getResourceType() + " has no date of creation element");
				return;
			}
			idEl.setAttribute(VALUE_ATTR, new DateUtils(modificationDate).getDateAsISO8601String());
			this.modificationDate = modificationDate;
		} catch (XPathExpressionException e) {
			throw new IllegalStateException(XPATH_ERROR, e);
		}

	}

	@Override
	public String getResourceUri() {
		return resourceUri;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.OpaqueResource#setResourceUri(java.lang.String)
	 */
	@Override
	public void setResourceUri(final String resourceUri) {
		try {
			final XPath xpath = XPathFactory.newInstance().newXPath();
			final Element uriEl = (Element) xpath.evaluate("/RESOURCE_PROFILE/HEADER/RESOURCE_URI", asDom(), XPathConstants.NODE);
			uriEl.setAttribute(VALUE_ATTR, resourceUri);
			this.resourceUri = resourceUri;
		} catch (XPathExpressionException e) {
			throw new IllegalStateException(XPATH_ERROR, e);
		}
	}

	public Transformer getTransformer() {
		return transformer;
	}

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

}
