

package gov.va.med.cds.socket.server.handler;


import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exception.MllpIllegalEncodingException;
import gov.va.med.cds.hapi.HL7SupportException;
import gov.va.med.cds.hapi.HL7SupportInterface;
import gov.va.med.cds.hapi.HL7Utility;
import gov.va.med.cds.hapi.Hl7SupportHdrLegacy;
import gov.va.med.cds.logger.ApplicationLoggerInterface;
import gov.va.med.cds.logger.LogApplicationLogger;
import gov.va.med.cds.util.MllpUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Scanner;


public class MinaMllpHandler
    extends
        IoHandlerAdapter
{

    private static Log LOGGER = LogFactory.getLog( MinaMllpHandler.class );

    public static final String INDEX_KEY = MinaMllpHandler.class.getName() + ".INDEX";

    public static final String ALTONA_SITE = "503";
    public static final String LAB_MSG = "LA7LAB";

    /** A handle to the access validator that will validate the access parameters in the request. */
    //private AccessValidatorInterface accessValidator = new NoOpAccessValidator();
    AccessValidatorInterface accessValidator = null;
    /** Responsible for dispatching messages to the appropriate message mediator instance */
    //private MessageMediatorDispatcherInterface messageMediatorDispatcher = null;
    MessageMediatorDispatcherInterface messageMediatorDispatcher = null;

    /** Responsible for generating client specific response messages */
    //private ResponseGeneratorInterface responseGenerator = null;
    ResponseGeneratorInterface responseGenerator = null;

    MessageUtil messageUtil = null;
    boolean processLabMessage;


    public boolean isProcessLabMessage( )
    {
        return processLabMessage;
    }


    public void setProcessLabMessage( boolean processLabMessage )
    {
        this.processLabMessage = processLabMessage;
    }


    public void setMessageUtil( MessageUtil messageUtil )
    {
        this.messageUtil = messageUtil;
    }

    public ApplicationLoggerInterface appLogger = new LogApplicationLogger( LOGGER );


    public void setResponseGenerator( ResponseGeneratorInterface responseGenerator )
    {
        this.responseGenerator = responseGenerator;
    }


    public void setMessageMediatorDispatcher( MessageMediatorDispatcher messageMediatorDispatcher )
    {
        this.messageMediatorDispatcher = messageMediatorDispatcher;
    }


    public void setAccessValidator( AccessValidatorInterface accessValidator )
    {
        this.accessValidator = accessValidator;
    }


    @Override
    public void exceptionCaught( IoSession session, Throwable cause )
        throws Exception
    {
        appLogger.logMessage( "Fatal Error occured processing message.", new Exception( cause.getMessage() ) );
    }


    @Override
    public void sessionCreated( IoSession session )
        throws Exception
    {
        session.setAttribute( INDEX_KEY, 0 );

    }


    /**
     * Reads hl7 message off the wire, processes it and sends response back to a client
     * @param connection
     * @return 
     */
    @Override
    public void messageReceived( IoSession session, Object message )
        throws Exception
    {

        String mediatorResponse = null;
        Exception exception = null;
        HL7SupportInterface hl7Support = null;
        ByteBuffer decodedBytes = null;
        String responseMessageControlId = null;
        try
        {
            ByteBuffer buffer = ByteBuffer.allocate( ( ( ( IoBuffer )message ).limit() ) );

            int index = 0;

            do
            {
                buffer.put( index, ( ( IoBuffer )message ).get( index ) );
                index++ ;
            }
            while ( index < ( ( IoBuffer )message ).limit() );

            if ( LOGGER.isDebugEnabled() )
            {
                LOGGER.debug( "Message Received : " + new String( buffer.array() ) );
            }

            decodedBytes = MllpUtil.decode( new ByteBuffer[] { buffer }, Charset.forName( "ISO-8859-1" ), Charset.defaultCharset() );

            if ( decodedBytes == null )
            {
                // If the decoded bytes are null, then that means that the data available to be read 
                // from the connection was not a complete message (eg. SOB+MESSAGE+EOB). By throwing the 
                // BufferUnderflowException, things will be reset and the onData method will be called
                // again when more data is available to be read.
                throw new BufferUnderflowException();
            }
            /** Use the connection id as the default value for the response message control id. **/

            String msg = new String( decodedBytes.array()).trim();//pass to msgUtil - it chks if  lab msg/altona, break it onto new hl7 mesgs. Loop thru the return type oflist and conitnue as below.
           
            String msh = getMshSegment(msg);

            if ( isProcessLabMessage() && isLabMessage( msh ) && isAltonaLabMessage( msh ) )
            {
                List<String> hl7Msgs = messageUtil.extractHl7Messages( msg, msh );

                for ( String hl7Msg : hl7Msgs )
                {
                    // If this fails, then try the NonHapiHL7Support try/catch HL7SupportException
                    //hl7Support = new Hl7SupportHdrLegacy( new String( decodedBytes.array() ) );
                    hl7Support = new Hl7SupportHdrLegacy( hl7Msg );

                    /** Call to hdr2 database required to create message controlId this will override the default value.

                     * Note: This logic will prevent sending messages to queues if data base is down.  

                     **/
                    responseMessageControlId = createMessageControlId();
                    // Validate access throws exception if access denied
                    accessValidator.validateAccess( hl7Support );
                    /** Process message using message specific processor instance. **/
                    mediatorResponse = messageMediatorDispatcher.dispatch( hl7Support );
                }
            }
            else
            {
                hl7Support = new Hl7SupportHdrLegacy( msg );

                /** Call to hdr2 database required to create message controlId this will override the default value.

                 * Note: This logic will prevent sending messages to queues if data base is down.  

                 **/
                responseMessageControlId = createMessageControlId();
                // Validate access throws exception if access denied
                accessValidator.validateAccess( hl7Support );
                /** Process message using message specific processor instance. **/
                mediatorResponse = messageMediatorDispatcher.dispatch( hl7Support );
            }

        }
        catch ( MllpIllegalEncodingException mle )
        {
            /** Log exceptions caused by decode **/
            exception = mle;
            appLogger.logMessage( "Error occured decoding message.", mle );
        }
        catch ( HL7SupportException hse )
        {
            exception = hse;
            appLogger.logMessage( "Error occured creating hl7 traslation support.", hse );
        }
        catch ( AccessValidatorException ave )
        {
            /** Error validating message. */
            exception = ave;
            appLogger.logMessage( "Error occured validating message. Access to message mediator denied.", ave );
        }
        catch ( DataBaseAccessException dae )
        {
            /** Error creating message control id **/
            exception = dae;
            appLogger.logMessage( "Error connecting to database", dae );
        }
        catch ( DispatchException mde )
        {
            /** Log exception caused by dispatch **/
            exception = mde;
            appLogger.logMessage( "Error occured processing message.", mde );
        } // Only catch our exceptions let framework handle runtime and unchecked exceptions
        catch ( Exception e )
        {
            exception = e;
            appLogger.logMessage( "Fatal Error occured processing message.", e );
        }

        /** Create response to client and indicate what errors occurred processing the message **/
        String response = responseGenerator.createResponse( hl7Support, mediatorResponse, responseMessageControlId, exception );
        sendResponse( response, session );
    }


    /**
     * Sends response back to a client
     * @param response
     * @param connection
     * @throws IOException
     */
    private void sendResponse( String response, IoSession session )
        throws IOException
    {
        /** Convert Response to ISO-8859-1 - Happens in the MLLP Utility encode method. **/
        /** Send response **/

        byte[] byteArray = MllpUtil.encode( ByteBuffer.wrap( response.getBytes() ), Charset.defaultCharset(), Charset.forName( "ISO-8859-1" ) )
                        .array();

        IoBuffer ioBuffer = IoBuffer.allocate( byteArray.length );

        ioBuffer.setAutoExpand( true );
        ioBuffer.setAutoShrink( true );

        ioBuffer.put( byteArray );

        ioBuffer.flip();

        session.write( ioBuffer );

        session.close( false );
    }


    /**
     * Wrap any database exception into checked exception
     * @param hl7Support
     * @return
     * @throws DataBaseAccessException
     */
    private String createMessageControlId( )
        throws DataBaseAccessException
    {
        String responseMessageControlId = null;
        try
        {
            responseMessageControlId = HL7Utility.createMessageControlId();
        }
        catch ( Throwable e )
        {
            throw new DataBaseAccessException( ErrorCodeEnum.DATABASE_ACCESS_EXCEPTION, e );
        }
        return responseMessageControlId;
    }


    @Override
    public void sessionIdle( IoSession session, IdleStatus status )
        throws Exception
    {
        session.close( false );

    }


    @Override
    public void messageSent( IoSession session, Object message )
        throws Exception
    {
        super.messageSent( session, message );
    }


    private String getMshSegment( String hl7Message )
    {
        String line = null;
        String msh = null;

        Scanner scanner = new Scanner( hl7Message );
        while ( scanner.hasNextLine() )
        {
            line = scanner.nextLine();
            if ( line.startsWith( "MSH" ) )
            {
                msh = line;
                break;
            }

        }
        scanner.close();

        return msh;
    }


    private boolean isLabMessage( String msh )
    {
        //return hl7Message.substring( 0, 15 ).contains( LAB_MSG );
        return msh.substring( 0, 15 ).contains( LAB_MSG );
    }


    private boolean isAltonaLabMessage( String msh )
    {
        return msh.substring( 16, 19 ).contains( ALTONA_SITE );
    }
}
