/********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.esr.common.persistent.lookup.hibernate;

// Java classes

// Library classes
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;

// Framework classes
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.hibernate.AbstractDAOAction;
import gov.va.med.fw.model.lookup.AbstractLookup;
import gov.va.med.fw.model.lookup.AbstractNamedLookup;

// ESR classes
import gov.va.med.esr.common.persistent.lookup.ZipCodeDAO;
import gov.va.med.esr.common.model.lookup.ZipCode;
import gov.va.med.esr.common.model.lookup.State;
import gov.va.med.esr.common.model.lookup.County;
import gov.va.med.esr.common.model.lookup.City;

import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.math.BigDecimal;
import java.lang.reflect.Method;

/**
 * This class is the DAO implementation for Zip Codes.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public class ZipCodeDAOImpl extends LookupsDAOImpl implements ZipCodeDAO
{
    private static final long serialVersionUID = 57308331910383004L;
    
    private String queryNameGetPartialByCode = null;
    private String queryNameGetZipCodeByCityState = null;
    private String queryNameGetZipCodeByCity = null;

    /**
     * Default constructor.
     */
    public ZipCodeDAOImpl()
    {
        super();
    }

    /**
     * Gets a zip code object that has only the following attributes filled in:
     *
     * ZipCode.id,
     * ZipCode.code,
     * ZipCode.state.name,
     * ZipCode.cities.id (a counter, rather than the city Id itself),
     * ZipCode.cities.name (abbreviated name when present or the full name when not),
     * ZipCode.county.name
     *
     * @param code The zip code to get
     * @return The zip code object
     * @throws DAOException if there were any problems.
     */
    public ZipCode getPartialZipCodeByCode(String code) throws DAOException
    {
        try
        {
    		Map contextData = new HashMap();
    		contextData.put("code", code);				        		        	
    		HibernateCallback callback = new AbstractDAOAction(contextData) { 
    			public Object execute(Session session) {
    	            // Get the named query
    	            Query query = session.getNamedQuery(queryNameGetPartialByCode);

    	            // Set the filter on zip code
    	            query.setString("code", (String) getContextData().get("code"));

    	            // Use caching if enabled
    	            if (isCacheQuery())
    	            {
    	                query.setCacheable(true);
    	                query.setCacheRegion(getCacheRegion());
    	            }

    	            // Get the results
    	            return query.list();    				
    			}
    		};
    		List results = this.getHibernateTemplate().executeFind(callback);
            if (results.size() == 0)
            {
                return null;
            }

            // Create the zip code object
            ZipCode zipCode = new ZipCode();
            for (int index = 0; index < results.size(); index++)
            {
                Object[] data = (Object[])results.get(index);

                // Set all data but cities only for the first result set element since they should all be the same
                // across the returned rows
                if (index == 0)
                {
                    // Set the zip code Id
                    setZipCodeId(zipCode, (BigDecimal)data[0]);

                    // Set the zip code itself
                    setZipCode(zipCode, (String)data[1] == null ? "" : (String)data[1]);

                    // Set the state name
                    State state = new State();
                    zipCode.setState(state);
                    state.setName((String)data[2] == null ? "" : (String)data[2]);

                    // Set the county name
                    County county = new County();
                    zipCode.setCounty(county);
                    county.setName((String)data[3] == null ? "" : (String)data[3]);
                }

                // Add a new city record
                City city = new City();
                setCityId(city, new BigDecimal(index));
                setCityName(city, (String)data[4] == null ? "" : (String)data[4]);
                zipCode.addCity(city);
            }

            // Return the partially built zip code
            return zipCode;
        }
        catch(HibernateException e)
        {
            throw new DAOException("Failed to get the list of zip codes by code.", e);
        }
    }

    /**
     * Gets a list of zip code objects that has only the following attributes filled in:
     * <p>
     * ZipCode.id
     * ZipCode.code
     * <p>
     * If no records are found, an empty list is returned.
     *
     * @param cityName The name of the city to query on
     * @param stateCode The state code to query on
     *
     * @return The zip code object
     * @throws DAOException if there were any problems.
     */
    public List getPartialZipCodeByCityState(String cityName, String stateCode) throws DAOException
    {
        ArrayList returnList = new ArrayList();
        try
        {
    		Map contextData = new HashMap();
    		contextData.put("cityName", cityName);				        		        	
    		contextData.put("stateCode", stateCode);
    		HibernateCallback callback = new AbstractDAOAction(contextData) { 
    			public Object execute(Session session) {
    	            // Get the named query
    	            Query query = session.getNamedQuery(queryNameGetZipCodeByCityState);

    	            // Set the filter criteria
    	            query.setString("state", (String) getContextData().get("stateCode"));
    	            query.setString("city", (String) getContextData().get("cityName"));

    	            // Use caching if enabled
    	            if (isCacheQuery())
    	            {
    	                query.setCacheable(true);
    	                query.setCacheRegion(getCacheRegion());
    	            }

    	            // Get the results of the query
    	            return query.list();    				
    			}
    		};
    		List results = this.getHibernateTemplate().executeFind(callback);

            // Iterate through the result set
            for (int index = 0; index < results.size(); index++)
            {
                // Create the zip code object
                ZipCode zipCode = new ZipCode();

                Object[] data = (Object[])results.get(index);

                // Set the zip code Id
                setZipCodeId(zipCode, (BigDecimal)data[0]);

                // Set the zip code itself
                setZipCode(zipCode, (String)data[1] == null ? "" : (String)data[1]);

                // Add the zip code to the return list
                returnList.add(zipCode);
            }

            // Return the list of partially built zip codes
            return returnList;
        }
        catch(HibernateException e)
        {
            throw new DAOException("Failed to get the list of zip codes by city name and state name.", e);
        }
    }

    /**
     * Gets a list of zip code objects that has only the following attributes filled in:
     * <p>
     * ZipCode.id
     * ZipCode.code
     * <p>
     * If no records are found, an empty list is returned.
     *
     * @param cityName The name of the city to query on
     *
     * @return The zip code object
     * @throws DAOException if there were any problems.
     */
    public List getPartialZipCodeByCity(String cityName) throws DAOException
    {
        ArrayList returnList = new ArrayList();
        try
        {
    		Map contextData = new HashMap();
    		contextData.put("cityName", cityName);				        		        	
    		HibernateCallback callback = new AbstractDAOAction(contextData) { 
    			public Object execute(Session session) {
    	            // Get the named query
    	            Query query = session.getNamedQuery(queryNameGetZipCodeByCity);

    	            // Set the filter criteria
    	            query.setString("city", (String) getContextData().get("cityName"));

    	            // Use caching if enabled
    	            if (isCacheQuery())
    	            {
    	                query.setCacheable(true);
    	                query.setCacheRegion(getCacheRegion());
    	            }

    	            // Get the results of the query
    	            return query.list();    				
    			}
    		};
    		List results = this.getHibernateTemplate().executeFind(callback);

            // Iterate through the result set
            for (int index = 0; index < results.size(); index++)
            {
                // Create the zip code object
                ZipCode zipCode = new ZipCode();

                Object[] data = (Object[])results.get(index);

                // Set the zip code Id
                setZipCodeId(zipCode, (BigDecimal)data[0]);

                // Set the zip code itself
                setZipCode(zipCode, (String)data[1] == null ? "" : (String)data[1]);

                // Add the zip code to the return list
                returnList.add(zipCode);
            }

            // Return the list of partially built zip codes
            return returnList;
        }
        catch(HibernateException e)
        {
            throw new DAOException("Failed to get the list of zip codes by city name.", e);
        }
    }

    public String getQueryNameGetPartialByCode()
    {
        return queryNameGetPartialByCode;
    }

    public void setQueryNameGetPartialByCode(String queryNameGetPartialByCode)
    {
        this.queryNameGetPartialByCode = queryNameGetPartialByCode;
    }

    public String getQueryNameGetZipCodeByCityState()
    {
        return queryNameGetZipCodeByCityState;
    }

    public void setQueryNameGetZipCodeByCityState(String queryNameGetZipCodeByCityState)
    {
        this.queryNameGetZipCodeByCityState = queryNameGetZipCodeByCityState;
    }

    
	/**
	 * @return Returns the queryNameGetZipCodeByCity.
	 */
	public String getQueryNameGetZipCodeByCity() {
		return queryNameGetZipCodeByCity;
	}

	
	/**
	 * @param queryNameGetZipCodeByCity The queryNameGetZipCodeByCity to set.
	 */
	public void setQueryNameGetZipCodeByCity(String queryNameGetZipCodeByCity) {
		this.queryNameGetZipCodeByCity = queryNameGetZipCodeByCity;
	}

	private void setZipCodeId(ZipCode zipCode, BigDecimal id)
    {
        try
        {
            Class clazz = Class.forName(AbstractLookup.class.getName());
            Method method = clazz.getDeclaredMethod("setIdentifier", new Class[] {BigDecimal.class});
            method.setAccessible(true);
            method.invoke(zipCode, new Object[] {id});
        }
        catch (Exception e)
        {
            throw new RuntimeException("Unable to set zip code identifier.", e);
        }
    }

    private void setZipCode(ZipCode zipCode, String code)
    {
        try
        {
            zipCode.setZipCode(code);
            Class clazz = Class.forName(AbstractLookup.class.getName());
            Method method = clazz.getDeclaredMethod("setCode", new Class[] {String.class});
            method.setAccessible(true);
            method.invoke(zipCode, new Object[] {code});
        }
        catch (Exception e)
        {
            throw new RuntimeException("Unable to set zipcode code.", e);
        }
    }

    private void setCityId(City city, BigDecimal id)
    {
        try
        {
            Class clazz = Class.forName(AbstractLookup.class.getName());
            Method method = clazz.getDeclaredMethod("setIdentifier", new Class[] {BigDecimal.class});
            method.setAccessible(true);
            method.invoke(city, new Object[] {id});
        }
        catch (Exception e)
        {
            throw new RuntimeException("Unable to set city identifier.", e);
        }
    }

    private void setCityName(City city, String cityName)
    {
        try
        {
            Class clazz = Class.forName(AbstractNamedLookup.class.getName());
            Method method = clazz.getDeclaredMethod("setName", new Class[] {String.class});
            method.setAccessible(true);
            method.invoke(city, new Object[] {cityName});
        }
        catch (Exception e)
        {
            throw new RuntimeException("Unable to set city name.", e);
        }
    }
}