

package gov.va.med.cds.tools.cleanup.errorq;


import gov.va.med.cds.exception.ResponseException;
import gov.va.med.cds.tools.cleanup.errorq.exception.ErrorQCleanupException;
import gov.va.med.cds.util.MllpUtil;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Map;

import javax.annotation.Resource;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.MessagePostProcessor;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
import ca.uhn.hl7v2.parser.PipeParser;
import ca.uhn.hl7v2.util.Terser;
import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;


public class ErrorQListenerWithErrors
    implements
        MessageListener
{

    private static Log logger = LogFactory.getLog( ErrorQListenerWithErrors.class );
    private JmsTemplate jmsTemplate;
    private static int processedNbr = 0;
    private Map<String, String> exceptionsMap;


    public void onMessage( Message message )
    {
        byte[] messageBytes = null;
        String methodName = "onMessage(Message message): ";

        logger.info( methodName + "Begin." );

        try
        {
            if ( message instanceof TextMessage )
            {
                TextMessage textMsg = ( TextMessage )message;
                messageBytes = textMsg.getText().getBytes();
                
                processAllTypes(textMsg.getText());
     
            }
   
            

            //processExceptions();
        }
        catch ( ErrorQCleanupException eqe )
        {
            processedNbr++ ;
            logger.error( "ErrorQCleanupException occured for the message ", eqe );

            processApplicationException( message, new String( messageBytes ), eqe );

        }
        catch ( RuntimeException rte )
        {
            processedNbr++ ;
            
            if ( rte instanceof java.lang.NullPointerException )
            {
                logger.info( "NullPointerExceptio/HL7SupportException occured for the message ", rte );
            }
            else
            {
                logger.error( "RuntimeException occured for the message ", rte );
            }
            sendLowPriorityMessage( new String( messageBytes ) );
        }
        catch ( Exception exception )
        {
            processedNbr++ ;
            String requestId = getRequestId( messageBytes );
            logger.error( "Exception occured for the message ", exception );
            sendLowPriorityMessage( new String( messageBytes ) );
        }
        catch ( Error error )
        {
            processedNbr++ ;
            String requestId = getRequestId( messageBytes );
            logger.error( "Error occured for the message ", error );
            sendLowPriorityMessage( new String( messageBytes ) );
            throw error;

        }
        catch ( Throwable throwable )
        {
            processedNbr++ ;
            String requestId = getRequestId( messageBytes );
            logger.error( "Throwable occured for the message ", throwable );
            sendLowPriorityMessage( new String( messageBytes ) );
            throw new Error( throwable );
        }
        logger.info( methodName + "End." );
    }

   private void processAllTypes(String msg) throws Throwable
   {
       if (msg.contains( "SR-HL-" ))
       {
           throw new ErrorQCleanupException( "HL7_SUPPORT_HAPI_ENCODE_EXCEPTION" );
       }
       else if (msg.contains("SR-ML-"))
       {
           throw new ErrorQCleanupException( "HDRII_OPERATION_FAILED" );
       }
       else if (msg.contains("SR-RLB"))
       {
           throw new ErrorQCleanupException( "Rollback" );
       }
       else if (msg.contains("SR-RTE-"))
       {
           throw new RuntimeException( "RTE OCCURED" );
       }
       else if (msg.contains("SR-EX-"))
       {
           throw new Exception( "EXCEPTION OCCURED" );
       }
       else if (msg.contains("SR-ER-"))
       {
           throw new Error( "ERROR" );
       }
       else if (msg.contains("SR-TH-"))
       {
           throw new Throwable( "THROWABLE" );
       }
   }

    private void processExceptions( )
        throws Exception,
            Error,
            Throwable
    {
        if ( processedNbr == 0 )
        {
            throw new ErrorQCleanupException( "HL7_SUPPORT_HAPI_ENCODE_EXCEPTION" );
        }
        else if ( processedNbr == 1 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 2 )
        {
            throw new ErrorQCleanupException( "x" );
        }
        else if ( processedNbr == 3 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 4 )
        {
            throw new ErrorQCleanupException( "timed out" );
        }
        else if ( processedNbr == 5 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 6 )
        {
            throw new ErrorQCleanupException( "MSH 4.1 and MSH 4.2 in the message are empty" );
        }
        else if ( processedNbr == 7 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 8 )
        {
            throw new ErrorQCleanupException( "MSH 4.1 is empty and not in facilityId map and thus cannot populate the empty PID 3.6.2" );
        }
        else if ( processedNbr == 9 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 10 )
        {
            throw new ErrorQCleanupException( "DoD Patient with LID and ICN does not have Correlated ID between LID and ICN" );
        }
        else if ( processedNbr == 11 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 12 )
        {
            throw new ErrorQCleanupException( "VA Patient with LID and ICN does not have Correlated ID between LID and ICN" );
        }
        else if ( processedNbr == 13 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 14 )
        {
            throw new ErrorQCleanupException( "DoD Patient with only LID, and does not have Correlated ID for LID" );
        }
        else if ( processedNbr == 15 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 16 )
        {
            throw new ErrorQCleanupException( "Patient has only ICN" );
        }
        else if ( processedNbr == 17 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 18 )
        {
            throw new ErrorQCleanupException( new HL7Exception( "HL7Exception" ) );
        }
        else if ( processedNbr == 19 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 20 )
        {
            throw new ErrorQCleanupException( new EncodingNotSupportedException( "x" ) );
        }
        else if ( processedNbr == 21 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 22 )
        {
            throw new RuntimeException( "Throwing RuntimeException Test" );
        }
        else if ( processedNbr == 23 )
        {
            processedNbr++ ;

        }
        else if ( processedNbr == 24 )
        {
            throw new Exception( "Throwing Exception Test" );
        }
        else if ( processedNbr == 25 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 26 )
        {
            throw new Error( "Error Test" );
        }
        else if ( processedNbr == 27 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr == 28 )
        {
            throw new Throwable( "Throwable Test" );
        }
        else if ( processedNbr == 29 )
        {
            processedNbr++ ;
        }
        else if ( processedNbr >= 30 )
        {
            processedNbr++ ;
        }
    }


    /**
     * Used to populate the failure reason in the failed jms Message in case of application exceptions (errorq/message mediatr/cds) to stop reprocessing. Reprocessing is done in case of system exceptions.
     * Only incorrect message format and errorq related exceptions information is stored in the exceptionsmap since the other cds/message mediator exceptions are runtime based and too descriptive to get a concise level of detail.
     * Thus in such cases, need to look in the cds application log table and errorq cleanup logs for more details about the cause(s) based on the request id in the failure reason.
     * 
     * @param rte
     * @return
     */
    private void processApplicationException( Message message, String msg, RuntimeException rte )
    {
        String exceptionMsg = null;
        String searchStr = rte.getMessage();

        if ( rte.getCause() instanceof ResponseException )
        {
            if ( searchStr.contains( "rolled back" ) || searchStr.contains( "JTA" ) || searchStr.contains( "timeout" )
                            || searchStr.contains( "rollback" ) || searchStr.contains( "Rolledback" ) || searchStr.contains( "timed out" )
                            || searchStr.contains( "BEA" ) || searchStr.contains( "SQL" ) || searchStr.contains( "ORA" )
                            || searchStr.contains( "TimedOutException" ) || searchStr.contains( "RollbackException" )
                            || searchStr.contains( "Transaction Rolledback" ) || searchStr.contains( "Transaction timed out" )
                            || searchStr.contains( "TimedOut" ) || searchStr.contains( "Error committing transaction" )
                            || searchStr.contains( "javax.transaction.SystemException" ) || searchStr.contains( "Timeout" ) )

            {
                sendLowPriorityMessage( msg );
            }
            else
            {
                if ( searchStr.contains( "HL7_SUPPORT_HAPI_ENCODE_EXCEPTION" )
                                || searchStr.contains( "ca.uhn.hl7v2.HL7Exception: Can't encode group" )
                                || searchStr.contains( "gov.va.med.cds.hapi.HL7SupportException" ) )
                {
                    exceptionMsg = exceptionsMap.get( "HL7_SUPPORT_HAPI_ENCODE_EXCEPTION" );
                }
                else if ( searchStr.contains( "gov.va.med.cds.xml.schema.SchemaValidationException" )
                                || searchStr.contains( "SCHEMA_VALIDATION_FAILED" ) )
                {
                    exceptionMsg = exceptionsMap.get( "SchemaValidationException" );
                }
                else if ( searchStr.contains( "HDRII_OPERATION_FAILED" ) )
                {
                    exceptionMsg = exceptionsMap.get( "HDRII_OPERATION_FAILED" );
                }
                else
                {
                    if ( searchStr.contains( "Unable to process message" ) && searchStr.contains( "ErrorCode" )
                                    && searchStr.contains( "DisplayMessage" ) )
                    {
                        exceptionMsg = searchStr.substring( searchStr.indexOf( "ErrorCode" ), searchStr.indexOf( "DisplayMessage" ) );
                    }
                    else
                    {
                        exceptionMsg = "Failure to process the message in CDS or message mediator. Look for the cause in cds application log table ";
                    }

                }
                sendWithConversion( message, exceptionMsg );
            }

        }
        else
        {
            exceptionMsg = exceptionsMap.get( rte.getMessage() );

            if ( exceptionMsg != null && !exceptionMsg.equals( "" ) )
            {
                sendWithConversion( message, exceptionMsg );
            }
            else
            {
                if ( rte.getCause() instanceof EncodingNotSupportedException )
                {
                    exceptionMsg = exceptionsMap.get( "EncodingNotSupportedException" );
                    sendWithConversion( message, exceptionMsg );
                }
                else if ( rte.getCause() instanceof HL7Exception )
                {
                    exceptionMsg = exceptionsMap.get( "HL7Exception" );
                    sendWithConversion( message, exceptionMsg );
                }
                else
                {
                    sendLowPriorityMessage( msg );
                }
            }
        }
    }


    private void sendWithConversion( Message message, final String reason )
    {

        jmsTemplate.convertAndSend( message, new MessagePostProcessor()
        {
            public Message postProcessMessage( Message message )
                throws JMSException
            {
                message.clearProperties();
                message.setBooleanProperty( "CDSProcessedMsg", true );
                message.setStringProperty( "CDSFailureReason", reason );
                logger.info( "failure reason is - " + message.getStringProperty( "CDSFailureReason" ) );
                //                message.setJMSCorrelationID("123-00001");
                //                message.setJMSMessageID( "AlreadyProcessed");
                //                message.setJMSRedelivered( true );
                message.setJMSPriority( 0 );

                return message;
            }
        } );

    }


    private String decodeMessage( byte[] messageBytes )
    {
        String decodedMessage;
        // remove non-printable characters from  HL7 messages only 
        ByteBuffer messageBuffer = ByteBuffer.wrap( messageBytes );
        //messageBuffer = MllpUtil.decode( new ByteBuffer[] { messageBuffer } );
messageBuffer = MllpUtil.decode( new ByteBuffer[] { messageBuffer }, true, Charset.forName( "ISO-8859-1" ), Charset.defaultCharset() );

        if ( messageBuffer != null )
        {
            decodedMessage = new String( messageBuffer.array() );
        }
        else
        {
            decodedMessage = new String( ByteBuffer.wrap( messageBytes ).array() );
        }
        return decodedMessage;
    }


    private String getRequestId( byte[] messageBytes )
    {
        String decodedMessage;
        Terser terser = null;
        String requestId = null;

        try
        {
            decodedMessage = decodeMessage( messageBytes );

            if ( decodedMessage.contains( "<graph>" ) )
            {
                requestId = "Graph message";
            }
            else
            {
                // Parse the input request using HAPI
                ca.uhn.hl7v2.model.Message msg = parseUsingHapi( decodedMessage );

                terser = new Terser( msg );
                requestId = terser.get( "/MSH-10" );
                requestId = "with Message Control Id: ".concat( requestId );
            }

        }
        catch ( HL7Exception e )
        {
            new RuntimeException( e );
        }
        return requestId;
    }


    /**
     * Parse the given decoded input HL7 request using HAPI parser
     * 
     * @param decodedHl7Request
     *            - Decoded input HL7 request
     * 
     */
    private ca.uhn.hl7v2.model.Message parseUsingHapi( String decodedHl7Request )
    {
        ca.uhn.hl7v2.model.Message message = null;
        String methodName = "parseUsingHapi( String decodedHl7Request )";

        logger.info( methodName + "Begin." );

        try
        {
            // Replace all /n with /r
            String request = decodedHl7Request.replace( '\n', '\r' );

            // Parse using PipeParser
            PipeParser hapiParser = new PipeParser();
            hapiParser.setValidationContext( new ValidationContextImpl() );
            message = hapiParser.parse( request );
        }
        catch ( Exception e )
        {
            throw new RuntimeException( e );
        }

        logger.info( methodName + "End." );

        return message;
    }


    private void sendLowPriorityMessage( String strMessage )
    {
        final String s = strMessage;

        if ( logger.isDebugEnabled() )
        {
            logger.debug( "sendLowPriorityMessage( String strMessage ): Inserting message back to ErrorQ : " + s );
        }

        this.jmsTemplate.send( new MessageCreator()
        {
            public Message createMessage( Session session )
                throws JMSException
            {
                Message message = session.createTextMessage( s );
                message.setJMSPriority( 0 );
                return message;
            }
        } );
    }


    public void setJmsTemplate( JmsTemplate jmsTemplate )
    {
        this.jmsTemplate = jmsTemplate;
    }


    @Resource
    public void setExceptionsMap( Map<String, String> exceptionsMap )
    {
        this.exceptionsMap = exceptionsMap;
    }
}
