package eu.dnetlib.enabling.is.lookup;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.jws.WebService;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Required;
import org.xml.sax.SAXException;

import com.sun.xml.messaging.saaj.util.Base64;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.store.rmi.ISStoreException;
import eu.dnetlib.enabling.is.store.rmi.ISStoreService;
import eu.dnetlib.enabling.tools.AbstractBaseService;
import eu.dnetlib.enabling.tools.CompatResourceIdentifierResolverImpl;
import eu.dnetlib.enabling.tools.OpaqueResource;
import eu.dnetlib.enabling.tools.ResourceIdentifierResolver;
import eu.dnetlib.enabling.tools.ResourceType;
import eu.dnetlib.enabling.tools.StringOpaqueResource;
import eu.dnetlib.enabling.tools.XQueryUtils;

/**
 * ISLookUpService implementation.
 * 
 * @author marko
 * @author michele
 * 
 */
@WebService(targetNamespace = "http://services.dnetlib.eu/")
public class ISLookUpServiceImpl extends AbstractBaseService implements ISLookUpService { // NOPMD

	/**
	 * error message when the given collection cannot be fetched.
	 */
	private static final String COLLECTION_ERROR = "cannot get collection";

	/**
	 * base xmldb directory.
	 */
	private static final String DB_BASE_DIR = "/db/DRIVER";

	/**
	 * error message when the profile is not present in the db.
	 */
	private static final String PROFILE_NOT_FOUND = "Profile not found";

	private ISStoreService isStore;

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

	/**
	 * resource identifier resolver. Resolves identifiers to xmldb file and collection names.
	 */
	private ResourceIdentifierResolver resIdManager = new CompatResourceIdentifierResolverImpl();

	/**
	 * xquery utils. Used to obtain the xmldb collection mapping for resources.
	 */
	private XQueryUtils xqueryUtils;

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#flushCachedResultSets()
	 */
	@Override
	public Boolean flushCachedResultSets() {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#retrieveCollection(java.lang.String)
	 */
	@Override
	public String retrieveCollection(final String profId) throws ISLookUpException {
		try {
			String profile = this.getResourceProfile(profId);
			final List<String> list = quickSearchProfile("for $x in collection('/db/DRIVER/CollectionDSResources') where $x//FATHER/@id = '" + profId
					+ "' return $x//RESOURCE_IDENTIFIER/@value/string()");

			if (!list.isEmpty()) {
				final SAXReader reader = new SAXReader();
				final Document doc = reader.read(new StringReader(profile));
				final Element childrenNode = (Element) doc.selectSingleNode("//CHILDREN"); // NOPMD
				for (final String idC : list) {
					childrenNode.addElement("CHILD").addAttribute("id", idC);
				}
				profile = doc.asXML();
			}

			return profile;
		} catch (final Exception e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#getCollection(java.lang.String, java.lang.String)
	 */
	@Override
	public String getCollection(final String profId, final String format) throws ISLookUpException {
		OpaqueResource prof;
		try {
			prof = new StringOpaqueResource(profId);

			final StringBuilder query = new StringBuilder();
			if (format != null && format.equals("ui")) {
				query.append("doc('");
				query.append(xqueryUtils.getCollectionAbsPath(prof));
				query.append('/');
				query.append(prof.getResourceId().split("_")[0]);
				query.append("')");
			} else {
				query.append("let $x := doc('");
				query.append(xqueryUtils.getCollectionAbsPath(prof));
				query.append('/');
				query.append(prof.getResourceId().split("_")[0]);
				query.append("') return <COLLECTION name='{$x//NAME}' id='{$x//RESOURCE_IDENTIFIER/@value/string()}'>");
				query.append("<STATUS private='{$x//PRIVATE}' visible='{$x//VISIBLE}' container='{$x//CONTAINER}'");
				query.append("count_docs='{$x//COUNT_DOCS/@number/string()}' last_update='{$x//COUNT_DOCS/@last_update/string()}' />");
				query.append("{$x//IMAGE_URL}{$x//DESCRIPTION}{$x//OWNER}{$x//FATHER}{$x//SUBJECT}{$x//CHILDREN}{$x//MEMBERSHIP_CONDITION}{$x//RETRIEVAL_CONDITION}</COLLECTION>");
			}

			return isStore.getXMLbyQuery(query.toString());
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		} catch (final XPathExpressionException e) {
			throw new ISLookUpException(COLLECTION_ERROR, e);
		} catch (final SAXException e) {
			throw new ISLookUpException(COLLECTION_ERROR, e);
		} catch (final IOException e) {
			throw new ISLookUpException(COLLECTION_ERROR, e);
		} catch (final ParserConfigurationException e) {
			throw new ISLookUpException(COLLECTION_ERROR, e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#getResourceProfile(java.lang.String)
	 */
	@Override
	public String getResourceProfile(final String profId) throws ISLookUpException {
		if (profId == null || profId.isEmpty()) { throw new ISLookUpException("Invalid null profile ID: " + profId); }

		try {
			final String res = isStore.getXML(getFileNameForId(profId),
					xqueryUtils.getRootCollection() + getFileCollForId(profId));
			if (res == null) { throw new ISLookUpDocumentNotFoundException("document " + profId + " not found"); }
			return res;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * obtain the xmldb file name from the profile identifier.
	 * 
	 * @param profId
	 *            profile id
	 * @return xml db file name
	 */
	String getFileNameForId(final String profId) {
		return resIdManager.getFileName(profId);
	}

	/**
	 * obtain the xmldb collection name from the profile identifier.
	 * 
	 * @param profId
	 *            profile id
	 * @return plaintext xmldb collection name
	 */
	String getFileCollForId(final String profId) {
		return resIdManager.getCollectionName(profId);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#getResourceProfileByQuery(java.lang.String)
	 */
	@Override
	public String getResourceProfileByQuery(final String xquery) throws ISLookUpException {
		String resource;
		//TODO remove following hack as soon as https://issue.openaire.research-infrastructures.eu/issues/2774 is closed.
		if (StringUtils.isNotBlank(xquery) && xquery.trim().startsWith("//*[local-name() = 'complexType'")) {
			final Pattern p = Pattern.compile(".*value = '([a-zA-z]+)']");
			final Matcher m = p.matcher(xquery);
			if (m.matches()) {
				final String serviceName = m.group(1);
				log.debug(String.format("found service '%s', hacking response for xquery: %s", serviceName, xquery));
				return getResourceTypeSchema(serviceName);
			}
		}
		try {
			resource = isStore.getXMLbyQuery(xquery);
			if (resource == null || resource.isEmpty()) { throw new ISLookUpDocumentNotFoundException(PROFILE_NOT_FOUND); }
			return resource;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#getResourceQoSParams(java.lang.String)
	 */
	@Override
	public String getResourceQoSParams(final String profId) throws ISLookUpException {
		final String query = "for $x in collection('/db/DRIVER/ServiceResources') where $x//RESOURCE_IDENTIFIER/@value = '" + profId + "' return $x//QOS";
		return getResourceProfileByQuery(query);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#getResourceTypeSchema(java.lang.String)
	 */
	@Override
	public String getResourceTypeSchema(final String resourceType) throws ISLookUpException {
		if (resourceType == null || resourceType.isEmpty()) { throw new ISLookUpException("Invalid resourceType"); }

		try {
			final String resource = isStore.getXML(resourceType, xqueryUtils.getRootCollection() + ResourceType.RESOURCE_TYPES);
			if (resource == null || resource.isEmpty()) { throw new ISLookUpDocumentNotFoundException(PROFILE_NOT_FOUND); }
			return resource;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listCollections(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public W3CEndpointReference listCollections(final String format, final String idfatherParam, final String owner) throws ISLookUpException {
		String idfather = idfatherParam; // PMD
		if (idfather == null || idfather.length() == 0) {
			idfather = "InfoSpace";
		}
		final String fileColl = xqueryUtils.getRootCollection() + "CollectionDSResources/CollectionDSResourceType";

		final StringBuilder query = new StringBuilder();
		query.append("for $x in collection('" + fileColl + "') where $x//FATHER[@id='" + idfather + "'] "); // NOPMD
		if (owner != null && owner.length() > 0) {
			query.append("and $x//OWNER[@id='" + owner + "'] ");
		}
		if (format != null && format.equals("short")) {
			query.append("return $x//RESOURCE_IDENTIFIER/@value/string()");
		} else {
			query.append("return $x");
		}

		return searchProfile(query.toString());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listCommunities()
	 */
	@Override
	public W3CEndpointReference listCommunities() throws ISLookUpException {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listDHNIDs()
	 */
	@Override
	public List<String> listDHNIDs() throws ISLookUpException {
		final String fileColl = DB_BASE_DIR + "/InfrastructureResources/DRIVERHostingNodeDSResourceType";

		final ArrayList<String> ret = new ArrayList<String>();
		try {
			for (final String i : isStore.getFileNames(fileColl)) {
				ret.add(i + ":" + fileColl);
			}
			return ret;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listMDStores(java.lang.String)
	 */
	@Override
	public W3CEndpointReference listMDStores(final String metadataformat) throws ISLookUpException {
		final String fileColl = xqueryUtils.getRootCollection() + "MDStoreDSResources/MDStoreDSResourceType";

		String query;
		if (metadataformat != null && metadataformat.length() > 0) {
			query = "for $x in collection('" + fileColl + "') where $x//METADATA_FORMATS/METADATA_FORMAT[@name='" + metadataformat + "'] return $x";
		} else {
			query = "for $x in collection('" + fileColl + "') return $x";
		}

		return searchProfile(query);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listResourceProfiles(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public W3CEndpointReference listResourceProfiles(final String resourceKind, final String format, final String resourceType) throws ISLookUpException {
		String query; // NOPMD - pmd telling nonsense here

		if (format != null && format.equals("short")) {
			query = "for $x in collection('" + xqueryUtils.getRootCollection() + resourceKind + "/" + resourceType
					+ "')/RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value return $x/string()";
		} else {
			query = "for $x in collection('" + xqueryUtils.getRootCollection() + resourceKind + "/" + resourceType + "') return $x";
		}

		return searchProfile(query);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listResourceTypes()
	 */
	@Override
	public List<String> listResourceTypes() throws ISLookUpException {
		try {
			return isStore.getFileNames(DB_BASE_DIR + "/" + ResourceType.RESOURCE_TYPES);
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listServiceIDs(java.lang.String)
	 */
	@Override
	public List<String> listServiceIDs(final String serviceType) throws ISLookUpException {
		final String fileColl = "ServiceResources/" + serviceType;
		final String encodedColl = "_" + new String(Base64.encode(fileColl.getBytes())); // NOPMD

		final List<String> ret = new ArrayList<String>();
		try {
			for (final String i : isStore.getFileNames(DB_BASE_DIR + "/" + fileColl)) {
				ret.add(i + encodedColl);
			}
			return ret;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listServiceTypes()
	 */
	@Override
	public List<String> listServiceTypes() throws ISLookUpException {
		final List<String> ret = new ArrayList<String>();
		try {
			for (final String i : isStore.getFileColls()) {
				if (i.startsWith("ServiceResources/")) {
					ret.add(i.substring("ServiceResources/".length()));
				}
			}
			return ret;
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#quickSearchProfile(java.lang.String)
	 */
	@Override
	public List<String> quickSearchProfile(final String xquery) throws ISLookUpException {
		try {
			return isStore.quickSearchXML(xquery);
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#searchProfile(java.lang.String)
	 */
	@Override
	public W3CEndpointReference searchProfile(final String xquery) throws ISLookUpException {
		try {
			return isStore.searchXML(xquery);
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

	public ResourceIdentifierResolver getResIdManager() {
		return resIdManager;
	}

	public void setResIdManager(final ResourceIdentifierResolver resIdManager) {
		this.resIdManager = resIdManager;
	}

	@Required
	public void setXqueryUtils(final XQueryUtils xqueryUtils) {
		this.xqueryUtils = xqueryUtils;
	}

	public XQueryUtils getXqueryUtils() {
		return xqueryUtils;
	}

	public ISStoreService getIsStore() {
		return isStore;
	}

	@Required
	public void setIsStore(final ISStoreService isStore) {
		this.isStore = isStore;
	}

}
