package eu.dnetlib.enabling.is.lookup;

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

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

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.ServiceLocator;
import eu.dnetlib.enabling.tools.StringOpaqueResource;
import eu.dnetlib.enabling.tools.XQueryUtils;

/**
 * ISLookUpService implementation.
 *
 * @author marko
 * @author michele
 *
 */
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";

	// 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

	/**
	 * ISStore service locator.
	 */
	private ServiceLocator<ISStoreService> storeLocator;

	/**
	 * 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()
	 */
	public Boolean flushCachedResultSets() {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#retrieveCollection(java.lang.String)
	 */
	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)
	 */
	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 storeLocator.getService().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)
	 */
	public String getResourceProfile(final String profId) throws ISLookUpException {
		if (profId == null || profId.isEmpty())
			throw new ISLookUpException("Invalid null profile ID");

		try {
			final String res = storeLocator.getService().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)
	 */
	public String getResourceProfileByQuery(final String xquery) throws ISLookUpException {
		String resource;
		try {
			resource = storeLocator.getService().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)
	 */
	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)
	 */
	public String getResourceTypeSchema(final String resourceType) throws ISLookUpException {
		if (resourceType == null || resourceType.isEmpty())
			throw new ISLookUpException("Invalid resourceType");

		try {
			return storeLocator.getService().getXML(resourceType, xqueryUtils.getRootCollection() + ResourceType.RESOURCE_TYPES);
		} 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)
	 */
	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()
	 */
	public W3CEndpointReference listCommunities() throws ISLookUpException {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService#listDHNIDs()
	 */
	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 : storeLocator.getService().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)
	 */
	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)
	 */
	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()
	 */
	public List<String> listResourceTypes() throws ISLookUpException {
		try {
			return storeLocator.getService().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)
	 */
	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 : storeLocator.getService().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()
	 */
	public List<String> listServiceTypes() throws ISLookUpException {
		final List<String> ret = new ArrayList<String>();
		try {
			for (final String i : storeLocator.getService().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)
	 */
	public List<String> quickSearchProfile(final String xquery) throws ISLookUpException {
		try {
			return storeLocator.getService().quickSearchXML(xquery);
		} catch (final ISStoreException e) {
			throw new ISLookUpException(e);
		}
	}

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

	public ServiceLocator<ISStoreService> getStoreLocator() {
		return storeLocator;
	}

	@Required
	public void setStoreLocator(final ServiceLocator<ISStoreService> storeLocator) {
		this.storeLocator = storeLocator;
	}

	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;
	}


}
