package com.k_int.discover.service;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xpath.XPathAPI;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeIterator;

import com.k_int.aggregator.augmentation.XMLDocumentAugmentationException;
import com.k_int.aggregator.augmentation.XMLDocumentAugmentationFactory;
import com.k_int.aggregator.augmentation.XMLDocumentAugmentor;
import com.k_int.aggregator.cache.CacheContentResult;
import com.k_int.aggregator.cache.impl.CacheContentImageThumbnail;
import com.k_int.aggregator.core.AggregatorPlugin;
import com.k_int.aggregator.core.DepositResult;
import com.k_int.aggregator.dto.AggregatorCollectionDTO;
import com.k_int.aggregator.dto.AggregatorSourceDTO;
import com.k_int.aggregator.plugin.XMLDocumentHandlerPlugin;
import com.k_int.aggregator.plugin.XMLProcessingResult;
import com.k_int.aggregator.repository.RepositoryService;
import com.k_int.aggregator.repository.RepositoryStoreOptions;
import com.k_int.aggregator.repository.RepositoryStoreResult;
import com.k_int.aggregator.util.SOLRHelper;
import com.k_int.commons.util.XMLUtil;
import com.k_int.discover.datamodel.CultureGrid_Address;
import com.k_int.discover.datamodel.CultureGrid_InstitutionDTO;
import com.k_int.discover.datamodel.CultureGrid_InstitutionDocument;
import com.k_int.discover.datamodel.OAIDCDocument;
import com.k_int.svc.spatial.service.Gazetteer;
import com.k_int.svc.spatial.service.GazetteerPlace;

@Service("XMLHandler-OAIIS")
public class OAIISHandler implements XMLDocumentHandlerPlugin, ApplicationContextAware, org.springframework.context.ApplicationListener {

	private static Log log = LogFactory.getLog(OAIISHandler.class);
	
	protected ApplicationContext ctx = null;
	
	// Namespace declarations
	public static String XMLNS_DC = "http://purl.org/dc/elements/1.1/";
	public static String XMLNS_DCTERMS = "http://purl.org/dc/terms/";
	public static String XMLNS_PNDSTERMS = "http://purl.org/mla/pnds/terms/";
	public static String XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
	public static String XMLNS_PNDS_DC = "http://purl.org/mla/pnds/pndsdc/";
	public static String XMLNS_OAI_DC = "http://www.openarchives.org/OAI/2.0/oai_dc/";
	public static String XMLNS_XHTML = "http://www.w3.org/1999/xhtml";
	public static String XMLNS_OAI_IS = "http://www.mla.gov.uk/OAI_IS/";

	private static String[] SUPPORTED_SCHEMAS = new String[] { XMLNS_OAI_IS };
	
	public String[] getSupportedSchemas() {
		return SUPPORTED_SCHEMAS;
	}
	
	/**
	 * @deprecated Replaced by {@link #process(DepositResult, byte[], Document, String, String, String, Boolean, long)}
	 */
	@Deprecated
	public XMLProcessingResult process(DepositResult deposit_result,
			byte[] content, Document d, String depositor_id, String owner_id,
			String root_namespace, Boolean authoritative) {
		return this.process(deposit_result, content, d, depositor_id, owner_id, root_namespace, authoritative, (RepositoryStoreOptions.REPO_STORE_OPTIONS_CACHE_THUMBNAIL | RepositoryStoreOptions.REPO_STORE_OPTIONS_CALC_CHECKSUM | RepositoryStoreOptions.REPO_STORE_OPTIONS_STORE_ORIGINAL_DOC) );
	}

	public XMLProcessingResult process(DepositResult deposit_result,
			byte[] content, Document d, String depositor_id, String owner_id,
			String root_namespace, Boolean authoritative, long options) {

		// Set up the return value
	    XMLProcessingResult returnValue = new XMLProcessingResult();
	    
		// Set up a version of the options with store checksums set to false as used when
		// storing all versions of the document other than the original
		long noChecksumOptions = options & ~RepositoryStoreOptions.REPO_STORE_OPTIONS_CALC_CHECKSUM;

		// Set up namespace elements
		Element new_namespace_node = d.createElement("NSNode");
		// Set up our local namespace definitions
		new_namespace_node.setAttribute("xmlns:dc", XMLNS_DC);
		new_namespace_node.setAttribute("xmlns:dcterms", XMLNS_DCTERMS);
		new_namespace_node.setAttribute("xmlns:xsi", XMLNS_XSI);
		new_namespace_node.setAttribute("xmlns:pn", XMLNS_PNDS_DC);
		new_namespace_node.setAttribute("xmlns:pndsterms", XMLNS_PNDSTERMS);
		new_namespace_node.setAttribute("xmlns:oai_dc", XMLNS_OAI_DC);
		new_namespace_node.setAttribute("xmlns:oai_is", XMLNS_OAI_IS);
		
		try {

			// Get all of the data from the original file
			HashMap<String,NameXPathMapping> xpathMappings = this.setupMappings();
			for(String key: xpathMappings.keySet()) {
				NameXPathMapping thisOne = xpathMappings.get(key);
				String xpath = thisOne.getXpathExpression();
				if ( !xpath.equals("SPECIAL") ) {
					String[] newValues = null;
					newValues = getValues(d, xpath, new_namespace_node);
					//   	      	  log.debug("Just mapped xpath " + xpath + " to " + newValues.length + " values");

					if (newValues != null && newValues.length != 0 ) {
						thisOne.setValues(newValues);
						//	    	          	  log.debug("stored the new found value.. in a local object");
						xpathMappings.put(key, thisOne);
						//	    	          	  log.debug("stored the value in the mappings");
					}
				}
			}

			// Check that we have all of the required information
			this.checkForRequiredData(xpathMappings);
			
			// Get the original identifier of the document so that it can be used when storing data
			String doc_identifier = xpathMappings.get("identifier").getValues()[0];
			log.debug("doc_identifier: " + doc_identifier);

			
			// Work out the list of collections for the data
			List<String> collectionList = new ArrayList<String>();

			if ( owner_id.equals("MLA") ) {
				// Dealing with MLA Institution Server
	    		collectionList.add("MLAInstitution");
			} else {
				log.debug("Unable to determine the collection list based on the source of the data. Identifier: " + doc_identifier + " source: " + owner_id);
			}
			
			// Set up the objects needed to store the different documents
			RepositoryService content_store = (RepositoryService) ctx.getBean("ContentStore");
			Long resourceId = -1l;
			RepositoryStoreResult stored_resource_info = null;

			// Store the Original format (if specified in options)
			if ( (options & RepositoryStoreOptions.REPO_STORE_OPTIONS_STORE_ORIGINAL_DOC) == RepositoryStoreOptions.REPO_STORE_OPTIONS_STORE_ORIGINAL_DOC ) {
				stored_resource_info = content_store.store(content,owner_id,depositor_id,doc_identifier,"oai_is_raw","application/xml",
						SUPPORTED_SCHEMAS[0],authoritative, collectionList.toArray(new String[collectionList.size()]), options);
			} else {
				// Not storing the original format - need to go and get information about the resource
				// that is already in the database - resource id, collections lists, etc.
				log.debug("Not storing the original document, as specified by the options");
				
				stored_resource_info = content_store.lookupResourceInfo(owner_id, doc_identifier);
				if ( stored_resource_info == null ) {
					log.error("Not storing the original document and unable to find it in the database to retrieve information about it!!");
					throw(new XMLDocumentAugmentationException("Unable to retrieve information about the original document from the database to set up data augmentation"));
				}
			}

			// Get the information from the result of storing the new doc or looking up the original in the DB to
			// control augmentation below.
			resourceId = stored_resource_info.getDocId();
			AggregatorSourceDTO sourceDetails = stored_resource_info.getSource();
			List<AggregatorCollectionDTO> collectionDetails = stored_resource_info.getCollections();

			
	        returnValue.setDocId(resourceId);

			
			// Set up the augmentation factory and get the relevant augmentor for this type
			// of object
			XMLDocumentAugmentationFactory augFactory = (XMLDocumentAugmentationFactory)ctx.getBean("AugmentationFactory");
			XMLDocumentAugmentor augmentor = (XMLDocumentAugmentor)augFactory.getAugmentor("INSTITUTION");
			
			if ( augmentor == null ) { 
				log.error("augmentor is null - not able to augment the data");
			} else {
				log.debug("non-null augmentor - ready to start augmenting data.");
				
				// Set up the DTO to be passed to the augmentor
				CultureGrid_InstitutionDTO instDTO = new CultureGrid_InstitutionDTO();
				if ( checkHasValueToStore(xpathMappings, "address-identifier") ) {
					instDTO.setAddressIdentifier(xpathMappings.get("address-identifier").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-pobox") ) {
					instDTO.setAddressPoBox(xpathMappings.get("address-pobox").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-streetAddress") ) {
					instDTO.setAddressStreetAddresses(convertArrayToList(xpathMappings.get("address-streetAddress").getValues()));
				}
				if ( checkHasValueToStore(xpathMappings, "address-town") ) {
					instDTO.setAddressTown(xpathMappings.get("address-town").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-county") ) {
					instDTO.setAddressCounty(xpathMappings.get("address-county").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-country") ) {
					instDTO.setAddressCountry(xpathMappings.get("address-country").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-postcode") ) {
					instDTO.setAddressPostcode(xpathMappings.get("address-postcode").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "address-digital") ) {
					instDTO.setAddressDigital(xpathMappings.get("address-digital").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "administrativeStatus") ) {
					instDTO.setAdminStatus(xpathMappings.get("administrativeStatus").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "alternativeNames") ) {
					instDTO.setAlternativeNames(convertArrayToList(xpathMappings.get("alternativeNames").getValues()));
				}
				if ( checkHasValueToStore(xpathMappings, "description") ) {
					instDTO.setDescription(xpathMappings.get("description").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "disabledAccess") ) {
					instDTO.setDisabledAccess(xpathMappings.get("disabledAccess").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "email") ) {
					instDTO.setEmail(xpathMappings.get("email").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "faxNum") ) {
					instDTO.setFaxNum(xpathMappings.get("faxNum").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "identifier") ) {
					instDTO.setIdentifier(xpathMappings.get("identifier").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "institutionType") ) {
					instDTO.setInstitutionType(xpathMappings.get("institutionType").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "jurisdiction") ) {
					instDTO.setJurisdiction(xpathMappings.get("jurisdiction").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "mlaRegion") ) {
					instDTO.setMlaRegion(xpathMappings.get("mlaRegion").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "operationalHours") ) {
					instDTO.setOperationalHours(xpathMappings.get("operationalHours").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "otherIdentifier") && checkHasValueToStore(xpathMappings, "otherIdentifierScheme") ) {
					String[] otherIdentifiers = xpathMappings.get("otherIdentifier").getValues();
					String[] otherIdentifierSchemes = xpathMappings.get("otherIdentifierScheme").getValues();
					if ( otherIdentifiers.length != otherIdentifierSchemes.length ) {
						log.error("Collected a different number of other identifiers (" + otherIdentifiers.length + ") to other identifier schemes (" + otherIdentifierSchemes.length + ") from the source document.");
					} else {
						// Matching numbers of other identifiers and their schemes - remember them in the DTO
						HashMap<String,String> otherIdentifierInfo = new HashMap<String,String>();
						for(int ctr = 0; ctr < otherIdentifiers.length; ctr++) {
							otherIdentifierInfo.put(otherIdentifierSchemes[ctr], otherIdentifiers[ctr]);
						}
						
						instDTO.setOtherIdentifiers(otherIdentifierInfo);
					}
				}
				if ( checkHasValueToStore(xpathMappings, "phoneNum") ) {
					instDTO.setPhoneNum(xpathMappings.get("phoneNum").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "referralDetails") ) {
					instDTO.setReferralDetails(xpathMappings.get("referralDetails").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "sector") ) {
					instDTO.setSector(xpathMappings.get("sector").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "spatial") ) {
					instDTO.setSpatial(xpathMappings.get("spatial").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "thumbnail") ) {
					instDTO.setThumbnail(xpathMappings.get("thumbnail").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "title") ) {
					instDTO.setTitle(xpathMappings.get("title").getValues()[0]);
				}
				if ( checkHasValueToStore(xpathMappings, "usageConditions") ) {
					instDTO.setUsageConditions(xpathMappings.get("usageConditions").getValues()[0]);
				}
				
				// Perform the augmentation getting back the list of generated objects to be stored / indexed
				HashMap<String, Object> augmentedData = augmentor.augmentData(instDTO, depositor_id, sourceDetails, root_namespace, collectionDetails, resourceId, options);
				
				// Loop through the returned augmented data, storing each of the different documents as appropriate
				Set<String> augmentedKeyset = augmentedData.keySet();
				Iterator<String> augmentedIterator = augmentedKeyset.iterator();
				
				while(augmentedIterator.hasNext()) {
					String type = augmentedIterator.next();
					Object doc = augmentedData.get(type);

					String mime_type = "";
					String sub_type = "";
					byte[] contents = null;
					
					log.debug("Institution handler dealing with the augmented document of type: " + type);
					if ( type.equals("oai_dc") ) {
						// OAI_DC document
						mime_type = "application/xml";
						sub_type = "http://www.openarchives.org/OAI/2.0/oai_dc/";
						OAIDCDocument oaiDcDoc = (OAIDCDocument)doc;
						contents = XMLUtil.serializeDocument(oaiDcDoc.toXML());
					} else if ( type.equals("CultureGrid_Institution") ) {
						// CultureGrid internal institution format
						mime_type = "application/xml";
						sub_type = "http://www.peoplesnetwork.gov.uk/schema/CultureGrid_Institution"; // TODO - what should this really be?
						CultureGrid_InstitutionDocument cgInstDoc = (CultureGrid_InstitutionDocument)doc;
						contents = XMLUtil.serializeDocument(cgInstDoc.toXML());
					} else if ( type.equals("thumbnail_image_jpeg") ) {
						// Thumbnail image
						mime_type = "image/jpeg";
						sub_type = "";
						CacheContentResult cachedThumb = (CacheContentResult)doc;
						contents = cachedThumb.content;
					} else if ( type.equals("solr") ) {
						// Solr index properties
						mime_type = "application/xml";
						sub_type = "";
						List<SOLRHelper.NVPair> index_properties = (List<SOLRHelper.NVPair>)doc;
						
						// Solr is different - we need to do the indexing here - pass the returned properties through
						// to SOLR and then store the returned XML document
						
						SOLRHelper solr_helper = (com.k_int.aggregator.util.SOLRHelper) ctx.getBean("SolrHelper");
				
						log.debug("Indexing the document into solr");
						Document solr_doc = solr_helper.postIndexEntries(index_properties, false);
						if ( solr_doc != null ) {
							contents = XMLUtil.serializeDocument(solr_doc);
						} else {
							log.debug("An error occurred when indexing the document to solr - no document returned! doc_identifier: " + doc_identifier);
						}
					} else {
						// Unrecognised - don't know what to do, so log it..
						log.error("Unrecognised type of document returned from augmenting institution data. Type: " + type + ". Object class: " + doc.getClass().getName());
					}
					
					// The document is now processed and we have all of the information we need to store it - do so
					if ( contents != null ) {
						// We have something to store - do so
						log.debug("Storing a document of type: " + type + ", mime_type: " + mime_type + ", sub_type: " + sub_type);
						content_store.store(contents, owner_id, depositor_id, doc_identifier, type, mime_type, sub_type, authoritative, 
								collectionList.toArray(new String[collectionList.size()]), noChecksumOptions);
					}
					
				}

			}
			
		} catch (TransformerException te) {
			log.error("Problem - transformer exception: " + te);
			returnValue.setMessage("TransformerException thrown when parsing the OAI_IS document");
		} catch (XPathExpressionException xe) {
			log.error("Problem when parsing the OAI_IS document. " + xe.getMessage());
			returnValue.setMessage(xe.getMessage());
		} catch (XMLDocumentAugmentationException ae) {
			log.error("Problem - XMLDocumentAugmentationException: " + ae); // TODO - should i handle this differently?
			returnValue.setMessage(ae.getMessage());
		}

        return returnValue;
	}

	public void onApplicationEvent(ApplicationEvent evt) {
		log.info("Spring event : "+evt);
	}

	public void setApplicationContext(ApplicationContext ctx)
			throws BeansException {
		this.ctx = ctx;
	}
	
	private HashMap<String,NameXPathMapping> setupMappings() {

		HashMap<String, NameXPathMapping> mappings = new HashMap<String,NameXPathMapping>();
		
		mappings.put("address-identifier", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/@identifier", false));
		mappings.put("address-pobox", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:po_box", false));
		mappings.put("address-streetAddress", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:street_address", false, true));
		mappings.put("address-town", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:town_city", false));
		mappings.put("address-county", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:region_county", false));
		mappings.put("address-country", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:country", false));
		mappings.put("address-postcode", new NameXPathMapping("unparsed.postcode", "/oai_is:institution/oai_is:address/oai_is:postal_code", false));
		mappings.put("address-digital", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:address/oai_is:digital_address", false));
		mappings.put("administrativeStatus", new NameXPathMapping("oai_is.administrativeStatus", "/oai_is:institution/oai_is:administrative_status", false));
		mappings.put("alternativeNames", new NameXPathMapping("oai_is.alternativeName", "/oai_is:institution/oai_is:alternativeName", false, true));
		mappings.put("description", new NameXPathMapping("dc.description","/oai_is:institution/oai_is:institution_history", false));
		mappings.put("disabledAccess", new NameXPathMapping("disabledAccess", "/oai_is:institution/oai_is:address/oai_is:access_conditions/oai_is:disability_access", false));
		mappings.put("email", new NameXPathMapping("vcard.email", "/oai_is:institution/oai_is:address/oai_is:institution_contact_details/oai_is:e_mail", false));
		mappings.put("faxNum", new NameXPathMapping("vcard.fax", "/oai_is:institution/oai_is:address/oai_is:institution_contact_details/oai_is:facsimilie", false));
		mappings.put("identifier", new NameXPathMapping("dc.identifier","/oai_is:institution/@identifier", false));
		mappings.put("institutionType", new NameXPathMapping("institution_type", "/oai_is:institution/oai_is:type", false));
		mappings.put("jurisdiction", new NameXPathMapping("oai_is.jurisdiction", "/oai_is:institution/oai_is:jurisdiction", false));
		mappings.put("mlaRegion", new NameXPathMapping("oai_is.mlaRegion", "/oai_is:institution/oai_is:mla_region", false));
		mappings.put("operationalHours", new NameXPathMapping("mla.operational_hours", "/oai_is:institution/oai_is:address/oai_is:access_conditions/oai_is:operational_hours", false));
		mappings.put("otherIdentifier", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:other_identifier", false, true));
		mappings.put("otherIdentifierScheme", new NameXPathMapping("NO_INDEX", "/oai_is:institution/oai_is:other_identifier/@scheme", false, true));
		mappings.put("phoneNum", new NameXPathMapping("vcard.voice", "/oai_is:institution/oai_is:address/oai_is:institution_contact_details/oai_is:fixed_telephone", false));
		mappings.put("referralDetails", new NameXPathMapping("oai_is.referral", "/oai_is:institution/oai_is:address/oai_is:access_conditions/oai_is:referral", false));
		mappings.put("sector", new NameXPathMapping("institution_sector", "/oai_is:institution/oai_is:sector", false));
		// TODO - do something with spatial...
		mappings.put("spatial", new NameXPathMapping("dcterms.spatial", "/oai_is:institution/oai_is:address/oai_is:postal_code", false)); // TODO - not ideal as it throws away a lot of other spatial info
		mappings.put("thumbnail", new NameXPathMapping("pndsterms.thumbnail", "/oai_is:institution/oai_is:logo", false));
		mappings.put("title",new NameXPathMapping("dc.title", "/oai_is:institution/oai_is:full_name", false));
		mappings.put("usageConditions", new NameXPathMapping("mla.usage_conditions", "/oai_is:institution/oai_is:address/oai_is:access_conditions/oai_is:usage_conditions", false));
		mappings.put("website", new NameXPathMapping("oai_is.website", "/oai_is:institution/oai_is:website", false));

		// TODO - add something to handle successor / predecessor / associatedWith / hasPart / isPart
		

		  
		return mappings;
	}


	/**
	 * Check through the data in the mappings (once filled with values) - if something is
	 * required then check that it isn't null i.e. that we have found a value for it
	 * @throws XPathExpressionException thrown if a field that is required has not been filled in from the XML document
	 */
	private void checkForRequiredData(HashMap<String,NameXPathMapping> xpathMappings) throws XPathExpressionException {
		// Loop through all of the entries and consider those where required == true
		for(String key: xpathMappings.keySet()) {
			NameXPathMapping thisEntry = xpathMappings.get(key);
			if ( thisEntry.isRequired() ) {
				// We care about it being a non-null value - check
				String[] value = thisEntry.getValues();
				if ( value == null ) {
					log.debug("The required field: " + key + " doesn't have a value in the data file");
					throw(new XPathExpressionException("Required field: " + key + " does not have a value in the data"));
				}
			}
		}
	}
	  


	/**
	 * Get the value of the element or attribute given by the specified xpath expression
	 * @param metadata_record The document to apply the xpath to
	 * @param xpath The xpath expression to the element or attribute to consider
	 * @param namespace_node The set of namespaces used in the xpath and document
	 * @return The text contents of the elements or attributes as an array
	 * @throws javax.xml.transform.TransformerException thrown if there's an error with the XPath
	 */
	protected static String[] getValues(Node metadata_record, String xpath, Node namespace_node) throws javax.xml.transform.TransformerException {
		// First work out whether we're looking for an attribute or a full element
		// - we're looking at an attribute if there is an @ after the last / 
		// (and the expression doesn't end with ].
		String node_xpath = xpath;
		String attribute_xpath = "";

		if ( !xpath.endsWith("]") ) {

			int indexOfLastSlash = xpath.lastIndexOf("/");
			int indexOfLastAt = xpath.lastIndexOf("@");

			if ( indexOfLastAt > indexOfLastSlash ) {
				// Dealing with an attribute
				attribute_xpath = xpath.substring(indexOfLastAt + 1);
				node_xpath = xpath.substring(0, indexOfLastAt - 1);
			}
		}

		// Go and get the node(s) we're interested in
		NodeIterator nodeList = XPathAPI.selectNodeIterator(metadata_record, node_xpath, namespace_node);
		String[] returnValue = null;
		ArrayList<String> tempList = new ArrayList<String>();

		Node actualNode;
		while ((actualNode = nodeList.nextNode()) != null) {
			// We've got a node - now are we getting an attribute or not?
			String value = null;
			if ( !"".equals(attribute_xpath) ) {
				value = ((Element)actualNode).getAttribute(attribute_xpath);
			} else {
				value = extractText(actualNode);
			}

			// Unescape any html entites, etc. in the string (a couple of times since there are some places
			// that encode multiple times and then remove the HTML
			//			  log.debug("about to unescape the value: " + value);
			if ( value != null ) {
				value = StringEscapeUtils.unescapeHtml(StringEscapeUtils.unescapeHtml(value));
				value = value.replaceAll("\\<.*?\\>", "");
			}

			//			  log.debug("value after unescaping and stripping tags: " + value);

			tempList.add(value);
		}

		returnValue = (String[])tempList.toArray(new String[tempList.size()]);

		return returnValue;
	}


	protected static String extractText(Node n) {
		try {
			Node node = XPathAPI.selectSingleNode(n,"./text()");
			if ( node != null ) {
				node.normalize();
				return node.getNodeValue().trim();
			}
		}
		catch ( javax.xml.transform.TransformerException te ) {
			te.printStackTrace();
		}
		return null;
	}

	  
	/**
	 * Check to see whether the specified mappings have a valid entry for the specified
	 * key and whether that entry has a value to be stored
	 * @param mappings The set of mappings to consider
	 * @param key The key to be checked for
	 * @return true if the key exists and has a non-null value, false otherwise
	 */
	private boolean checkHasValueToStore(HashMap<String,NameXPathMapping> mappings, String key) {
		if ( mappings.containsKey(key) && mappings.get(key).getValues() != null && mappings.get(key).getValues().length > 0 ) {
			return true;
		} else {
			return false;
		}
	}
		

	/**
	 * Convert the passed array into a list for easier manipulation, etc.
	 * @param valueArray The array to be converted
	 * @return A List representing the same data as the array in the same order, etc.
	 */
	private List<String> convertArrayToList(String[] valueArray) {
		List<String> valueList = null;
		if ( valueArray != null && valueArray.length > 0 ) {
			valueList = new ArrayList<String>();
			for(String value: valueArray) {
				if ( value != null && !"".equals(value.trim()) ) {
					valueList.add(value.trim());
				}
			}
		}
		if ( valueList != null && valueList.size() == 0 ) {
			valueList = null;
		}
		
		return valueList;
	}

}
