package au.com.redboxresearchdata.curationmanager.identityProviderService.sruclient
/*
 * The Fascinator - ReDBox/Mint SRU Client - NLA Identity
 * Copyright (C) 2012 Queensland Cyber Infrastructure Foundation (http://www.qcif.edu.au/)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Element;
import org.dom4j.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>A basic wrapper for handling EAC-CPF formatted identities that return for
 * the National Library of Australia. This is neither a complete EAC-CPF handling
 * class, nor a complete implementation of NLA identities. It is just a utility
 * for access the things ReDBox/Mint cares about in common node.</p>
 *
 * @author Greg Pendlebury
 * @updated Devika Indla
 */
public class NLAIdentity {
	/** Logging **/
	private static Logger log = LoggerFactory.getLogger(NLAIdentity.class);

	/** DOM4J Node for this person **/
	private Node eac;

	/** Properties we extract **/
	private String nlaId;
	private String displayName;
	private String firstName;
	private String surname;
	private String institution;
	private List<Map<String, String>> knownIds;

	/**
	 * <p>Default Constructor. Extract some basic information.</p>
	 *
	 * @param searchResponse A parsed DOM4J Document
	 * @throws SRUException If any of the XML structure does not look like expected
	 */
	public NLAIdentity(Node node) throws SRUException {
		eac = node;

		// Identity
		List<Node> otherIds = eac.selectNodes("eac:eac-cpf/eac:control/eac:otherRecordId");
		for (Node idNode : otherIds) {
			String otherId = idNode.getText();
			if (otherId.startsWith("http://nla.gov.au")) {
				nlaId = otherId;
			}
		}
		if (nlaId == null) {
			throw new SRUException("Error processing Identity; Cannot find ID");
		}

		knownIds = getSourceIdentities();

		// Cosmetically we want to use the first row (should be the longest top-level name we found)
		firstName = knownIds.get(0).get("firstName");
		surname = knownIds.get(0).get("surname");
		displayName = knownIds.get(0).get("displayName");
		// For institution we want the first one we find that isn't NLA or Libraries Australia
		for (Map<String, String> id : knownIds) {
			if (institution == null
					// But we'll settle for those in a pinch
					|| "National Library of Australia Party Infrastructure".equals(institution)
					|| "Libraries Australia".equals(institution)) {
				institution = id.get("institution");
			}
		}
	}

	private List<Map<String, String>> getSourceIdentities() {
		List<Map<String, String>> returnList = new ArrayList<Map<String, String>>();

		// Top level institution
		Map<String, String> idMap = new HashMap<String, String>();
		Node institutionNode = eac.selectSingleNode("eac:eac-cpf/eac:control/eac:maintenanceAgency/eac:agencyName");
		String institutionString = institutionNode.getText();
		// Top level name
		Node nlaNamesNode = eac.selectSingleNode("eac:eac-cpf/eac:cpfDescription/eac:identity");
		// Get all the names this ID lists
		List<Map<String, String>> nameList = getNames(nlaNamesNode);
		for (Map<String, String> name : nameList) {
			// Only use the longest top-level name for display purposes
			String oldDisplayName = idMap.get("displayName");
			String thisDisplayName = name.get("displayName");
			if (oldDisplayName == null
					|| (thisDisplayName != null
						&& thisDisplayName.length() > oldDisplayName.length())) {
				// Clear any old data
				idMap.clear();
				// Store this ID
				idMap.putAll(name);
				idMap.put("institution", institutionString);
			}
		}
		// And add to the list
		returnList.add(idMap);

		// All name entities from contributing insitutions
		List<Node> sourceIdentities = eac.selectNodes("eac:eac-cpf/eac:cpfDescription//eac:eac-cpf");
		for (Node identity : sourceIdentities) {
			// Insitution for this ID
			institutionNode = identity.selectSingleNode("*//eac:maintenanceAgency/eac:agencyName");
			institutionString = institutionNode.getText();

			// Any names for this ID
			List<Node> idNodes = identity.selectNodes("*//eac:identity");
			for (Node idNode : idNodes) {
				// A Map for each name
				idMap = new HashMap<String, String>();
				// Get all the names this ID lists
				nameList = getNames(idNode);
				for (Map<String, String> name : nameList) {
					idMap.putAll(name);
				}
				// Indicate the insitution for each one
				idMap.put("institution", institutionString);
				// And add to the list
				returnList.add(idMap);
			}
		}

		// Debugging
		//for (Map<String, String> id : returnList) {
		//    String display = id.get("displayName") + " (" + id.get("institution") + ")";
		//    log.debug("Identity: {}", display);
		//}

		return returnList;
	}

	private List<Map<String, String>> getNames(Node node) {
		List<Map<String, String>> nameList = new ArrayList<Map<String, String>>();

		// Any names for this ID
		List<Node> names = node.selectNodes("eac:nameEntry");
		for (Node name : names) {
			Map<String, String> nameMap = new HashMap<String, String>();

			String thisDisplay = null;
			String thisFirstName = null;
			String thisSurname = null;
			String title = null;

			// First name
			Node firstNameNode = name.selectSingleNode("eac:part[(@localType=\"forename\") or (@localType=\"givenname\")]");
			if (firstNameNode != null) {
				thisFirstName = firstNameNode.getText();
			}

			// Surname
			Node surnameNode = name.selectSingleNode("eac:part[(@localType=\"surname\") or (@localType=\"familyname\")]");
			if (surnameNode != null) {
				thisSurname = surnameNode.getText();
			}

			// Title
			Node titleNode = name.selectSingleNode("eac:part[@localType=\"title\"]");
			if (titleNode != null) {
				title = titleNode.getText();
			}

			// Display Name
			if (thisSurname != null) {
				thisDisplay = thisSurname;
				nameMap.put("surname", thisSurname);
				if (thisFirstName != null) {
					thisDisplay += ", " + thisFirstName;
					nameMap.put("firstName", thisFirstName);
				}
				if (title != null) {
					thisDisplay += " (" + title + ")";
				}
				nameMap.put("displayName", thisDisplay);
			}

			// Last ditch effort... we couldn't find simple name information from
			//  recommended values. So just concatenate what we can see.
			if (thisDisplay == null) {
				// Find every part
				List<Node> parts = name.selectNodes("eac:part");
				for (Node part : parts) {
					// Grab the value and type of this value
					Element element = (Element) part;
					String value = element.getText();
					String type = element.attributeValue("localType");
					// Build a display value for this part
					if (type != null) {
						value += " ("+type+")";
					}
					// And add to the display name
					if (thisDisplay == null) {
						thisDisplay = value;
					} else {
						thisDisplay += ", " + value;
					}
				}
				nameMap.put("displayName", thisDisplay);
			}

			nameList.add(nameMap);
		}

		return nameList;
	}

	/**
	 * <p>Getter for the NLA Identifier in use by this Identity.</p>
	 *
	 * @return String The ID from the NLA for this Identity
	 */
	public String getId() {
		return nlaId;
	}

	/**
	 * <p>Getter for our best estimation on a display name for this Identity.</p>
	 *
	 * @return String The display name for this Identity
	 */
	public String getDisplayName() {
		return displayName;
	}

	/**
	 * <p>Getter for the first name for this Identity.</p>
	 *
	 * @return String The first name for this Identity
	 */
	public String getFirstName() {
		return firstName;
	}

	/**
	 * <p>Getter for the surname for this Identity.</p>
	 *
	 * @return String The surname for this Identity
	 */
	public String getSurame() {
		return surname;
	}

	/**
	 * <p>Getter for the institution for this Identity.</p>
	 *
	 * @return String The institution for this Identity
	 */
	public String getInstitution() {
		return institution;
	}

	/**
	 * <p>Getter for the List of Identities observed for this person. The return
	 * Objects are Maps containing keys very similar to the methods found on the
	 * top-level NLAIdentity Object.</p>
	 * <ul>
	 *   <li>'displayName'</li>
	 *   <li>'firstName'</li>
	 *   <li>'surname'</li>
	 *   <li>'institution'</li>
	 * </ul>
	 *
	 * @return List<Map<String, String>> A List Object containing identities
	 */
	public List<Map<String, String>> getKnownIdentities() {
		return knownIds;
	}

	/**
	 * <p>Converts a List of DOM4J Nodes into a List of processed NLAIdentity(s).
	 * Individual Nodes that fail to process will be skipped.</p>
	 *
	 * @param nodes A List of Nodes to process
	 * @return List<NLAIdentity> A List of processed Identities
	 */
	public static List<NLAIdentity> convertNodesToIdentities(List<Node> nodes) {
		try {
			return convertNodesToIdentities(nodes, false);
		} catch (SRUException ex) {
			// Will never executre because 'false' is set above,
			// but trapping this here allows users to call this method
			// with greater ease, since they wan't need to trap.
			return null;
		}
	}

	/**
	 * <p>Converts a List of DOM4J Nodes into a List of processed NLAIdentity(s).
	 * Must indicate whether or not errors should cause processing to halt.</p>
	 *
	 * @param nodes A List of Nodes to process
	 * @param haltOnErrors Flag if a single Node failing to process should halt execution.
	 * @return List<NLAIdentity> A List of processed Identities
	 * @throws SRUException If 'haltOnErrors' is set to TRUE and a Node fails to process.
	 */
	public static List<NLAIdentity> convertNodesToIdentities(List<Node> nodes,
			boolean haltOnErrors) throws SRUException {
		List<NLAIdentity> response = new ArrayList<NLAIdentity>();
		// Sanity check
		if (nodes == null || nodes.isEmpty()) {
			return response;
		}
		// Process each Node in turn
		for (Node node : nodes) {
			try {
				NLAIdentity newId = new NLAIdentity(node);
				response.add(newId);

			// Only halt if requested
			} catch(SRUException ex) {
				log.error("Unable to process identity: ", ex);
				if (haltOnErrors) {
					throw ex;
				}
			}
		}
		return response;
	}
}
