/**
 *
 */


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


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.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.ParameterizedType;


public class Dom4jDateUserType
    implements
        EnhancedUserType,
        ParameterizedType
{
    private Properties params = null;


    @Override
    public Object assemble( Serializable cached, Object owner )
        throws HibernateException
    {
        return deepCopy( cached );
    }


    @Override
    public Object deepCopy( Object value )
        throws HibernateException
    {
        Object ret = null;
        if ( value != null && value instanceof Element )
        {
            ret = ( ( Element )value ).clone();
        }

        return ret;
    }


    @Override
    public Serializable disassemble( Object value )
        throws HibernateException
    {
        return ( Serializable )deepCopy( value );
    }


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

        String literalX = ( ( Element )x ).getText();
        String literalY = ( ( Element )y ).getText();

        if ( literalX == null || literalY == null )
            return false;

        return literalX.equals( literalY );
    }


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

        Element literal = ( Element )x;
        if ( literal.getText() == null || "".equals( literal.getText() ) )
            return 0;

        return literal.hashCode();
    }


    @Override
    public Object nullSafeGet( ResultSet rs, String[] names, SessionImplementor sessionImplementor, Object owner )
        throws HibernateException,
            SQLException
    {
        Timestamp timestamp = rs.getTimestamp( names[0] );
        String nodeName = params.getProperty("nodeName", "literal");
        Element element = DocumentHelper.createElement( nodeName );

        if ( ! rs.wasNull() )
        {
            String dateFormatString = params.getProperty( "dateFormat" );
            DateFormat dateFormat = new SimpleDateFormat( dateFormatString );
            element.addText( dateFormat.format( timestamp ) );
            return element.detach();
        }
        else
            return null;
    }


    @Override
    public void nullSafeSet( PreparedStatement st, Object value, int index, SessionImplementor sessionImplementor )
        throws HibernateException,
            SQLException
    {
        if ( value == null )
        {
        	st.setNull( index, StandardBasicTypes.TIMESTAMP.sqlType() );
        }
        else
        {
            if ( value instanceof Element )
            {
                Element literal = ( Element )value;
                String dateValue = literal.getText();
                
                String dateFormatString = params.getProperty( "dateFormat" );
                try
                {
                    if ( dateFormatString.endsWith( "ZZ" ) )
                    {
                    	dateValue = cleanupTimezoneOffset( 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'", 
                                    literal.getName(), dateFormatString), e );
                }
            }
            else
            {
                throw new HibernateException( String.format( "The value parameter must be of type %s. The actual type was %s",
                                Element.class.getName(), value.getClass().getName() ) );
            }
        }
    }

    
    private String cleanupTimezoneOffset( String dateString )
    {
        // expects that date string ends with (+/-)NN:NN
        int idxTzColon = dateString.lastIndexOf( ':' );
        if ( idxTzColon != dateString.length() - 3 )
        {
            throw new IllegalArgumentException( String.format( "Invalid date string. Date string must end in (+/-)NN:NN" ) );
        }
        dateString = dateString.substring( 0, idxTzColon ) + dateString.substring( idxTzColon + 1 );
        return dateString;

    }

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


    public int[] sqlTypes( )
    {
        return new int[] { StandardBasicTypes.TIMESTAMP.sqlType() };
    }


    @Override
    public boolean isMutable( )
    {
        return true;
    }


    @Override
    public Object replace( Object original, Object target, Object owner )
        throws HibernateException
    {
        if ( original instanceof Element && target instanceof Element && owner instanceof Element )
        {
            Element originalElement = ( Element )original;
            Element targetElement = ( Element )target;

            targetElement.setText( originalElement.getText() );
        }
        return target;
    }


    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hibernate.usertype.EnhancedUserType#objectToSQLString(java.lang.Object
     * )
     */
    @Override
    public String objectToSQLString( Object value )
    {
        if ( value instanceof Node )
        {
            Node nodeVal = ( Node )value;
            return nodeVal.getText();
        }
        else
        {
            return value.toString();
        }
    }


    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hibernate.usertype.EnhancedUserType#toXMLString(java.lang.Object)
     */
    @Override
    public String toXMLString( Object value )
    {
        return objectToSQLString( value );
    }


    /*
     * (non-Javadoc)
     * 
     * @see
     * org.hibernate.usertype.EnhancedUserType#fromXMLString(java.lang.String)
     */
    @Override
    public Object fromXMLString( String xmlValue )
    {
    	String nodeName = this.params.getProperty("nodeName", "literal");
        Element literalElement = DocumentHelper.createElement( nodeName );
        literalElement.setText( xmlValue );
        return literalElement;
    }


    @Override
    public void setParameterValues( Properties parameters )
    {
        this.params = parameters;

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