/**
 * 
 */


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


import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;

import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.Scanner;
import java.util.TimeZone;


/**
 * @author vhaislegberb
 *
 */
public abstract class AbstractPointInTimeUserType
    implements
        ParameterizedType,
        CompositeUserType
{
    private static String DATE_FORMAT = "yyyyMMddHHmmss";
    private static String DATE_FORMAT_NO_TIME = "yyyyMMdd";
    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_NUMERIC_ONLY = "numericOnly";
    public static final String STATION_ID = "stationId";
    protected String setValues;


    /* (non-Javadoc)
     * @see org.hibernate.usertype.CompositeUserType#isMutable()
     */
    @Override
    public boolean isMutable( )
    {
        return false;
    }


    /* (non-Javadoc)
     * @see org.hibernate.usertype.CompositeUserType#replace(java.lang.Object, java.lang.Object, org.hibernate.engine.SessionImplementor, java.lang.Object)
     */
    @Override
    public Object replace( Object original, Object target, SessionImplementor session, Object owner )
        throws HibernateException
    {
        return original;
    }


    /* (non-Javadoc)
     * @see org.hibernate.usertype.CompositeUserType#setPropertyValue(java.lang.Object, int, java.lang.Object)
     */
    @Override
    public void setPropertyValue( Object component, int property, Object value )
        throws HibernateException
    {
        throw new UnsupportedOperationException( "Point In Time property is immutable." );

    }


    /* (non-Javadoc)
     * @see org.hibernate.usertype.ParameterizedType#setParameterValues(java.util.Properties)
     */
    @Override
    public void setParameterValues( Properties parameters )
    {
        if ( parameters != null )
        {
            setValues = parameters.getProperty( SET_VALUES );
        }

    }


    /**
     * builds the new double from date and time parts of the given string.
     * 
     * @param isoDate
     * @return builds the new double from date and time parts of the given string
     */
    protected Double isoTime2Double( String isoDate )
    {
        Double retVal;

        if ( ( isoDate != null ) && ( isoDate.length() >= 4 ) )
        {
            // trim off the timezone offset and the milliseconds
            isoDate = isoDate.split( "[-+]" )[0].split( "\\." )[0];

            // Filter out non-conformant string dates
            if ( isNonConformatString( isoDate ) )
            {
                return null;
            }

            // split the time up into date and time parts.
            int dateMaxIndex = ( isoDate.length() >= 8 ) ? 8 : isoDate.length();
            String datePart = isoDate.substring( 0, dateMaxIndex ) + "0000".substring( 0, 8 - dateMaxIndex );
            String timePart = ( isoDate.length() > dateMaxIndex ) ? isoDate.substring( dateMaxIndex ) : "0";

            // subtract 1700 from the year to match Cache date format
            datePart = ( Integer.parseInt( datePart.substring( 0, 4 ) ) - 1700 ) + datePart.substring( 4 );

            // build the new double from date and time parts
            retVal = new Double( datePart + "." + timePart );
        }
        else
        {
            retVal = new Double( 0.0 );
        }

        return retVal;
    }


    /**
     * builds the new double from date and time parts of the given string.
     * 
     * @param isoDate
     * @return builds the new double from date and time parts of the given string
     */
    protected Date isoTime2Date( String isoDate )
    {
        Date retVal = null;

        if ( isoDate != null )
        {
            // trim off the timezone offset and the milliseconds
            String[] dateTime = isoDate.split( "\\." );
            isoDate = dateTime[0];

            // Filter out non-conformant string dates
            if ( isNonConformatString( isoDate ) )
            {
                return null;
            }

            // split the time up into date and time parts.
            int dateMaxIndex = ( isoDate.length() >= 8 ) ? 8 : isoDate.length();
            String datePart = isoDate.substring( 0, dateMaxIndex ) + "0000".substring( 0, 8 - dateMaxIndex );
            String timePart = ( isoDate.length() > dateMaxIndex ) ? isoDate.substring( dateMaxIndex ) : "0";

            isoDate = datePart + timePart;

            SimpleDateFormat formatter = new SimpleDateFormat( "yyyyMMddHHmmss" );
            try
            {
                retVal = new java.sql.Date( ( formatter.parse( isoDate ) ).getTime() );
            }
            catch ( ParseException pe )
            {
                retVal = new Date( 0 );
            }

        }
        //      else
        //      {
        //        retVal = new Date( 0 );
        //      }

        return retVal;
    }


    /**
     * 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;
    }


    protected String formatHdrTime( String rawDate, String namespace )
    {
        if ( rawDate == null )
            return null;

        // trim off the nano-seconds
        String[] dateTime = rawDate.split( "\\." );
        String dtTime = dateTime[0];
        String retVal = dtTime.replaceAll( "[- :]", "" );

        // compute timezone offset
        retVal = getOffset( retVal, namespace );
        return retVal;
    }


    /**
     * builds the new date string from the historical date format to match HDR2.
     *  
     *  Historical Date Conversion: 
     *  
     *  Input                       Converted Output
     *  0                           - 0
     *  308                         - 20080000
     *  3081219                     - 20081219
     *  30812191219                 - 20081219
     *  3081219.170515              - 20081219170515
     *  30812191219.170515          - 20081219170515    
     *  
     *  Rules:
     *  Date not all numeric or Date length < 3 -- returns unconverted value
     *  Date length > 3 and date length < 7 -- adds 1700 to year and pads with zeros to make length 7
     *  Date length >= 7 -- adds 1700 to year and ignores characters beyond 7
     *  
     *  Time always attached to converted date (No offset as time is numeric)
     * 
     * @param vistaDate
     * @return builds the date string to match HDR2 
     */
    protected String formatVistaTime( String vistaDate, String namespace )
    {
        String retVal = null;

        if ( vistaDate != null && vistaDate.length() >= 3 )
        {
            // trim off the time
            //            String[] dateTime = vistaDate.split( "\\." );
            String[] dateTime = vistaDate.split( "[.^]+" );
            vistaDate = dateTime[0];

            // Filter out non-conformant string dates
            if ( isNonConformatString( vistaDate ) )
            {
                return vistaDate;
            }

            // split the time up into date and time parts.
            int dateMaxIndex = ( vistaDate.length() >= 7 ) ? 7 : vistaDate.length();
            String datePart = vistaDate.substring( 0, dateMaxIndex ) + "0000".substring( 0, 7 - dateMaxIndex );

            // add 1700 from the year to match Cache date format
            datePart = ( Integer.parseInt( datePart.substring( 0, 3 ) ) + 1700 ) + datePart.substring( 3 );
            retVal = datePart;

            int timeLength = dateTime.length > 2 ? dateTime[1].length() : 0;
            String timePart = ( timeLength > 0 ) ? dateTime[1].concat( "000000".substring( 0, 6 - timeLength ) ) : "";

            // build the new return string from date and time parts
            retVal = datePart + timePart;

            // add timezone offset
            retVal = getOffset( retVal, namespace );
        }
        else
        {
            retVal = vistaDate;
        }

        return retVal;
    }


    protected String getOffset( String timestamp, String facilityId )
    {
        String timezone = VistaTimeZoneOffset.getTimeZone( facilityId );

        if ( timezone == null )
            return timestamp;

        String time = null;
        try
        {
            String format = DATE_FORMAT;
            if ( timestamp.length() == 8 )
            {
                format = DATE_FORMAT_NO_TIME;
            }
            SimpleDateFormat sdf = new SimpleDateFormat( format );
            java.util.Date remoteDate = sdf.parse( timestamp );
            TimeZone tz = TimeZone.getTimeZone( timezone );
            int offset = tz.getOffset( remoteDate.getTime() );
            time = String.format( "%s%s%02d%02d", timestamp, offset >= 0 ? "+" : "-", Math.abs( offset ) / 3600000, ( offset / 60000 ) % 60 );
        }
        catch ( ParseException pe )
        {
            time = timestamp;

        }

        return time;
    }


    protected boolean isNumeric( String value )
    {
        if ( value == null || value.length() == 0 )
        {
            return false;
        }

        try
        {
            Float.parseFloat( value );
        }
        catch ( Exception e )
        {
            return false;
        }

        return value.matches( "-?\\d+(.\\d+)?" );
    }
}
