/********************************************************************
 * Copyright © 2006 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.esr.common.batchprocess.datasync;

import java.util.List;

import org.apache.commons.lang.Validate;

import gov.va.med.fw.batchprocess.DataFileProcessExecutionContext;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.support.AbstractSpawnedThreadTask;
import gov.va.med.fw.util.builder.Builder;
import gov.va.med.fw.util.builder.BuilderException;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.ee.PrisonerOfWar;
import gov.va.med.esr.common.model.ee.PurpleHeart;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.registry.Registry;
import gov.va.med.esr.common.model.registry.RegistryTrait;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.RegistrySearchCriteria;
import gov.va.med.esr.service.RegistrySearchResultBean;
import gov.va.med.esr.service.RegistryService;

/**
 * Prototype Spring bean for concurrent processing of file data.
 * 
 * Created May 3, 2006 5:31:00 PM
 * 
 * @author DNS   BOHMEG
 */
public class HECLegacyDataSynchronizationConsumerSpawnedThreadTask extends
		AbstractSpawnedThreadTask {
	private DataFileProcessExecutionContext context;

	private List acquiredData;

	private Builder mergeBuilder;

	private PersonService personService;

	private RegistryService registryService;

	/*
	 * (non-Javadoc)
	 * 
	 * @see gov.va.med.fw.service.support.SpawnedThreadTask#execute()
	 */
	public void executeTask() throws Throwable {
		if (logger.isDebugEnabled()) {
			logger.debug("HECLegacyDataSynchronizationConsumerSpawnedThreadTask started");
		}
		// ordering implied here for the first element (must be identity
		// information)
		HECLegacyIdentityFileData identityData = (HECLegacyIdentityFileData) acquiredData
				.get(0);
		try {
			if (identityData.isPersonUpdate()) {
				// retrieve Person
				Person person = matchPerson(identityData);
				// overlay data
				overlayFileDataOnPerson(person, acquiredData, identityData.isCommsOnly());
				// call Rules
				// Code CR7502 - If Comms Only change, do not trigger E&E calculations
				if (!identityData.isCommsOnly()) {
					executeRules(person);
				}
			} else {
				// retrieve Registry
				Registry registry = matchRegistry(identityData);
				// overlay data
				overlayFileDataOnRegistry(registry, acquiredData);
				// save Registry
				registryService.updateRegistry(registry);
			}
			context.getProcessStatistics().incrementNumberOfSuccessfulRecords();	
		} catch (Exception e) {
			handleFailure(context, identityData, e);
		} finally {
			if (logger.isDebugEnabled()) {
				logger.debug("HECLegacyDataSynchronizationConsumerSpawnedThreadTask ended");
			}
			HECLegacyDataSynchronizationConsumerProcess.adjustTaskCount(
					context, -1);
			Object threadCreator = getThreadCreator(context);
			synchronized (threadCreator) {
				threadCreator.notifyAll();
			}
			if (logger.isDebugEnabled()) {
				logger.debug("HECLegacyDataSynchronizationConsumerSpawnedThreadTask notified creator");
			}
			
			/*
			 * force release of context as thay will hold the ThreadPool(->Threads)
			 * this is necessary to GC ThreadPool threads at end
			 */
			context = null;
		}
	}

	private Object getThreadCreator(DataProcessExecutionContext context) {
		return context
				.getContextData()
				.get(
						HECLegacyDataSynchronizationConsumerProcess.CONTEXT_THREAD_CREATOR);
	}

	private void overlayFileDataOnPerson(Person person, List acquiredData,
			boolean isCommsOnly) throws Exception {
		Object[] args = new Object[3];
		args[0] = person;
		args[1] = acquiredData;
		args[2] = Boolean.valueOf(isCommsOnly);
		mergeBuilder.build(args);
	}

	private void overlayFileDataOnRegistry(Registry registry, List acquiredData)
			throws Exception {
		Object[] args = new Object[3];
		args[0] = registry;
		args[1] = acquiredData;
		mergeBuilder.build(args);
	}

	private void executeRules(Person person) throws ServiceException {
		personService.processDataSyncFromHECLegacy(person);
	}

	private Person matchPerson(HECLegacyIdentityFileData identityData)
			throws Exception {
		PersonEntityKey vpid = CommonEntityKeyFactory
				.createVPIDEntityKey(identityData.getICN());
		if (logger.isDebugEnabled())
			logger.debug("Retrieving Person by short-form VPID (ICN): " + vpid);
		// retrieve Person
		Person person = personService.getPerson(vpid);
		if (person == null)
			throw new RuntimeException("Unable to find Person for VPID: "
					+ vpid);
		return person;
	}

	private Registry matchRegistry(HECLegacyIdentityFileData identityData) throws Exception {
		RegistrySearchCriteria query = new RegistrySearchCriteria();
		query.setSsn(identityData.getSSN());
		query.setLastName(identityData.getLegalName().getFamilyName());
		query.setFirstName(identityData.getLegalName().getGivenName());
		query.setRegistryType(identityData.getRegistryType());
		
		Registry onFile = null;
		// TODO: make these 2 RegistryService api calls into one!
		List registryData = registryService.search(query);
		if(registryData.size() == 0) {
			if(identityData.getRegistryType() != null) {
				if(logger.isInfoEnabled())
					logger.info("Unable to find a matching Registry entry in ESR.....creating a new one");
				if(identityData.getRegistryType().isPrisonerOfWar())
					onFile = new PrisonerOfWar();
				else if(identityData.getRegistryType().isPurpleHeart())
					onFile = new PurpleHeart();
				else {
					throw new IllegalStateException("Unable to find a matching Registry entry in ESR...unable to create a new one " +
						"unsupported RegistryType");					
				}
				onFile.setRegistryTrait(new RegistryTrait());
				onFile.getRegistryTrait().setRegistryType(identityData.getRegistryType());				
			} else {
				throw new IllegalStateException("Unable to find a matching Registry entry in ESR...unable to create a new one " +
						"since HECLegacy sent null for RegistryType");
			}			
		} else if(registryData.size() == 1) {
			RegistrySearchResultBean searchResult = (RegistrySearchResultBean) registryData.get(0);
			onFile = registryService.getRegistryById(searchResult.getEntityKey(), searchResult.getRegistryType());			
		} else if(registryData.size() > 1) {
			throw new IllegalStateException("Can not find a singular match by Registry traits");
		}

		return onFile;
	}

	private void handleFailure(DataFileProcessExecutionContext context,
			HECLegacyIdentityFileData identityData, Exception e) {

		String exceptionText = null;
		
		if (e == null) {
			exceptionText = "Error: Unable to synchronize data for identity data: " + identityData;
		}
		else {
			if(e instanceof BuilderException) {
				// do this since reflection is being used...
				Throwable cause = e.getCause(); // typically this is a non-helpful reflection exception 
				Throwable causesCause = cause != null ? cause.getCause() : null;
				exceptionText = "Error: Unable to synchronize data for identity data: "
						+ identityData;
				if (causesCause != null) {
					exceptionText += " because of BuilderException with cause: " + cause + " and causes cause: " + causesCause;
				}
			}
			else {
				exceptionText = "Error: Unable to synchronize data for identity data: "
						+ identityData + " because of exception: " + e;		
			}
		}
		
		if (logger.isErrorEnabled()) {
			logger.error(exceptionText);
		}
		context.getProcessStatistics().incrementNumberOfErrorRecords();
		context.getExceptionData().add(identityData);
		context.getExceptionData().add(exceptionText + "\n\n");
	}

	/**
	 * @return Returns the acquiredData.
	 */
	public List getAcquiredData() {
		return acquiredData;
	}

	/**
	 * @param acquiredData
	 *            The acquiredData to set.
	 */
	public void setAcquiredData(List acquiredData) {
		this.acquiredData = acquiredData;
	}

	/**
	 * @return Returns the context.
	 */
	public DataFileProcessExecutionContext getContext() {
		return context;
	}

	/**
	 * @param context
	 *            The context to set.
	 */
	public void setContext(DataFileProcessExecutionContext context) {
		this.context = context;
	}

	/**
	 * @return Returns the personService.
	 */
	public PersonService getPersonService() {
		return personService;
	}

	/**
	 * @param personService
	 *            The personService to set.
	 */
	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}

	/**
	 * @return Returns the registryService.
	 */
	public RegistryService getRegistryService() {
		return registryService;
	}

	/**
	 * @param registryService
	 *            The registryService to set.
	 */
	public void setRegistryService(RegistryService registryService) {
		this.registryService = registryService;
	}

	public void afterPropertiesSet() {
		Validate.notNull(personService, "personService can not be null");
		Validate.notNull(registryService, "registryService can not be null");
		Validate.notNull(mergeBuilder, "mergeBuilder can not be null");
	}

	/**
	 * @return Returns the mergeBuilder.
	 */
	public Builder getMergeBuilder() {
		return mergeBuilder;
	}

	/**
	 * @param mergeBuilder
	 *            The mergeBuilder to set.
	 */
	public void setMergeBuilder(Builder mergeBuilder) {
		this.mergeBuilder = mergeBuilder;
	}

}
