/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.esr.common.persistent.history;

// Java Classes
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

// Library Classes
import org.apache.commons.lang.Validate;
import org.hibernate.Query;
import org.hibernate.Session;

// Framework Classes
import gov.va.med.esr.service.impl.ChangeEvent;
import gov.va.med.esr.service.impl.FinancialsChangeEvent;
import gov.va.med.fw.model.AbstractVersionedEntity;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.FinancialInformation;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.person.Dependent;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.Relation;
import gov.va.med.esr.common.model.person.Spouse;
import gov.va.med.esr.common.model.person.SSN;

// Common Classes

/**
 * @author DNS   CHENJ2
 * Returns spouse and dependent demographic history data, supporting the
 * dependent history screen.
 */
public class DependentHistoryDAOImpl extends FinancialsHistoryDAOImpl {

	/**
	 *
	 */
	private static final long serialVersionUID = 7443861181862802236L;

	// getHistoryByChangeTime queries name
	private static final String[][] getHistoryByChangeTimeQueries =
		{{"spouseQuery_GetHistoryId", "spouseQuery_GetHistoryByChangeTime"},
		 {"dependentQuery_GetHistoryId", "dependentQuery_GetHistoryByChangeTime"}};

	private int RELATION_SPOUSE = 0;
	private int RELATION_DEPENDENT = 1;

	/**
	 * A default constructor
	 */
	public DependentHistoryDAOImpl() {
		super();
	}

	/**
	 * Retrieves historical data.  Customized using hand coded HQL.  This is
	 * due to spouseFinancials.spouse is a many-to-one relationship, and therefore
	 * is not allowed the "order-by" clause, or the filters defined in the mapping
	 * file.
	 * @param changeEvent
	 * @return
	 */
	protected AbstractVersionedEntity getHistoricalEntity(Session session, ChangeEvent changeEvent)
		throws DAOException
	{
		Validate.isTrue((changeEvent instanceof FinancialsChangeEvent), "changeEvent must be instance of FinancialsChangeEvent!");
		FinancialsChangeEvent fce = (FinancialsChangeEvent)changeEvent;

		// create empty holder objects: person, financial statement,
		// and spouseFinancials, and dependent financials
		Person p = new Person();
		Integer incomeYear = fce.getIncomeYear();
		FinancialStatement fs = new FinancialStatement(p, incomeYear);
		p.setFinancialStatement(incomeYear, fs);

		// now create spouse and dependent financials based on customized query restuls
		Set spouses = null;
		Set dependents = null;
		Iterator iter = null;

		// spouse:
		Spouse sp = null;
		spouses = getHistoricalRelations(session, fce, RELATION_SPOUSE);
		iter = spouses.iterator();
		while (iter.hasNext()) {
			sp = (Spouse)iter.next();
			fs.addSpouseFinancials(new SpouseFinancials(sp));
		}

		// dependents
		Dependent dep = null;

		dependents = getHistoricalRelations(session, fce, RELATION_DEPENDENT);
		iter = dependents.iterator();
		while (iter.hasNext()) {
			dep = (Dependent)iter.next();
			fs.addDependentFinancials(new DependentFinancials(dep));
		}

		// evict the data, since the default evict behavior in the base
		// class doesn't work here.  The top level objects are merely empty holders
		for (iter = spouses.iterator(); iter.hasNext();) {
			evict(iter.next());
		}
		for (iter = dependents.iterator(); iter.hasNext();) {
			evict(iter.next());
		}

		if (logger.isDebugEnabled())
            logger.debug("DependentHistoryDaoImpl getEntity: " + p);

		return p;
	}

	/**
	 * Execute named query to retrieve spouse or dependent object
	 *
	 * @param changeEvent
	 * @param relation
	 * @return
	 */
	private Set getHistoricalRelations(Session session, FinancialsChangeEvent changeEvent, int relation)
	{
		BigDecimal id = null;
		AbstractVersionedEntity entity = null;
		Set relations = new HashSet();
		List objResults = null;
		Iterator objIter = null;

		// first get the id(s)
		Query q = session.getNamedQuery(getHistoryByChangeTimeQueries[relation][0]);
		q.setParameter("parent_id", (BigDecimal) (changeEvent.getEntityKey().getKeyValue()));

		List keyResults = q.list();

		if (keyResults == null)
			return null;

		Iterator iter = keyResults.iterator();
		while (iter.hasNext()) {
			id = (BigDecimal) iter.next();
			// now using the id, execute the second query, getting the object
			// this approach allows the filters to be applied.  Otherwise the
			// filters cannot be applied to the many-to-one relationship inside
			// the mapping file, and therefore will not produce the correct results.

			q = session.getNamedQuery(getHistoryByChangeTimeQueries[relation][1]);
			q.setParameter("id", id);
			objResults = q.list();

			if (objResults != null) {
				for (objIter = objResults.iterator(); objIter.hasNext();) {
					entity = (AbstractVersionedEntity) objIter.next();
				}
			}
			relations.add(entity);
		}

		if (logger.isDebugEnabled())
            logger.debug("DependentHistoryDaoImpl getRelations: " + relations);

		return relations;
	}

	/*
	 *
	 * @see gov.va.med.esr.common.persistent.history.HistoryDAOImpl#removeDeletedRecords(gov.va.med.fw.model.AbstractKeyedEntity)
	 */
	protected void removeDeletedRecords(AbstractVersionedEntity entity) throws DAOException
	{
		// all top layer objects should be detached, and empty:
		// person, financialStatement, spouse/dependentFinancials
		// So just check data at the spouse/dependent level and ssn level
		Person p = (Person)entity;

		Map fsMap = p.getFinancialStatements();
		Set keySet = fsMap.keySet();
		if (keySet != null) {
			Iterator i=keySet.iterator();
			if (i.hasNext()) {

				FinancialStatement fs = (FinancialStatement)fsMap.get(i.next());

				// check all spouses and their children,
				// remove any deleted from the parent -- Financial Statement
				processDelete(fs.getSpouseFinancials(), fs, RELATION_SPOUSE);

				// check all dependents and their children,
				// remove any deleted from the parent -- Financial Statement
				processDelete(fs.getDependentFinancials(), fs, RELATION_DEPENDENT);
			}
		}
	}

	/**
	 * Clean out any deleted SpouseFinancial/DependentFinancials (actually the Spouse/Dependnent
	 * object being deleted) from the FinancialStatement's set
	 *
	 * @param ssns
	 * @param reportedOn
	 * @throws DAOException
	 */
	protected void processDelete(Set financials, FinancialStatement fs, int relationType)
			throws DAOException {

		FinancialInformation fi = null;
		Relation reportedOn = null;

		Set toBeDeletedSet = null;

		if (financials != null) {
			for (Iterator i=financials.iterator(); i.hasNext();) {
				fi = (FinancialInformation) i.next();
				if (relationType == RELATION_SPOUSE)
					reportedOn = ((SpouseFinancials)fi).getReportedOn();
				else
					reportedOn = ((DependentFinancials)fi).getReportedOn();

				if (reportedOn.isDeleted().booleanValue()) {
					if (toBeDeletedSet == null)
						toBeDeletedSet = new HashSet();

					toBeDeletedSet.add(fi);
					// We cannot call i.remove, since it's an unmodifieable set;
					// we cannot call fs.remove...(fi); it would cause concurrentModificationException

				} else {
					// if reportedOn is not deleted, go check ssns
					Set ssns = reportedOn.getSsns();
					if (ssns != null)
						processDelete(ssns, reportedOn);
				}
			}
		}

		if (toBeDeletedSet != null) {
			if (relationType == RELATION_SPOUSE)
				fs.removeSpouseFinancials(toBeDeletedSet);
			else
				fs.removeDependentFinancials(toBeDeletedSet);
		}
	}

	/**
	 *
	 * @param ssns
	 * @param reportedOn
	 * @throws DAOException
	 */
	protected void processDelete(Set ssns, Relation reportedOn)
			throws DAOException {
		SSN ssn = null;
		Set toBeDeletedSet = null;

		for (Iterator i = ssns.iterator(); i.hasNext();) {
			ssn = (SSN) i.next();

			if (ssn.isDeleted().booleanValue()) {
				if (toBeDeletedSet == null) {
					toBeDeletedSet = new HashSet();
				}
				toBeDeletedSet.add(ssn);
				// We cannot call i.remove, since it's an unmodifieable set;
				// we cannot call reportedOn.removeSsn(ssn); it would cause concurrentModificationException
			}
		}

		if (toBeDeletedSet != null) {
			for (Iterator i=toBeDeletedSet.iterator(); i.hasNext();) {
				reportedOn.removeSsn((SSN)i.next());
			}
		}
	}
}