/**
 *
 */


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


import org.dom4j.Document;
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.type.Type;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


public class Dom4jPointInTimeUserType
    extends
        AbstractPointInTimeUserType
{

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


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

        return ret;
    }


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


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


    public Type[] getPropertyTypes( )
    {
        if ( this.setValues != null )
        {
            if ( isSetLiteral() )
            {
                return new Type[] { StandardBasicTypes.STRING };
            }
            else if ( isSetNumeric() )
            {
                return new Type[] { StandardBasicTypes.DOUBLE };
            }
        }

        return new Type[] { StandardBasicTypes.STRING, StandardBasicTypes.DOUBLE };
    }


    public Object getPropertyValue( Object value, int property )
        throws HibernateException
    {
        if ( value instanceof Element )
        {
            return ( ( Element )value ).getText();
        }

        return null;
    }


    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 );
    }


    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();
    }


    public Object nullSafeGet( ResultSet rs, String[] names, SessionImplementor sessionImplementor, Object owner )
        throws HibernateException,
            SQLException
    {
        String timestamp = rs.getString( names[0] );
        Document document = DocumentHelper.createDocument();
        Element literal = document.addElement( "literal" );

        if ( !rs.wasNull() )
        {
            literal.addText( timestamp );
            return literal.detach();
        }
        else
            return null;
    }


    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 ( isSetNumeric() )
            {
                st.setNull( index, StandardBasicTypes.DOUBLE.sqlType() );
            }
        }
        else
        {
            if ( value instanceof Element )
            {
                Element literal = ( Element )value;
                if ( isSetLiteral() )
                {
                    st.setString( index, literal.getText() );
                    index++ ;
                }

                if ( isSetNumeric() )
                {
                    Double timestamp = isoTime2Double( literal.getText() );
                    if ( timestamp != null && timestamp.doubleValue() != 0.0 )
                    {
                        st.setDouble( index, timestamp );
                    }
                    else
                    {
                        st.setNull( index, StandardBasicTypes.DOUBLE.sqlType() );
                    }
                }
            }
            else if ( value instanceof PointInTime )
            {
                PointInTime pointInTime = ( PointInTime )value;
                if ( isSetLiteral() )
                {
                    st.setString( index, pointInTime.getTimestamp() ); // set existing date string
                    index++ ;
                }

                if ( isSetNumeric() )
                {
                    Double timestamp = isoTime2Double( pointInTime.getTimestamp() );
                    if ( timestamp != null && timestamp.doubleValue() != 0.0 )
                    {
                        st.setDouble( index, timestamp );
                    }
                    else
                    {
                        st.setNull( index, StandardBasicTypes.DOUBLE.sqlType() );
                    }
                }
            }
            else
            {
                throw new HibernateException( "The value parameter must of type " + Element.class.getName() + " or of type "
                                + PointInTime.class.getName() + ". The actual type was " + value.getClass().getName() );
            }
        }
    }


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


    public int[] sqlTypes( )
    {
        return new int[] { StandardBasicTypes.STRING.sqlType(), StandardBasicTypes.DOUBLE.sqlType() };
    }


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


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