package gov.va.med.esr.common.persistent.person.hibernate;

// Java Classes
import gov.va.med.esr.common.model.person.PersonMergeInfo;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.persistent.person.PersonMergeDAO;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.MaxRecordsExceededException;
import gov.va.med.fw.persistent.hibernate.AbstractDAOAction;
import gov.va.med.fw.persistent.hibernate.GenericDAOImpl;
import gov.va.med.fw.persistent.hibernate.PaginatedQueryExecutor;
import gov.va.med.fw.service.pagination.SearchQueryInfo;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.Validate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateSystemException;

/**
 * Person Merge DAO Implementation.
 * 
 * @author DNS   CHENJ2
 */
public class PersonMergeDAOImpl extends GenericDAOImpl implements
		PersonMergeDAO {
	// Find by Person Id
	private static final String PRIMARY_PERSON_ENTITY_KEYS_CONTEXT_KEY = "primaryPersonEntityKeys";
	private static final String SEARCH_QUERY_INFO_CONTEXT_KEY = "searchQueryInfo";
	private static final String FIND_PERSON_MERGE_BY_PERSON_ID = "personMerge_FindByPersonId";
	private static final String FIND_PERSON_MERGE_BY_DEPRECATED_ID = "personMerge_FindByDeprecatedPersonId";
	
	private static final String FIND_PERSON_MERGE_BY_PRIMARY_DEPRECATED_PERSON_IDS = "personMerge_FindByPrimaryDeprecatedPersonIds";
	private static final String PARAM_PERSON_ID = "personId";

	private static final String FIND_NOT_COMPLETED_PERSON_MERGE_BY_PRIMARY_PERSON_ENTITY_KEYS = "personMerge_FindNotCompletedByPrimaryPersonEntityKeys";
	private static final String FIND_NOT_COMPLETED_PERSON_MERGE_BY_PRIMARY_PERSON_ENTITY_KEYS_COUNT = "personMerge_FindNotCompletedByPrimaryPersonEntityKeysCount";

	// Find by Person Id's
	private static final String FIND_PERSON_MERGE_BY_PERSON_IDS = "personMerge_FindByPersonIds";
	private static final String PARAM_PERSON_ID1 = "personId1";
	private static final String PARAM_PERSON_ID2 = "personId2";
	private static final String PARAM_PERSON_IDS = "personIds";

	// Find all not completed
	private static final String LOAD_ALL_NOT_COMPLETED = "personMerge_FindAllNotCompleted";

	private static final String LOAD_ALL_NOT_COMPLETED_WHERE = "  MERGE_END_DATE IS NULL ";

	private static final String PERSON_MERGE_COLUMNS = " PERSON_MERGE_ID, PRIMARY_PERSON_ID, DEPRECATED_PERSON_ID, MERGE_RECORD_LOCKED_FLAG, MERGE_START_DATE, MERGE_END_DATE, RECORD_CREATED_BY, RECORD_CREATED_DATE, RECORD_MODIFIED_BY, RECORD_MODIFIED_DATE, RECORD_MODIFIED_COUNT, MERGE_DATA_TEXT ";

	private static final String PERSON_MERGE_TABLE = " PERSON_MERGE ";

	private static final String LOAD_ALL_NOT_COMPLETED_SELECT_QUERY = "SELECT "
			+ PERSON_MERGE_COLUMNS + " FROM " + PERSON_MERGE_TABLE + " WHERE "
			+ LOAD_ALL_NOT_COMPLETED_WHERE;

	private static final String LOAD_ALL_NOT_COMPLETED_COUNT_QUERY = "SELECT COUNT(*) FROM "
			+ PERSON_MERGE_TABLE + " WHERE " + LOAD_ALL_NOT_COMPLETED_WHERE;

	/**
	 * 
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = -7683550608719051161L;

	/**
	 * @see gov.va.med.esr.common.persistent.person.PersonMergeDAO#getPersonMergeInfo(gov.va.med.esr.common.model.person.id.PersonIdEntityKey)
	 */
	public PersonMergeInfo getPersonMergeInfo(PersonIdEntityKey personId)
			throws DAOException {
		Validate.notNull(personId, "Person Id can not be null.");

		try {
			List results = findByNamedQueryAndNamedParam(
					FIND_PERSON_MERGE_BY_PERSON_ID, PARAM_PERSON_ID,
					new BigDecimal(personId.getKeyValueAsString()));
			return ((results == null) || results.isEmpty()) ? null
					: (PersonMergeInfo) results.get(0);
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find PersonMergeInfo by personId.", ex);
		}
	}
	
	public PersonMergeInfo getPersonMergeInfoByDeprecatedId(PersonIdEntityKey personId)
		throws DAOException {
		Validate.notNull(personId, "Person Id can not be null.");

		try {
			List results = findByNamedQueryAndNamedParam(
					FIND_PERSON_MERGE_BY_DEPRECATED_ID, PARAM_PERSON_ID,
					new BigDecimal(personId.getKeyValueAsString()));
			return ((results == null) || results.isEmpty()) ? null
					: (PersonMergeInfo) results.get(0);
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find PersonMergeInfo by personId.", ex);
		}
	}
    
	/*
	 * @see gov.va.med.esr.common.persistent.person.PersonMergeDAO#loadAllNotCompleted
	 */
	public List loadAllNotCompleted() throws DAOException {
		try {
			return this.getHibernateTemplate().findByNamedQuery(
					LOAD_ALL_NOT_COMPLETED);
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find all non-completed PersonMergeInfo objects.");
		}
	}

    /**
     * Loads all non-completed person merge info records
     *
     * @param searchQueryInf The search criteria
     * @return The list of person merge info records
     * @throws DAOException if any problems were encountered
     */
    public List loadAllNotCompleted(SearchQueryInfo searchQueryInfo) throws DAOException, MaxRecordsExceededException
    {
        try
        {
            Date startDate = new Date();
            
            Map contextData = new HashMap();
            contextData.put("searchQueryInfo", searchQueryInfo); 
            
            HibernateCallback callback = new AbstractDAOAction(contextData) { 
                public Object execute(Session session) throws DAOException {
                    // Build the count and data retrieval queries
                    Query countQuery = session.createQuery(" select count(*) FROM PersonMergeInfo pm  WHERE  pm.mergeEndDate = null ");
                    Query dataQuery =  session.createQuery(" FROM   PersonMergeInfo pm  WHERE  pm.mergeEndDate = null ORDER BY  pm.primaryPersonIdentifier");
                    

                    SearchQueryInfo targetQueryInfo = (SearchQueryInfo) this.getContextData().get("searchQueryInfo");
                    
                    // Execute the query to retrieve the results
                    PaginatedQueryExecutor queryExecutor = new PaginatedQueryExecutor(countQuery, dataQuery, targetQueryInfo);
                   
                    try {
                        return queryExecutor.executeQuery();
                    } catch(MaxRecordsExceededException e) {
                        throw new DAOException("max records exceeded", e);
                    }               
                }
            };
            List results = null;
            try {
                results = this.getHibernateTemplate().executeFind(callback);
            } catch(HibernateSystemException e) {
                MaxRecordsExceededException rootCause = (MaxRecordsExceededException) getRootExceptionOfType(e, MaxRecordsExceededException.class);
                if(rootCause != null)
                    throw rootCause;
                throw e;
            }           
            
            if (logger.isInfoEnabled())
            {
                logger.info("Total time to perform person merge search: " +
                    (new Date().getTime() - startDate.getTime()) + " ms.");
            }

            // Return the processed results to the caller
            return results;
        }
        catch (DataAccessException ex)
        {
            throw new DAOException("Failed to find all non-completed PersonMergeInfo objects.");
        }
    }
    
        
    
	public List findNotCompletedByPrimaryPersonEntityKeys(
			List personEntityKeys, SearchQueryInfo searchQueryInfo)
			throws DAOException, MaxRecordsExceededException {
		Validate.notEmpty(personEntityKeys, "personEntityKeys cannot be empty");
		Validate.notNull(searchQueryInfo, "searchQueryInfo cannot be empty");
		try {
			Date startDate = new Date();
			Map contextData = new HashMap();
			contextData.put(PRIMARY_PERSON_ENTITY_KEYS_CONTEXT_KEY,
					personEntityKeys);
			contextData.put(SEARCH_QUERY_INFO_CONTEXT_KEY, searchQueryInfo);

			HibernateCallback callback = new AbstractDAOAction(contextData) {
				public Object execute(Session session) throws DAOException {
					Query countQuery = session
							.getNamedQuery(FIND_NOT_COMPLETED_PERSON_MERGE_BY_PRIMARY_PERSON_ENTITY_KEYS_COUNT);
					Query dataQuery = session
							.getNamedQuery(FIND_NOT_COMPLETED_PERSON_MERGE_BY_PRIMARY_PERSON_ENTITY_KEYS);
					List personEntityKeys = (List) this.getContextData().get(
							PRIMARY_PERSON_ENTITY_KEYS_CONTEXT_KEY);
					Iterator personEntityKeysIterator = personEntityKeys
							.iterator();
					List personIds = new ArrayList();
					while (personEntityKeysIterator.hasNext()) {
						PersonEntityKey personEntityKey = (PersonEntityKey) personEntityKeysIterator
								.next();
						personIds.add(new BigDecimal(personEntityKey
								.getKeyValueAsString()));
					}
					countQuery.setParameterList(PARAM_PERSON_IDS, personIds);
					dataQuery.setParameterList(PARAM_PERSON_IDS, personIds);
					SearchQueryInfo searchQueryInfo = (SearchQueryInfo) this
							.getContextData()
							.get(SEARCH_QUERY_INFO_CONTEXT_KEY);
					PaginatedQueryExecutor queryExecutor = new PaginatedQueryExecutor(
							countQuery, dataQuery, searchQueryInfo);
					try {
						return queryExecutor.executeQuery();
					} catch (MaxRecordsExceededException e) {
						throw new DAOException("max records exceeded", e);
					}
				}
			};
			List results = null;
			try {
				results = this.getHibernateTemplate().executeFind(callback);
			} catch (HibernateSystemException e) {
				MaxRecordsExceededException rootCause = (MaxRecordsExceededException) getRootExceptionOfType(
						e, MaxRecordsExceededException.class);
				if (rootCause != null)
					throw rootCause;
				throw e;
			}

			if (logger.isInfoEnabled()) {
				logger
						.info("Total time to perform findNotCompletedByPrimaryPersonIds(): "
								+ (new Date().getTime() - startDate.getTime())
								+ " ms.");
			}

			return results;
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find non-completed PersonMergeInfo objects.", ex);
		}
	}

	/**
	 * @see gov.va.med.esr.common.persistent.person.PersonMergeDAO#findByPersonIds(gov.va.med.esr.common.model.person.id.PersonIdEntityKey,
	 *      gov.va.med.esr.common.model.person.id.PersonIdEntityKey)
	 */
	public PersonMergeInfo findByPersonIds(PersonIdEntityKey personId1,
			PersonIdEntityKey personId2) throws DAOException {
		// TODO: the query needs to go both directions (primary=psn1
		// deprecated=psn2; then primary=psn2 deprecated=psn1)

		Validate.notNull(personId1, "Person Id 1 can not be null.");
		Validate.notNull(personId2, "Person Id 2 can not be null.");

		String[] paramNames = new String[] { PARAM_PERSON_ID1, PARAM_PERSON_ID2 };
		Object[] values = new Object[] {
				new BigDecimal(personId1.getKeyValueAsString()),
				new BigDecimal(personId2.getKeyValueAsString()) };

		try {
			List results = findByNamedQueryAndNamedParam(
					FIND_PERSON_MERGE_BY_PERSON_IDS, paramNames, values);
			return ((results == null) || results.isEmpty()) ? null
					: (PersonMergeInfo) results.get(0);
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find PersonMergeInfo by personId.", ex);
		}
	}
	
	/**
	 * @see gov.va.med.esr.common.persistent.person.PersonMergeDAO#findByPersonIds(gov.va.med.esr.common.model.person.id.PersonIdEntityKey,
	 *      gov.va.med.esr.common.model.person.id.PersonIdEntityKey)
	 */
//	public PersonMergeInfo findByPrimaryDeprecatedIds(PersonIdEntityKey primaryPersonId,
//			PersonIdEntityKey deprecatedPersonId) throws DAOException {
	public List findByPrimaryDeprecatedIds(PersonIdEntityKey primaryPersonId,
	PersonIdEntityKey deprecatedPersonId) throws DAOException {

		Validate.notNull(primaryPersonId, "Primary Person Id can not be null.");
		Validate.notNull(deprecatedPersonId, "Deprecated Person Id can not be null.");

		String[] paramNames = new String[] { PARAM_PERSON_ID1, PARAM_PERSON_ID2 };
		Object[] values = new Object[] {
				new BigDecimal(primaryPersonId.getKeyValueAsString()),
				new BigDecimal(deprecatedPersonId.getKeyValueAsString()) };

		try {
			List results = findByNamedQueryAndNamedParam(
					FIND_PERSON_MERGE_BY_PRIMARY_DEPRECATED_PERSON_IDS, paramNames, values);
			return ((results == null) || results.isEmpty()) ? null
//					: (PersonMergeInfo) results.get(0);
					: results;
		} catch (DataAccessException ex) {
			throw new DAOException(
					"Failed to find PersonMergeInfo by primary and deprecated personIds.", ex);
		}
	}
	
}
