<commit_before>/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.web.dwr;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.GlobalProperty;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.PersonAddress;
import org.openmrs.activelist.Allergy;
import org.openmrs.activelist.AllergySeverity;
import org.openmrs.activelist.AllergyType;
import org.openmrs.activelist.Problem;
import org.openmrs.activelist.ProblemModifier;
import org.openmrs.api.APIAuthenticationException;
import org.openmrs.api.APIException;
import org.openmrs.api.ConceptService;
import org.openmrs.api.DuplicateIdentifierException;
import org.openmrs.api.GlobalPropertyListener;
import org.openmrs.api.IdentifierNotUniqueException;
import org.openmrs.api.InsufficientIdentifiersException;
import org.openmrs.api.InvalidCheckDigitException;
import org.openmrs.api.InvalidIdentifierFormatException;
import org.openmrs.api.LocationService;
import org.openmrs.api.PatientIdentifierException;
import org.openmrs.api.PatientService;
import org.openmrs.api.context.Context;
import org.openmrs.patient.IdentifierValidator;
import org.openmrs.patient.UnallowedIdentifierException;
import org.openmrs.util.OpenmrsConstants;

/**
 * DWR patient methods. The methods in here are used in the webapp to get data from the database via
 * javascript calls.
 * 
 * @see PatientService
 */
public class DWRPatientService implements GlobalPropertyListener {
	
	private static final Log log = LogFactory.getLog(DWRPatientService.class);
	
	private static Integer maximumResults;
	
	/**
	 * Search on the <code>searchValue</code>. If a number is in the search string, do an identifier
	 * search. Else, do a name search
	 * 
	 * @param searchValue string to be looked for
	 * @param includeVoided true/false whether or not to included voided patients
	 * @return Collection<Object> of PatientListItem or String
	 * @should return only patient list items with nonnumeric search
	 * @should return string warning if invalid patient identifier
	 * @should not return string warning if searching with valid identifier
	 * @should include string in results if doing extra decapitated search
	 * @should not return duplicate patient list items if doing decapitated search
	 * @should not do decapitated search if numbers are in the search string
	 * @should get results for patients that have edited themselves
	 * @should logged in user should load their own patient object
	 */
	public Collection<Object> findPatients(String searchValue, boolean includeVoided) {
		return findBatchOfPatients(searchValue, includeVoided, null, null);
	}
	
	/**
	 * Search on the <code>searchValue</code>. If a number is in the search string, do an identifier
	 * search. Else, do a name search
	 * 
	 * @see PatientService#getPatients(String, String, List, boolean, int, Integer)
	 * @param searchValue string to be looked for
	 * @param includeVoided true/false whether or not to included voided patients
	 * @param start The starting index for the results to return
	 * @param length The number of results of return
	 * @return Collection<Object> of PatientListItem or String
	 * @since 1.8
	 */
	@SuppressWarnings("unchecked")
	public Collection<Object> findBatchOfPatients(String searchValue, boolean includeVoided, Integer start, Integer length) {
		if (maximumResults == null)
			maximumResults = getMaximumSearchResults();
		if (length != null && length > maximumResults)
			length = maximumResults;
		
		// the list to return
		List<Object> patientList = new Vector<Object>();
		
		PatientService ps = Context.getPatientService();
		Collection<Patient> patients;
		
		try {
			patients = ps.getPatients(searchValue, start, length);
		}
		catch (APIAuthenticationException e) {
			patientList.add(Context.getMessageSourceService().getMessage("Patient.search.error") + " - " + e.getMessage());
			return patientList;
		}
		
		patientList = new Vector<Object>(patients.size());
		for (Patient p : patients)
			patientList.add(new PatientListItem(p));
		// if the length wasn't limited to less than 3 or this is the second ajax call
		// and only 2 results found and a number was not in the
		// search, then do a decapitated search: trim each word
		// down to the first three characters and search again
		if ((length == null || length > 2) && patients.size() < 3 && !searchValue.matches(".*\\d+.*")) {
			String[] names = searchValue.split(" ");
			String newSearch = "";
			for (String name : names) {
				if (name.length() > 3)
					name = name.substring(0, 4);
				newSearch += " " + name;
			}
			newSearch = newSearch.trim();
			
			if (!newSearch.equals(searchValue)) {
				Collection<Patient> newPatients = ps.getPatients(newSearch, start, length);
				patients = CollectionUtils.union(newPatients, patients); // get unique hits
				//reconstruct the results list
				if (newPatients.size() > 0) {
					patientList = new Vector<Object>(patients.size());
					//patientList.add("Minimal patients returned. Results for <b>" + newSearch + "</b>");
					for (Patient p : newPatients) {
						PatientListItem pi = new PatientListItem(p);
						patientList.add(pi);
					}
				}
			}
		}
		//no results found and a number was in the search --
		//should check whether the check digit is correct.
		else if (patients.size() == 0 && searchValue.matches(".*\\d+.*")) {
			
			//Looks through all the patient identifier validators to see if this type of identifier
			//is supported for any of them.  If it isn't, then no need to warn about a bad check
			//digit.  If it does match, then if any of the validators validates the check digit
			//successfully, then the user is notified that the identifier has been entered correctly.
			//Otherwise, the user is notified that the identifier was entered incorrectly.
			
			Collection<IdentifierValidator> pivs = ps.getAllIdentifierValidators();
			boolean shouldWarnUser = true;
			boolean validCheckDigit = false;
			boolean identifierMatchesValidationScheme = false;
			
			for (IdentifierValidator piv : pivs) {
				try {
					if (piv.isValid(searchValue)) {
						shouldWarnUser = false;
						validCheckDigit = true;
					}
					identifierMatchesValidationScheme = true;
				}
				catch (UnallowedIdentifierException e) {}
			}
			
			if (identifierMatchesValidationScheme) {
				if (shouldWarnUser)
					patientList
					        .add("<p style=\"color:red; font-size:big;\"><b>WARNING: Identifier has been typed incorrectly!  Please double check the identifier.</b></p>");
				else if (validCheckDigit)
					patientList
					        .add("<p style=\"color:green; font-size:big;\"><b>This identifier has been entered correctly, but still no patients have been found.</b></p>");
			}
		}
		
		return patientList;
	}
	
	/**
	 * Returns a map of results with the values as count of matches and a partial list of the
	 * matching patients (depending on values of start and length parameters) while the keys are are
	 * 'count' and 'objectList' respectively, if the length parameter is not specified, then all
	 * matches will be returned from the start index if specified.
	 * 
	 * @param searchValue patient name or identifier
	 * @param start the beginning index
	 * @param length the number of matching patients to return
	 * @param getMatchCount Specifies if the count of matches should be included in the returned map
	 * @return a map of results
	 * @throws APIException
	 * @since 1.8
	 */
	@SuppressWarnings("unchecked")
	public Map<String, Object> findCountAndPatients(String searchValue, Integer start, Integer length, boolean getMatchCount)
	        throws APIException {
		
		//Map to return
		Map<String, Object> resultsMap = new HashMap<String, Object>();
		Collection<Object> objectList = new Vector<Object>();
		try {
			PatientService ps = Context.getPatientService();
			int patientCount = 0;
			//if this is the first call
			if (getMatchCount) {
				patientCount += ps.getCountOfPatients(searchValue);
				
				// if only 2 results found and a number was not in the
				// search, then do a decapitated search: trim each word
				// down to the first three characters and search again				
				if ((length == null || length > 2) && patientCount < 3 && !searchValue.matches(".*\\d+.*")) {
					String[] names = searchValue.split(" ");
					String newSearch = "";
					for (String name : names) {
						if (name.length() > 3)
							name = name.substring(0, 4);
						newSearch += " " + name;
					}
					
					newSearch = newSearch.trim();
					if (!newSearch.equals(searchValue)) {
						//since we already know that the list is small, it doesn't hurt to load the hits
						//so that we can remove them from the list of the new search results and get the 
						//accurate count of matches
						Collection<Patient> patients = ps.getPatients(searchValue);
						newSearch = newSearch.trim();
						Collection<Patient> newPatients = ps.getPatients(newSearch);
						newPatients = CollectionUtils.union(newPatients, patients);
						//Re-compute the count of all the unique patient hits
						patientCount = newPatients.size();
						if (newPatients.size() > 0) {
							resultsMap.put("notification", Context.getMessageSourceService().getMessage(
							    "Patient.warning.minimalSearchResults", new Object[] { newSearch }, Context.getLocale()));
						}
					}
				}

				//no results found and a number was in the search --
				//should check whether the check digit is correct.
				else if (patientCount == 0 && searchValue.matches(".*\\d+.*")) {
					
					//Looks through all the patient identifier validators to see if this type of identifier
					//is supported for any of them.  If it isn't, then no need to warn about a bad check
					//digit.  If it does match, then if any of the validators validates the check digit
					//successfully, then the user is notified that the identifier has been entered correctly.
					//Otherwise, the user is notified that the identifier was entered incorrectly.
					
					Collection<IdentifierValidator> pivs = ps.getAllIdentifierValidators();
					boolean shouldWarnUser = true;
					boolean validCheckDigit = false;
					boolean identifierMatchesValidationScheme = false;
					
					for (IdentifierValidator piv : pivs) {
						try {
							if (piv.isValid(searchValue)) {
								shouldWarnUser = false;
								validCheckDigit = true;
							}
							identifierMatchesValidationScheme = true;
						}
						catch (UnallowedIdentifierException e) {}
					}
					
					if (identifierMatchesValidationScheme) {
						if (shouldWarnUser)
							resultsMap.put("notification", "<b>"
							        + Context.getMessageSourceService().getMessage("Patient.warning.inValidIdentifier")
							        + "<b/>");
						else if (validCheckDigit)
							resultsMap.put("notification", "<b style=\"color:green;\">"
							        + Context.getMessageSourceService().getMessage("Patient.message.validIdentifier")
							        + "<b/>");
					}
				} else {
					//ensure that count never exceeds this value because the API's service layer would never
					//return more than it since it is limited in the DAO layer
					if (maximumResults == null)
						maximumResults = getMaximumSearchResults();
					if (length != null && length > maximumResults)
						length = maximumResults;
					
					if (patientCount > maximumResults) {
						patientCount = maximumResults;
						if (log.isDebugEnabled())
							log.debug("Limitng the size of matching patients to " + maximumResults);
					}
				}
				
			}
			
			//if we have any matches or this isn't the first ajax call when the caller
			//requests for the count
			if (patientCount > 0 || !getMatchCount)
				objectList = findBatchOfPatients(searchValue, false, start, length);
			
			resultsMap.put("count", patientCount);
			resultsMap.put("objectList", objectList);
		}
		catch (Exception e) {
			log.error("Error while searching for patients", e);
			objectList.clear();
			objectList.add(Context.getMessageSourceService().getMessage("Patient.search.error") + " - " + e.getMessage());
			resultsMap.put("count", 0);
			resultsMap.put("objectList", objectList);
		}
		return resultsMap;
	}
	
	/**
	 * Convenience method for dwr/javascript to convert a patient id into a Patient object (or at
	 * least into data about the patient)
	 * 
	 * @param patientId the {@link Patient#getPatientId()} to match on
	 * @return a truncated Patient object in the form of a PatientListItem
	 */
	public PatientListItem getPatient(Integer patientId) {
		PatientService ps = Context.getPatientService();
		Patient p = ps.getPatient(patientId);
		PatientListItem pli = new PatientListItem(p);
		if (p != null && p.getAddresses() != null && p.getAddresses().size() > 0) {
			PersonAddress pa = (PersonAddress) p.getAddresses().toArray()[0];
			pli.setAddress1(pa.getAddress1());
			pli.setAddress2(pa.getAddress2());
		}
		return pli;
	}
	
	/**
	 * find all patients with duplicate attributes (searchOn)
	 * 
	 * @param searchOn
	 * @return list of patientListItems
	 */
	public Vector<Object> findDuplicatePatients(String[] searchOn) {
		Vector<Object> patientList = new Vector<Object>();
		
		try {
			List<String> options = new Vector<String>(searchOn.length);
			for (String s : searchOn)
				options.add(s);
			
			List<Patient> patients = Context.getPatientService().getDuplicatePatientsByAttributes(options);
			
			if (patients.size() > 200)
				patients.subList(0, 200);
			
			for (Patient p : patients)
				patientList.add(new PatientListItem(p));
		}
		catch (Exception e) {
			log.error(e);
			patientList.add("Error while attempting to find duplicate patients - " + e.getMessage());
		}
		
		return patientList;
	}
	
	/**
	 * Auto generated method comment
	 * 
	 * @param patientId
	 * @param identifierType
	 * @param identifier
	 * @param identifierLocationId
	 * @return
	 */
	public String addIdentifier(Integer patientId, String identifierType, String identifier, Integer identifierLocationId) {
		
		String ret = "";
		
		if (identifier == null || identifier.length() == 0)
			return "PatientIdentifier.error.general";
		PatientService ps = Context.getPatientService();
		LocationService ls = Context.getLocationService();
		Patient p = ps.getPatient(patientId);
		PatientIdentifierType idType = ps.getPatientIdentifierTypeByName(identifierType);
		//ps.updatePatientIdentifier(pi);
		Location location = ls.getLocation(identifierLocationId);
		log.debug("idType=" + identifierType + "->" + idType + " , location=" + identifierLocationId + "->" + location
		        + " identifier=" + identifier);
		PatientIdentifier id = new PatientIdentifier();
		id.setIdentifierType(idType);
		id.setIdentifier(identifier);
		id.setLocation(location);
		
		// in case we are editing, check to see if there is already an ID of this type and location
		for (PatientIdentifier previousId : p.getActiveIdentifiers()) {
			if (previousId.getIdentifierType().equals(idType) && previousId.getLocation().equals(location)) {
				log.debug("Found equivalent ID: [" + idType + "][" + location + "][" + previousId.getIdentifier()
				        + "], about to remove");
				p.removeIdentifier(previousId);
			} else {
				if (!previousId.getIdentifierType().equals(idType))
					log.debug("Previous ID id type does not match: [" + previousId.getIdentifierType().getName() + "]["
					        + previousId.getIdentifier() + "]");
				if (!previousId.getLocation().equals(location)) {
					log.debug("Previous ID location is: " + previousId.getLocation());
					log.debug("New location is: " + location);
				}
			}
		}
		
		p.addIdentifier(id);
		
		try {
			ps.savePatient(p);
		}
		catch (InvalidIdentifierFormatException iife) {
			log.error(iife);
			ret = "PatientIdentifier.error.formatInvalid";
		}
		catch (InvalidCheckDigitException icde) {
			log.error(icde);
			ret = "PatientIdentifier.error.checkDigit";
		}
		catch (IdentifierNotUniqueException inue) {
			log.error(inue);
			ret = "PatientIdentifier.error.notUnique";
		}
		catch (DuplicateIdentifierException die) {
			log.error(die);
			ret = "PatientIdentifier.error.duplicate";
		}
		catch (InsufficientIdentifiersException iie) {
			log.error(iie);
			ret = "PatientIdentifier.error.insufficientIdentifiers";
		}
		catch (PatientIdentifierException pie) {
			log.error(pie);
			ret = "PatientIdentifier.error.general";
		}
		
		return ret;
	}
	
	/**
	 * Auto generated method comment
	 * 
	 * @param patientId
	 * @param reasonForExitId
	 * @param dateOfExit
	 * @param causeOfDeath
	 * @param otherReason
	 * @return
	 */
	public String exitPatientFromCare(Integer patientId, Integer exitReasonId, String exitDateStr,
	        Integer causeOfDeathConceptId, String otherReason) {
		log.debug("Entering exitfromcare with [" + patientId + "] [" + exitReasonId + "] [" + exitDateStr + "]");
		String ret = "";
		
		PatientService ps = Context.getPatientService();
		ConceptService cs = Context.getConceptService();
		
		Patient patient = null;
		try {
			patient = ps.getPatient(patientId);
		}
		catch (Exception e) {
			patient = null;
		}
		
		if (patient == null) {
			ret = "Unable to find valid patient with the supplied identification information - cannot exit patient from care";
		}
		
		// Get the exit reason concept (if possible)
		Concept exitReasonConcept = null;
		try {
			exitReasonConcept = cs.getConcept(exitReasonId);
		}
		catch (Exception e) {
			exitReasonConcept = null;
		}
		
		// Exit reason error handling
		if (exitReasonConcept == null) {
			ret = "Unable to locate reason for exit in dictionary - cannot exit patient from care";
		}
		
		// Parse the exit date
		Date exitDate = null;
		if (exitDateStr != null) {
			SimpleDateFormat sdf = Context.getDateFormat();
			try {
				exitDate = sdf.parse(exitDateStr);
			}
			catch (ParseException e) {
				exitDate = null;
			}
		}
		
		// Exit date error handling
		if (exitDate == null) {
			ret = "Invalid date supplied - cannot exit patient from care without a valid date.";
		}
		
		// If all data is provided as expected
		if (patient != null && exitReasonConcept != null && exitDate != null) {
			
			// need to check first if this is death or not
			String patientDiedConceptId = Context.getAdministrationService().getGlobalProperty("concept.patientDied");
			
			Concept patientDiedConcept = null;
			if (patientDiedConceptId != null) {
				patientDiedConcept = cs.getConcept(patientDiedConceptId);
			}
			
			// If there is a concept for death in the dictionary
			if (patientDiedConcept != null) {
				
				// If the exist reason == patient died
				if (exitReasonConcept.equals(patientDiedConcept)) {
					
					Concept causeOfDeathConcept = null;
					try {
						causeOfDeathConcept = cs.getConcept(causeOfDeathConceptId);
					}
					catch (Exception e) {
						causeOfDeathConcept = null;
					}
					
					// Cause of death concept exists
					if (causeOfDeathConcept != null) {
						try {
							ps.processDeath(patient, exitDate, causeOfDeathConcept, otherReason);
						}
						catch (Exception e) {
							log.warn("Caught error", e);
							ret = "Internal error while trying to process patient death - unable to proceed. Cause: " + e.getMessage();
						}
					}
					// cause of death concept does not exist
					else {
						ret = "Unable to locate cause of death in dictionary - cannot proceed";
					}
				}

				// Otherwise, we process this as an exit
				else {
					try {
						ps.exitFromCare(patient, exitDate, exitReasonConcept);
					}
					catch (Exception e) {
						log.warn("Caught error", e);
						ret = "Internal error while trying to exit patient from care - unable to exit patient from care at this time. Cause: " + e.getMessage();
					}
				}
			}

			// If the system does not recognize death as a concept, then we exit from care
			else {
				try {
					ps.exitFromCare(patient, exitDate, exitReasonConcept);
				}
				catch (Exception e) {
					log.warn("Caught error", e);
					ret = "Internal error while trying to exit patient from care - unable to exit patient from care at this time. Cause: " + e.getMessage();
				}
			}
			log.debug("Exited from care, it seems");<commit_msg>Random formatting fix<commit_after>/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.web.dwr;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.GlobalProperty;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.PersonAddress;
import org.openmrs.activelist.Allergy;
import org.openmrs.activelist.AllergySeverity;
import org.openmrs.activelist.AllergyType;
import org.openmrs.activelist.Problem;
import org.openmrs.activelist.ProblemModifier;
import org.openmrs.api.APIAuthenticationException;
import org.openmrs.api.APIException;
import org.openmrs.api.ConceptService;
import org.openmrs.api.DuplicateIdentifierException;
import org.openmrs.api.GlobalPropertyListener;
import org.openmrs.api.IdentifierNotUniqueException;
import org.openmrs.api.InsufficientIdentifiersException;
import org.openmrs.api.InvalidCheckDigitException;
import org.openmrs.api.InvalidIdentifierFormatException;
import org.openmrs.api.LocationService;
import org.openmrs.api.PatientIdentifierException;
import org.openmrs.api.PatientService;
import org.openmrs.api.context.Context;
import org.openmrs.patient.IdentifierValidator;
import org.openmrs.patient.UnallowedIdentifierException;
import org.openmrs.util.OpenmrsConstants;

/**
 * DWR patient methods. The methods in here are used in the webapp to get data from the database via
 * javascript calls.
 * 
 * @see PatientService
 */
public class DWRPatientService implements GlobalPropertyListener {
	
	private static final Log log = LogFactory.getLog(DWRPatientService.class);
	
	private static Integer maximumResults;
	
	/**
	 * Search on the <code>searchValue</code>. If a number is in the search string, do an identifier
	 * search. Else, do a name search
	 * 
	 * @param searchValue string to be looked for
	 * @param includeVoided true/false whether or not to included voided patients
	 * @return Collection<Object> of PatientListItem or String
	 * @should return only patient list items with nonnumeric search
	 * @should return string warning if invalid patient identifier
	 * @should not return string warning if searching with valid identifier
	 * @should include string in results if doing extra decapitated search
	 * @should not return duplicate patient list items if doing decapitated search
	 * @should not do decapitated search if numbers are in the search string
	 * @should get results for patients that have edited themselves
	 * @should logged in user should load their own patient object
	 */
	public Collection<Object> findPatients(String searchValue, boolean includeVoided) {
		return findBatchOfPatients(searchValue, includeVoided, null, null);
	}
	
	/**
	 * Search on the <code>searchValue</code>. If a number is in the search string, do an identifier
	 * search. Else, do a name search
	 * 
	 * @see PatientService#getPatients(String, String, List, boolean, int, Integer)
	 * @param searchValue string to be looked for
	 * @param includeVoided true/false whether or not to included voided patients
	 * @param start The starting index for the results to return
	 * @param length The number of results of return
	 * @return Collection<Object> of PatientListItem or String
	 * @since 1.8
	 */
	@SuppressWarnings("unchecked")
	public Collection<Object> findBatchOfPatients(String searchValue, boolean includeVoided, Integer start, Integer length) {
		if (maximumResults == null)
			maximumResults = getMaximumSearchResults();
		if (length != null && length > maximumResults)
			length = maximumResults;
		
		// the list to return
		List<Object> patientList = new Vector<Object>();
		
		PatientService ps = Context.getPatientService();
		Collection<Patient> patients;
		
		try {
			patients = ps.getPatients(searchValue, start, length);
		}
		catch (APIAuthenticationException e) {
			patientList.add(Context.getMessageSourceService().getMessage("Patient.search.error") + " - " + e.getMessage());
			return patientList;
		}
		
		patientList = new Vector<Object>(patients.size());
		for (Patient p : patients)
			patientList.add(new PatientListItem(p));
		// if the length wasn't limited to less than 3 or this is the second ajax call
		// and only 2 results found and a number was not in the
		// search, then do a decapitated search: trim each word
		// down to the first three characters and search again
		if ((length == null || length > 2) && patients.size() < 3 && !searchValue.matches(".*\\d+.*")) {
			String[] names = searchValue.split(" ");
			String newSearch = "";
			for (String name : names) {
				if (name.length() > 3)
					name = name.substring(0, 4);
				newSearch += " " + name;
			}
			newSearch = newSearch.trim();
			
			if (!newSearch.equals(searchValue)) {
				Collection<Patient> newPatients = ps.getPatients(newSearch, start, length);
				patients = CollectionUtils.union(newPatients, patients); // get unique hits
				//reconstruct the results list
				if (newPatients.size() > 0) {
					patientList = new Vector<Object>(patients.size());
					//patientList.add("Minimal patients returned. Results for <b>" + newSearch + "</b>");
					for (Patient p : newPatients) {
						PatientListItem pi = new PatientListItem(p);
						patientList.add(pi);
					}
				}
			}
		}
		//no results found and a number was in the search --
		//should check whether the check digit is correct.
		else if (patients.size() == 0 && searchValue.matches(".*\\d+.*")) {
			
			//Looks through all the patient identifier validators to see if this type of identifier
			//is supported for any of them.  If it isn't, then no need to warn about a bad check
			//digit.  If it does match, then if any of the validators validates the check digit
			//successfully, then the user is notified that the identifier has been entered correctly.
			//Otherwise, the user is notified that the identifier was entered incorrectly.
			
			Collection<IdentifierValidator> pivs = ps.getAllIdentifierValidators();
			boolean shouldWarnUser = true;
			boolean validCheckDigit = false;
			boolean identifierMatchesValidationScheme = false;
			
			for (IdentifierValidator piv : pivs) {
				try {
					if (piv.isValid(searchValue)) {
						shouldWarnUser = false;
						validCheckDigit = true;
					}
					identifierMatchesValidationScheme = true;
				}
				catch (UnallowedIdentifierException e) {}
			}
			
			if (identifierMatchesValidationScheme) {
				if (shouldWarnUser)
					patientList
					        .add("<p style=\"color:red; font-size:big;\"><b>WARNING: Identifier has been typed incorrectly!  Please double check the identifier.</b></p>");
				else if (validCheckDigit)
					patientList
					        .add("<p style=\"color:green; font-size:big;\"><b>This identifier has been entered correctly, but still no patients have been found.</b></p>");
			}
		}
		
		return patientList;
	}
	
	/**
	 * Returns a map of results with the values as count of matches and a partial list of the
	 * matching patients (depending on values of start and length parameters) while the keys are are
	 * 'count' and 'objectList' respectively, if the length parameter is not specified, then all
	 * matches will be returned from the start index if specified.
	 * 
	 * @param searchValue patient name or identifier
	 * @param start the beginning index
	 * @param length the number of matching patients to return
	 * @param getMatchCount Specifies if the count of matches should be included in the returned map
	 * @return a map of results
	 * @throws APIException
	 * @since 1.8
	 */
	@SuppressWarnings("unchecked")
	public Map<String, Object> findCountAndPatients(String searchValue, Integer start, Integer length, boolean getMatchCount)
	        throws APIException {
		
		//Map to return
		Map<String, Object> resultsMap = new HashMap<String, Object>();
		Collection<Object> objectList = new Vector<Object>();
		try {
			PatientService ps = Context.getPatientService();
			int patientCount = 0;
			//if this is the first call
			if (getMatchCount) {
				patientCount += ps.getCountOfPatients(searchValue);
				
				// if only 2 results found and a number was not in the
				// search, then do a decapitated search: trim each word
				// down to the first three characters and search again				
				if ((length == null || length > 2) && patientCount < 3 && !searchValue.matches(".*\\d+.*")) {
					String[] names = searchValue.split(" ");
					String newSearch = "";
					for (String name : names) {
						if (name.length() > 3)
							name = name.substring(0, 4);
						newSearch += " " + name;
					}
					
					newSearch = newSearch.trim();
					if (!newSearch.equals(searchValue)) {
						//since we already know that the list is small, it doesn't hurt to load the hits
						//so that we can remove them from the list of the new search results and get the 
						//accurate count of matches
						Collection<Patient> patients = ps.getPatients(searchValue);
						newSearch = newSearch.trim();
						Collection<Patient> newPatients = ps.getPatients(newSearch);
						newPatients = CollectionUtils.union(newPatients, patients);
						//Re-compute the count of all the unique patient hits
						patientCount = newPatients.size();
						if (newPatients.size() > 0) {
							resultsMap.put("notification", Context.getMessageSourceService().getMessage(
							    "Patient.warning.minimalSearchResults", new Object[] { newSearch }, Context.getLocale()));
						}
					}
				}

				//no results found and a number was in the search --
				//should check whether the check digit is correct.
				else if (patientCount == 0 && searchValue.matches(".*\\d+.*")) {
					
					//Looks through all the patient identifier validators to see if this type of identifier
					//is supported for any of them.  If it isn't, then no need to warn about a bad check
					//digit.  If it does match, then if any of the validators validates the check digit
					//successfully, then the user is notified that the identifier has been entered correctly.
					//Otherwise, the user is notified that the identifier was entered incorrectly.
					
					Collection<IdentifierValidator> pivs = ps.getAllIdentifierValidators();
					boolean shouldWarnUser = true;
					boolean validCheckDigit = false;
					boolean identifierMatchesValidationScheme = false;
					
					for (IdentifierValidator piv : pivs) {
						try {
							if (piv.isValid(searchValue)) {
								shouldWarnUser = false;
								validCheckDigit = true;
							}
							identifierMatchesValidationScheme = true;
						}
						catch (UnallowedIdentifierException e) {}
					}
					
					if (identifierMatchesValidationScheme) {
						if (shouldWarnUser)
							resultsMap.put("notification", "<b>"
							        + Context.getMessageSourceService().getMessage("Patient.warning.inValidIdentifier")
							        + "<b/>");
						else if (validCheckDigit)
							resultsMap.put("notification", "<b style=\"color:green;\">"
							        + Context.getMessageSourceService().getMessage("Patient.message.validIdentifier")
							        + "<b/>");
					}
				} else {
					//ensure that count never exceeds this value because the API's service layer would never
					//return more than it since it is limited in the DAO layer
					if (maximumResults == null)
						maximumResults = getMaximumSearchResults();
					if (length != null && length > maximumResults)
						length = maximumResults;
					
					if (patientCount > maximumResults) {
						patientCount = maximumResults;
						if (log.isDebugEnabled())
							log.debug("Limitng the size of matching patients to " + maximumResults);
					}
				}
				
			}
			
			//if we have any matches or this isn't the first ajax call when the caller
			//requests for the count
			if (patientCount > 0 || !getMatchCount)
				objectList = findBatchOfPatients(searchValue, false, start, length);
			
			resultsMap.put("count", patientCount);
			resultsMap.put("objectList", objectList);
		}
		catch (Exception e) {
			log.error("Error while searching for patients", e);
			objectList.clear();
			objectList.add(Context.getMessageSourceService().getMessage("Patient.search.error") + " - " + e.getMessage());
			resultsMap.put("count", 0);
			resultsMap.put("objectList", objectList);
		}
		return resultsMap;
	}
	
	/**
	 * Convenience method for dwr/javascript to convert a patient id into a Patient object (or at
	 * least into data about the patient)
	 * 
	 * @param patientId the {@link Patient#getPatientId()} to match on
	 * @return a truncated Patient object in the form of a PatientListItem
	 */
	public PatientListItem getPatient(Integer patientId) {
		PatientService ps = Context.getPatientService();
		Patient p = ps.getPatient(patientId);
		PatientListItem pli = new PatientListItem(p);
		if (p != null && p.getAddresses() != null && p.getAddresses().size() > 0) {
			PersonAddress pa = (PersonAddress) p.getAddresses().toArray()[0];
			pli.setAddress1(pa.getAddress1());
			pli.setAddress2(pa.getAddress2());
		}
		return pli;
	}
	
	/**
	 * find all patients with duplicate attributes (searchOn)
	 * 
	 * @param searchOn
	 * @return list of patientListItems
	 */
	public Vector<Object> findDuplicatePatients(String[] searchOn) {
		Vector<Object> patientList = new Vector<Object>();
		
		try {
			List<String> options = new Vector<String>(searchOn.length);
			for (String s : searchOn)
				options.add(s);
			
			List<Patient> patients = Context.getPatientService().getDuplicatePatientsByAttributes(options);
			
			if (patients.size() > 200)
				patients.subList(0, 200);
			
			for (Patient p : patients)
				patientList.add(new PatientListItem(p));
		}
		catch (Exception e) {
			log.error(e);
			patientList.add("Error while attempting to find duplicate patients - " + e.getMessage());
		}
		
		return patientList;
	}
	
	/**
	 * Auto generated method comment
	 * 
	 * @param patientId
	 * @param identifierType
	 * @param identifier
	 * @param identifierLocationId
	 * @return
	 */
	public String addIdentifier(Integer patientId, String identifierType, String identifier, Integer identifierLocationId) {
		
		String ret = "";
		
		if (identifier == null || identifier.length() == 0)
			return "PatientIdentifier.error.general";
		PatientService ps = Context.getPatientService();
		LocationService ls = Context.getLocationService();
		Patient p = ps.getPatient(patientId);
		PatientIdentifierType idType = ps.getPatientIdentifierTypeByName(identifierType);
		//ps.updatePatientIdentifier(pi);
		Location location = ls.getLocation(identifierLocationId);
		log.debug("idType=" + identifierType + "->" + idType + " , location=" + identifierLocationId + "->" + location
		        + " identifier=" + identifier);
		PatientIdentifier id = new PatientIdentifier();
		id.setIdentifierType(idType);
		id.setIdentifier(identifier);
		id.setLocation(location);
		
		// in case we are editing, check to see if there is already an ID of this type and location
		for (PatientIdentifier previousId : p.getActiveIdentifiers()) {
			if (previousId.getIdentifierType().equals(idType) && previousId.getLocation().equals(location)) {
				log.debug("Found equivalent ID: [" + idType + "][" + location + "][" + previousId.getIdentifier()
				        + "], about to remove");
				p.removeIdentifier(previousId);
			} else {
				if (!previousId.getIdentifierType().equals(idType))
					log.debug("Previous ID id type does not match: [" + previousId.getIdentifierType().getName() + "]["
					        + previousId.getIdentifier() + "]");
				if (!previousId.getLocation().equals(location)) {
					log.debug("Previous ID location is: " + previousId.getLocation());
					log.debug("New location is: " + location);
				}
			}
		}
		
		p.addIdentifier(id);
		
		try {
			ps.savePatient(p);
		}
		catch (InvalidIdentifierFormatException iife) {
			log.error(iife);
			ret = "PatientIdentifier.error.formatInvalid";
		}
		catch (InvalidCheckDigitException icde) {
			log.error(icde);
			ret = "PatientIdentifier.error.checkDigit";
		}
		catch (IdentifierNotUniqueException inue) {
			log.error(inue);
			ret = "PatientIdentifier.error.notUnique";
		}
		catch (DuplicateIdentifierException die) {
			log.error(die);
			ret = "PatientIdentifier.error.duplicate";
		}
		catch (InsufficientIdentifiersException iie) {
			log.error(iie);
			ret = "PatientIdentifier.error.insufficientIdentifiers";
		}
		catch (PatientIdentifierException pie) {
			log.error(pie);
			ret = "PatientIdentifier.error.general";
		}
		
		return ret;
	}
	
	/**
	 * Auto generated method comment
	 * 
	 * @param patientId
	 * @param reasonForExitId
	 * @param dateOfExit
	 * @param causeOfDeath
	 * @param otherReason
	 * @return
	 */
	public String exitPatientFromCare(Integer patientId, Integer exitReasonId, String exitDateStr,
	        Integer causeOfDeathConceptId, String otherReason) {
		log.debug("Entering exitfromcare with [" + patientId + "] [" + exitReasonId + "] [" + exitDateStr + "]");
		String ret = "";
		
		PatientService ps = Context.getPatientService();
		ConceptService cs = Context.getConceptService();
		
		Patient patient = null;
		try {
			patient = ps.getPatient(patientId);
		}
		catch (Exception e) {
			patient = null;
		}
		
		if (patient == null) {
			ret = "Unable to find valid patient with the supplied identification information - cannot exit patient from care";
		}
		
		// Get the exit reason concept (if possible)
		Concept exitReasonConcept = null;
		try {
			exitReasonConcept = cs.getConcept(exitReasonId);
		}
		catch (Exception e) {
			exitReasonConcept = null;
		}
		
		// Exit reason error handling
		if (exitReasonConcept == null) {
			ret = "Unable to locate reason for exit in dictionary - cannot exit patient from care";
		}
		
		// Parse the exit date
		Date exitDate = null;
		if (exitDateStr != null) {
			SimpleDateFormat sdf = Context.getDateFormat();
			try {
				exitDate = sdf.parse(exitDateStr);
			}
			catch (ParseException e) {
				exitDate = null;
			}
		}
		
		// Exit date error handling
		if (exitDate == null) {
			ret = "Invalid date supplied - cannot exit patient from care without a valid date.";
		}
		
		// If all data is provided as expected
		if (patient != null && exitReasonConcept != null && exitDate != null) {
			
			// need to check first if this is death or not
			String patientDiedConceptId = Context.getAdministrationService().getGlobalProperty("concept.patientDied");
			
			Concept patientDiedConcept = null;
			if (patientDiedConceptId != null) {
				patientDiedConcept = cs.getConcept(patientDiedConceptId);
			}
			
			// If there is a concept for death in the dictionary
			if (patientDiedConcept != null) {
				
				// If the exist reason == patient died
				if (exitReasonConcept.equals(patientDiedConcept)) {
					
					Concept causeOfDeathConcept = null;
					try {
						causeOfDeathConcept = cs.getConcept(causeOfDeathConceptId);
					}
					catch (Exception e) {
						causeOfDeathConcept = null;
					}
					
					// Cause of death concept exists
					if (causeOfDeathConcept != null) {
						try {
							ps.processDeath(patient, exitDate, causeOfDeathConcept, otherReason);
						}
						catch (Exception e) {
							log.warn("Caught error", e);
							ret = "Internal error while trying to process patient death - unable to proceed. Cause: "
							        + e.getMessage();
						}
					}
					// cause of death concept does not exist
					else {
						ret = "Unable to locate cause of death in dictionary - cannot proceed";
					}
				}

				// Otherwise, we process this as an exit
				else {
					try {
						ps.exitFromCare(patient, exitDate, exitReasonConcept);
					}
					catch (Exception e) {
						log.warn("Caught error", e);
						ret = "Internal error while trying to exit patient from care - unable to exit patient from care at this time. Cause: "
						        + e.getMessage();
					}
				}
			}

			// If the system does not recognize death as a concept, then we exit from care
			else {
				try {
					ps.exitFromCare(patient, exitDate, exitReasonConcept);
				}
				catch (Exception e) {
					log.warn("Caught error", e);
					ret = "Internal error while trying to exit patient from care - unable to exit patient from care at this time. Cause: "
					        + e.getMessage();
				}
			}
			log.debug("Exited from care, it seems");
{'fraction_non_alphanumeric': 0.10228566009104705, 'fraction_numerical': 0.0018968133535660092, 'mean_word_length': 3.447643150901613, 'pattern_counts': {'":': 0, '<': 93, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4553720', 'n_tokens_mistral': 14320, 'n_tokens_neox': 11522, 'n_words': 3815}