

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

import gov.va.med.cds.template.generated.basedatatypes.PointInTimeWithPattern;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Properties;
import java.util.TimeZone;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.DoubleType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;


public class PointInTimeWithPatternUserType
    implements
        CompositeUserType,
        ParameterizedType
{
    public static final String SET_VALUES = "setValues";

    public static final String SV_ALL = "all";
    public static final String SV_LITERAL_ONLY = "literalOnly";
    public static final String SV_TIMESTAMP_ONLY = "timestampOnly";

    private String setValues;


    public void setParameterValues( Properties properties )
    {
        if ( properties != null )
        {
            setValues = properties.getProperty( SET_VALUES );
        }
    }


    public Object assemble( Serializable cached, SessionImplementor sessionImplementor, Object owner )
        throws HibernateException
    {
        return cached;
    }


    public Object deepCopy( Object value )
        throws HibernateException
    {
        return value;
    }


    public Serializable disassemble( Object value, SessionImplementor sessionImplementor )
        throws HibernateException
    {
        return ( Serializable )value;
    }


    public String[] getPropertyNames( )
    {
        return new String[] { "literal", "timestamp" };
    }


    public Type[] getPropertyTypes( )
    {
        if ( this.setValues != null )
        {
            if ( isSetLiteral() )
            {
                return new Type[] { StringType.INSTANCE };
            }
            else if ( isSetTimestamp() )
            {
                return new Type[] {TimestampType.INSTANCE };
            }
        }

        return new Type[] { StringType.INSTANCE, DoubleType.INSTANCE };
    }


    public Object getPropertyValue( Object value, int property )
        throws HibernateException
    {
        if ( value instanceof gov.va.med.cds.template.generated.basedatatypes.PointInTimeWithPattern)
        {
        	PointInTimeWithPattern pointInTime = ( PointInTimeWithPattern )value;
            return ( property == 1 ) ? pointInTime.getLiteral() : pointInTime.getLiteral();
        }

        return null;
    }


    public void setPropertyValue( Object component, int property, Object value )
        throws HibernateException
    {
        throw new UnsupportedOperationException( "Immutable class PointInTime." );
    }


    public boolean equals( Object x, Object y )
        throws HibernateException
    {
        if ( x == null || y == null )
            return true;
        if ( !( x instanceof PointInTimeWithPattern ) || !( y instanceof PointInTimeWithPattern ) )
            return false;
        if ( x == y )
            return true;

        PointInTimeWithPattern pointInTimeX = ( PointInTimeWithPattern )x;
        PointInTimeWithPattern pointInTimeY = ( PointInTimeWithPattern )y;

        //if ( pointInTimeX.getTimestamp() == null || pointInTimeY.getTimestamp() == null )
        if ( pointInTimeX.getLiteral() == null || pointInTimeY.getLiteral()== null )
            return false;

        //return pointInTimeX.getTimestamp().equals( pointInTimeY.getTimestamp() );
        return pointInTimeX.getLiteral().equals( pointInTimeY.getLiteral() );
    }


    public int hashCode( Object x )
        throws HibernateException
    {
        if ( x == null || !( x instanceof PointInTimeWithPattern ) )
            return 0;

        PointInTimeWithPattern pointInTimeX = ( PointInTimeWithPattern )x;
        
        /* if ( pointInTimeX.getTimestamp() == null )
        return 0;

    	return pointInTimeX.getTimestamp().hashCode();*/
       
        if ( pointInTimeX.getLiteral() == null )
            return 0;

        return pointInTimeX.getLiteral().hashCode();
       
    }


    public boolean isMutable( )
    {
        return true;
    }


    public Object nullSafeGet( ResultSet rs, String[] names, SessionImplementor sessionImplementor, Object owner )
        throws HibernateException,
            SQLException
    {
    	PointInTimeWithPattern pointInTime = null;

        String timestamp = rs.getString( names[0] );
        if ( !rs.wasNull() )
        {
            pointInTime = new PointInTimeWithPattern();
            pointInTime.setLiteral( timestamp );
            //pointInTime.setTimestamp( timestamp );
        }

        return pointInTime;
    }


    public void nullSafeSet( PreparedStatement st, Object value, int index, SessionImplementor sessionImplementor )
        throws HibernateException,
            SQLException
    {

        if ( value == null )
        {
            if ( isSetLiteral() )
            {
            	st.setNull( index , StandardBasicTypes.STRING.sqlType() );
                index++ ;
            }

            if ( isSetTimestamp() )
            {
            	st.setNull( index, StandardBasicTypes.TIMESTAMP.sqlType() );
            }
            
        }
        else
        {
            if ( value instanceof PointInTimeWithPattern )
            {
            	PointInTimeWithPattern pointInTime = ( PointInTimeWithPattern )value;
            	String dateValue = pointInTime.getLiteral();
            	String dateFormatString = "yyyyMMddHHmmssZ";
                try{    
                	st.setString( index++ , dateValue );
                	DateFormat dateFormat = new SimpleDateFormat( dateFormatString );
                	dateFormat.setLenient( false );
                	Long time = dateFormat.parse( dateValue ).getTime();
                	st.setTimestamp( index, new Timestamp( time ), Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
                }
                catch ( ParseException e )
                {
                    throw new HibernateException( String.format("Error parsing date literal at date element '%s' using date format '%s'", 
                    		dateValue, dateFormatString), e );
                }
            }
            else
            {
                throw new HibernateException( "The value parameter must be of type " + PointInTimeWithPattern.class.getName() + ". The actual type was "
                                + value.getClass().getName() );
            }
        }
    }
    
   
    public Object replace( Object original, Object target, SessionImplementor sessionImplementor, Object owner )
        throws HibernateException
    {
        return original; // not quite sure what to do with this.
    }


    public Class<PointInTime> returnedClass( )
    {
        return PointInTime.class;
    }


    public int[] sqlTypes( )
    {
        return new int[] { StringType.INSTANCE.sqlType(), DoubleType.INSTANCE.sqlType() };
    }


   


    /**
     * This return ensures the user string is conformant to at least the following rules: - All characters must be
     * numeric for the iso conversion
     *
     * @param isoDate
     * @return boolean
     */
    protected boolean isNonConformatString(String isoDate) 
    {
		boolean returnValue = false;

		if ( isoDate.length() < 4 ) 
		{
			returnValue = true;
		} 
		else 
		{
			for (int index = 0; index < isoDate.length(); index++) 
			{
				char isoDateCharacter = isoDate.charAt( index );
				if ( ! ( ( isoDateCharacter <= '9' ) && ( isoDateCharacter >= '0' ) ) ) 
				{
					returnValue = true;
					break;
				}
			}
		}

		return returnValue;
	}


    private boolean isSetLiteral( )
    {
        return ( this.setValues == null ) || this.setValues.equals( SV_ALL ) || this.setValues.equals( SV_LITERAL_ONLY );
    }


    private boolean isSetTimestamp( )
    {
        return ( this.setValues == null ) || this.setValues.equals( SV_ALL ) || this.setValues.equals( SV_TIMESTAMP_ONLY );
    }


    public static void main( String[] args )
    {
        
    }
}

