package eu.dnetlib.actionmanager.actions;

import java.io.StringReader;
import java.util.List;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.DocumentResult;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.SAXReader;

import com.google.common.collect.Lists;

import eu.dnetlib.actionmanager.ActionManagerConstants;
import eu.dnetlib.actionmanager.ActionManagerConstants.ACTION_TYPE;
import eu.dnetlib.actionmanager.common.Agent;
import eu.dnetlib.actionmanager.common.Operation;
import eu.dnetlib.actionmanager.common.Provenance;
import eu.dnetlib.actionmanager.hbase.HBasePutFactory;
import eu.dnetlib.actionmanager.rmi.ActionManagerException;

public class XsltInfoPackageAction extends AbstractAction {

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

	private Operation operation;
	private String infoPackage;
	private Provenance provenance;
	private String nsprefix;
	private String trust;
	private ActionFactory actionFactory;
	private Transformer transformer;
	private final List<Put> puts = Lists.newArrayList();

	/*
	 * protected XsltInfoPackageAction(final String set, final Agent agent, final Operation operation, final Provenance provenance, final
	 * String trust, final String nsprefix, final String infoPackage, final Transformer transformer, final ActionFactory actionFactory) {
	 * super(ACTION_TYPE.pkg, set, agent);
	 * 
	 * this.operation = operation; this.infoPackage = infoPackage; this.actionFactory = actionFactory; this.transformer = transformer;
	 * 
	 * this.setPuts(asPutOperations()); }
	 */

	public XsltInfoPackageAction(final String rawSet, final Agent agent, final Operation operation, final String infoPackage, final Provenance provenance,
			final String nsprefix, final String trust, final Transformer transformer, final ActionFactory actionFactory) {
		super(ACTION_TYPE.pkg, rawSet, agent);

		this.transformer = transformer;
		this.operation = operation;
		this.infoPackage = infoPackage;
		this.provenance = provenance;
		this.nsprefix = nsprefix;
		this.trust = trust;
		this.actionFactory = actionFactory;
		this.transformer = transformer;
	}

	public List<Put> asPutOperations() throws ActionManagerException {
		if (puts.isEmpty()) {
			final List<String> partIds = Lists.newArrayList();
			try {
				for (AtomicAction action : calculateAtomicActions()) {
					if ((action != null) && action.isValid()) {
						puts.addAll(action.asPutOperations(getRowKey(), provenance, trust, nsprefix));
						partIds.add(action.getRowKey());
					}
				}
				puts.add(prepareMetaPut(partIds));
			} catch (Exception e) {
				log.error("Error generating actions", e);
				throw new ActionManagerException(e);
			}
		}
		return puts;
	}

	private Document applyXslt(final String xml, final Provenance provenance, final String trust, final String nsprefix) throws DocumentException,
			TransformerException {
		final Document doc = (new SAXReader()).read(new StringReader(xml));

		final DocumentResult result = new DocumentResult();

		transformer.setParameter("trust", trust);
		transformer.setParameter("provenance", provenance.toString());
		transformer.setParameter("namespaceprefix", nsprefix);

		transformer.transform(new DocumentSource(doc), result);

		return result.getDocument();
	}

	protected List<AtomicAction> calculateAtomicActions() {
		final List<AtomicAction> list = Lists.newArrayList();

		try {
			Document doc = applyXslt(getInfoPackage(), provenance, trust, nsprefix);
			for (Object o : doc.selectNodes("//ACTION")) {
				list.add(createAtomicAction((Element) o));
			}
		} catch (Exception e) {
			log.error("Error generating actions", e);
		}

		return list;
	}

	private AtomicAction createAtomicAction(final Element elem) {
		String key = elem.valueOf("./@targetKey");
		String colFamily = elem.valueOf("./@targetColumnFamily");
		String col = elem.valueOf("./@targetColumn");
		String value64 = elem.getTextTrim();
		byte[] value = value64.isEmpty() ? null : Base64.decodeBase64(value64);

		return getActionFactory().createAtomicAction(getRawSet(), getAgent(), key, colFamily, col, value);
	}

	private Put prepareMetaPut(final List<String> partIds) {
		final Put put = HBasePutFactory.createPutOperation(getRowKey(), getRawSet(), getAgent());

		put.add(ActionManagerConstants.OPERATION_COLFAMILY, Bytes.toBytes(getOperation().toString()), Bytes.toBytes(getOperation().toString()));
		put.add(ActionManagerConstants.ACTION_COLFAMILY, Bytes.toBytes(getActionType().toString()), getBytesContent(infoPackage));

		for (String id : partIds) {
			put.add(ActionManagerConstants.RELATION_COLFAMILY, Bytes.toBytes(id), ActionManagerConstants.HASPARTS);
		}
		return put;
	}

	public Operation getOperation() {
		return operation;
	}

	public void setOperation(final Operation operation) {
		this.operation = operation;
	}

	public ActionFactory getActionFactory() {
		return actionFactory;
	}

	public void setActionFactory(final ActionFactory actionFactory) {
		this.actionFactory = actionFactory;
	}

	protected byte[] getBytesContent(final String content) {
		return Bytes.toBytes(content);
	}

	public String getInfoPackage() {
		return infoPackage;
	}

	public void setInfoPackage(final String infoPackage) {
		this.infoPackage = infoPackage;
	}

	public Provenance getProvenance() {
		return provenance;
	}

	public void setProvenance(final Provenance provenance) {
		this.provenance = provenance;
	}

	public String getTrust() {
		return trust;
	}

	public void setTrust(final String trust) {
		this.trust = trust;
	}

	public Transformer getTransformer() {
		return transformer;
	}

	public String getNsprefix() {
		return nsprefix;
	}

	public void setNsprefix(final String nsprefix) {
		this.nsprefix = nsprefix;
	}

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

}
