/**
 * 
 */


package gov.va.med.cds.persistence.hibernate;


import gov.va.med.cds.persistence.hibernate.common.AbstractPointInTimeUserType;
import gov.va.med.cds.persistence.hibernate.common.PointInTime;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.hibernate.Query;
import org.hibernate.TypeHelper;
import org.hibernate.internal.TypeLocatorImpl;
import org.hibernate.type.TypeResolver;


/**
 * @author Moroni Pickering
 *
 *  The <code>OptionalParameterQueryParameterBinder</code> class implements the convention-based named 
 *  query name building mechanism defined originally in CDS v2.0. This convention
 *  based approach append to the entry point name optionally the 'pids' or patient
 *  identifiers, the start and end dates, and the record identifiers. 
 */
public class OptionalParameterQueryParameterBinder
    implements
        QueryParameterBinderInterface
{

    private static final String OPTIONAL_PARAM_START = "_optional_";
    private static final String OPTIONAL_PARAM_START_NULL = OPTIONAL_PARAM_START + "null_";
    

    @Override
    public Query bindQueryParameters( Query query, Map<String, Object> queryParametersMap, Class<?> pointInTimeUserType )
    {
        List<String> queryBindVars = Arrays.asList(query.getNamedParameters());
        for(String bindVarName : queryBindVars )
        {
            
            if( containsKey(queryParametersMap, bindVarName ) )
            {
                String parameterMapKey = getParameterName( bindVarName );
                Object parameterValue = queryParametersMap.get( parameterMapKey );
                if ( parameterValue instanceof PointInTime || parameterValue instanceof gov.va.med.cds.template.generated.basedatatypes.PointInTime)
                {
                    Properties userTypeProperties = new Properties();
                    userTypeProperties.setProperty( AbstractPointInTimeUserType.SET_VALUES, AbstractPointInTimeUserType.SV_NUMERIC_ONLY );
                    TypeHelper typeHelper = new TypeLocatorImpl(new TypeResolver());
                    //query.setParameter( bindVarName, parameterValue, Hibernate.custom( pointInTimeUserType, userTypeProperties ) );
                    query.setParameter( bindVarName, parameterValue, typeHelper.custom( pointInTimeUserType, userTypeProperties ) );
                }
                else if ( parameterValue instanceof Map<?, ?>) //added support for complex type optional parameters
                {   
                    @SuppressWarnings("unchecked")
					Map<String, Object> complexParam = ( Map<String, Object> )parameterValue;
                    bindQueryParameters( query, complexParam, pointInTimeUserType );
                }
                else if ( parameterValue instanceof Collection )
                {
                    query.setParameterList( bindVarName, ( Collection<?> )parameterValue );
                }
                else
                {
                    query.setParameter( bindVarName, parameterValue );
                }
            }
            else if(bindVarName.startsWith( OPTIONAL_PARAM_START_NULL ) && optionalParameterHasValue(bindVarName, queryParametersMap))
            {
                query.setParameter( bindVarName, 1 ); //set to any non-null
            }
            else
            {
                query.setParameter( bindVarName, null, org.hibernate.type.StandardBasicTypes.STRING );
            }
        }
        
        return query;
    }    
    
    private boolean optionalParameterHasValue( String bindVarName, Map<String, Object> queryParametersMap )
    {
        String parameterName = bindVarName.replaceFirst(OPTIONAL_PARAM_START_NULL, "");
        return queryParametersMap.get( parameterName ) != null;
    }


    private boolean containsKey( Map<String, Object> queryParametersMap, String bindVarName )
    {
        String parameterName = getParameterName(bindVarName); 
        return queryParametersMap.containsKey( parameterName );
    }


    private String getParameterName( String bindVarName )
    {
        return bindVarName.replaceFirst( OPTIONAL_PARAM_START, ""); 
    }

}
