/*****************************************************************************************
 * Copyright © 2004 EDS. All rights reserved
 ****************************************************************************************/
package gov.va.med.esr.messaging.xml;

import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import gov.va.med.fw.service.AbstractComponent;

/**
 * Class to handle errors that are encountered while processing messages.
 * 
 * @author Vu Le
 * @version 1.0
 */
public class HL7MessageErrorHandler extends AbstractComponent implements ErrorHandler {
   
   private static final String ignorableErrorPattern_enumeration = "cvc-enumeration-valid: Value '.*' is not facet-valid with respect to enumeration '\\[.*\\]'\\. It must be a value from the enumeration\\.";

   private static final String ignorableErrorPattern_datatype = "cvc-datatype-valid\\.1\\.2\\.2: '.*' is not a valid value of list type '.*'\\.";

   private static final String invalidValuePattern_generic = "cvc-type.3.1.3: The value '.*' of element '([a-zA-Z0-9]+(?:\\.[0-9]+)?)' is not valid\\.";

   private static final String invalidValuePattern_valueConstraint = "cvc-elt\\.5\\.2\\.2\\.2\\.2: The value '.*' of element '([a-zA-Z0-9]+(?:\\.[0-9]+)?)' does not match the \\{value constraint\\} value '(.*)'\\.";

   private static final String missingAttributePattern = "cvc-complex-type\\.4: Attribute '([a-zA-Z0-9]+)' must appear on element '[a-zA-Z0-9]+'\\.";

   private static final String missingElementPattern = "cvc-complex-type\\.2\\.4\\.a: Invalid content was found starting with element '[a-zA-Z0-9]+(?:\\.[0-9]+)?'\\. One of '\\{\"urn:esr-vha-gov:internal:hl7-messages\":([a-zA-Z0-9]+(?:\\.[0-9]+)?)(?:, \"urn:esr-vha-gov:internal:hl7-messages\":([a-zA-Z0-9]+(?:\\.[0-9]+)?))*\\}' is expected\\.";

   private MessageSource messageSource;
   
   // private static final String invalidAttributePattern1 =
   // "cvc-complex-type\\.3\\.1: Value '.*' of attribute '([a-zA-Z0-9]+)' of
   // element 'msg:[a-zA-Z0-9]+' is not valid with respect to the corresponding
   // attribute use\\. Attribute '\\1' has a fixed value of '.*'\\.";
   // private static final String invalidAttributePattern2 =
   // "cvc-attribute\\.3: The value '.*' of attribute '([a-zA-Z0-9]+)' on
   // element 'msg:[a-zA-Z0-9]+' is not valid with respect to its type,
   // '.*'\\.";

   // private static final String invalidMessagePattern =
   // "cvc-complex-type\\.2\\.4\\.a: Invalid content was found starting with
   // element 'msg:[a-zA-Z0-9]+'\\. One of
   // '\\{\"[a-zA-Z0-9\\-]+\":[a-zA-Z0-9]+(?:,
   // \"[a-zA-Z0-9\\-]+\":[a-zA-Z0-9]+)*\\}' is expected\\.";

   // private static final String unknownAttributePattern =
   // "cvc-complex-type\\.3\\.2\\.2: Attribute '([a-zA-Z0-9]+)\\.([0-9]+)' is
   // not allowed to appear in element '.*'\\.";

   // private static final String invalidDataTypeValuePattern1 =
   // "cvc-datatype-valid\\.1\\.2\\.1: '.*' is not a valid value for '.*'\\.";
   // private static final String invalidDataTypeValuePattern2 =
   // "cvc-datatype-valid\\.1\\.2\\.2: '.*' is not a valid value of list type
   // '.*'\\.";
   // private static final String invalidPatternValuePattern =
   // "cvc-pattern-valid: Value '.*' is not facet-valid with respect to pattern
   // '.*' for type '.*'\\.";

   private Set knownSegments;

   public Set getKnownSegments() {
      return this.knownSegments;
   }

   public void setKnownSegments(Set knownSegments) {
      this.knownSegments = knownSegments;
   }

   public MessageSource getMessageSource() {
    return messageSource;
   }

   public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

/**
    * Handles error parsing and validating a HL7 message in XML format
    * 
    * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
    * @param e
    *           The error object.
    */
   public void error(SAXParseException e) throws SAXException {
      if( !this.shouldIgnore(e) ) {
         this.processInvalidValuePatternMatch(e);
         this.processMissingAttributePatternMatch(e);
         this.processMissingElementPatternMatch(e);

         this.processDefaultException(e);
      }
   }

   /**
    * Handles error parsing and validating a HL7 message in XML format
    * 
    * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
    * @param e
    *           The error object.
    */
   public void fatalError(SAXParseException e) throws SAXException {
      // do something then throw
      throw new HL7MessageException(e);
   }

   /**
    * Handles error parsing and validating a HL7 message in XML format
    * 
    * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
    * @param e
    *           The error object.
    */
   public void warning(SAXParseException e) throws SAXException {
      // do something then throw
      throw new HL7MessageException(e);
   }

   private boolean isSegment(String elementName) {
      return ( ( this.knownSegments != null ) && this.knownSegments.contains(elementName) );
   }

   /**
    * The method to get message using lookup code.
    * 
    * @param code
    *           The code to lookup.
    * @return The message.
    */
   private String getMessage(String code) {
      //return super.getApplicationContext().getMessage(code, null, null);
       return messageSource.getMessage(code, null, Locale.getDefault());
   }

   /**
    * Method to get message using lookup code and list of args.
    * 
    * @param code
    *           The code to lookup.
    * @param args
    *           The list of args.
    * @return The message.
    */
   private String getMessage(String code, Object[] args) {
      //return super.getApplicationContext().getMessage(code, args, null);
       return messageSource.getMessage(code, args, Locale.getDefault());
   }

   /**
    * Method to process error handling.
    * 
    * @param e
    *           The error object.
    * @return The SAXException.
    */
   /*
    * private SAXException processDefault(SAXParseException e) { return
    * (Pattern.compile(HL7MessageErrorHandler.invalidDataTypeValuePattern1)
    * .matcher(e.getMessage()).matches() ||
    * Pattern.compile(HL7MessageErrorHandler.invalidDataTypeValuePattern2)
    * .matcher(e.getMessage()).matches() ||
    * Pattern.compile(HL7MessageErrorHandler.invalidEnumerationValuePattern)
    * .matcher(e.getMessage()).matches() ||
    * Pattern.compile(HL7MessageErrorHandler.invalidMaxLengthValuePattern)
    * .matcher(e.getMessage()).matches() ||
    * Pattern.compile(HL7MessageErrorHandler.invalidPatternValuePattern)
    * .matcher(e.getMessage()).matches()) ? null : e; }
    */

   /**
    * Method to process error handling of unknown fields.
    * 
    * @param e
    *           The error object.
    * @return The SAXException.
    */
   /*
    * private SAXException processGenericMatch(SAXParseException e) { SAXException
    * processedException = this.processPatternMatch(e,
    * HL7MessageErrorHandler.unknownAttributePattern, "UNKNOWN_FIELD");
    * //if(processedException == null) //{ // processedException =
    * this.processPatternMatch(e, // HL7MessageErrorHandler.missingElementPattern,
    * "MISSING_SEGMENT"); //} if(processedException == null) { processedException =
    * this.processPatternMatch(e, HL7MessageErrorHandler.invalidMessagePattern,
    * "INVALID_MESSAGE"); } return processedException; }
    */

   /**
    * Method to process error handling for invalid attributes.
    * 
    * @param e
    *           The error object.
    * @return The SAXException.
    */
   /*
    * private SAXException processInvalidAttribute(SAXParseException e) { Matcher matcher =
    * null; if((matcher = Pattern.compile(
    * HL7MessageErrorHandler.invalidAttributePattern1).matcher(e.getMessage())).matches() ||
    * (matcher = Pattern.compile(
    * HL7MessageErrorHandler.invalidAttributePattern2).matcher(e.getMessage())).matches()) {
    * String field = matcher.group(1); String messageCode = null;
    * if(field.equals("msh.15")) { messageCode = "INVALID_ACCEPT_ACK_TYPE"; } else
    * if(field.equals("msh.16")) { messageCode = "INVALID_APPLICATION_ACK_TYPE"; } else
    * if(field.equals("msh.12")) { messageCode = "INVALID_HL7_VERSION"; } else
    * if(field.equals("msh.5")) { messageCode = "INVALID_RECEIVING_APPLICATION"; } else
    * if(field.equals("msh.6")) { messageCode = "INVALID_RECEIVING_FACILITY"; } else
    * if(field.equals("msh.3")) { messageCode = "INVALID_SENDING_APPLICATION"; } else {
    * messageCode = "INVALID_FIELD"; return new
    * HL7MessageException(this.getMessage(messageCode, new Object[] { field }), e); }
    * return new HL7MessageException(this.getMessage(messageCode), e); } else { return
    * null; } }
    */

   private void processDefaultException(SAXParseException e) throws SAXException {
      throw new HL7MessageException(this.getMessage("DEFAULT_MESSAGE"), e);
   }

   private void processInvalidValuePatternMatch(SAXParseException e) throws SAXException {
      if( super.logger.isDebugEnabled() ) {
         super.logger.debug("Testing for an invalid value pattern match");
         super.logger.debug("Pattern: "
               + HL7MessageErrorHandler.invalidValuePattern_generic);
         super.logger.debug("Message: " + e.getMessage());
      }

      Matcher matcher = Pattern.compile(
            HL7MessageErrorHandler.invalidValuePattern_generic).matcher(e.getMessage());

      if( matcher.matches() ) {
         String valueName = matcher.group(1);

         if( super.logger.isDebugEnabled() ) {
            super.logger.debug("MATCH: Invalid value = " + valueName);
         }

         if( valueName != null ) {
            try {
               String errorMessage = this.getMessage("INVALID_" + valueName.toUpperCase()
                     + "_VALUE");
               throw new HL7MessageException(errorMessage, e);
            }
            catch( NoSuchMessageException nsme ) {
               // Do nothing
            }

            String errorMessage = this.getMessage("DEFAULT_INVALID_VALUE_WITH_FIELD",
                  new Object[] { valueName });
            throw new HL7MessageException(errorMessage, e);
         }
         else {
            String errorMessage = this.getMessage("DEFAULT_INVALID_VALUE");
            throw new HL7MessageException(errorMessage, e);
         }
      }
      else {
         if( super.logger.isDebugEnabled() ) {
            super.logger.debug("Pattern: "
                  + HL7MessageErrorHandler.invalidValuePattern_valueConstraint);
            super.logger.debug("Message: " + e.getMessage());
         }

         matcher = Pattern.compile(
               HL7MessageErrorHandler.invalidValuePattern_valueConstraint).matcher(
               e.getMessage());

         if( matcher.matches() ) {
            String valueName = matcher.group(1);
            String expectedValue = matcher.group(2);

            if( super.logger.isDebugEnabled() ) {
               super.logger.debug("MATCH: Invalid value = " + valueName);
            }

            if( "hd.1".equals(valueName) && "MVR".equals(expectedValue) ) {
               throw new HL7MessageException(this.getMessage("INVALID_MSH.3_VALUE"), e);
            }
            else if( "hd.1".equals(valueName) && "EDB eGate".equals(expectedValue) ) {
               throw new HL7MessageException(this.getMessage("INVALID_MSH.5_VALUE"), e);
            }
            else if( "hd.1".equals(valueName) && "200".equals(expectedValue) ) {
               throw new HL7MessageException(this.getMessage("INVALID_MSH.6_VALUE"), e);
            }
            else {
               String errorMessage = this.getMessage("DEFAULT_INVALID_VALUE");
               throw new HL7MessageException(errorMessage, e);
            }
         }
      }
   }

   private void processMissingAttributePatternMatch(SAXParseException e)
         throws SAXException {
      if( super.logger.isDebugEnabled() ) {
         super.logger.debug("Testing for missing attribute pattern match");
         super.logger.debug("Pattern: " + HL7MessageErrorHandler.missingAttributePattern);
         super.logger.debug("Message: " + e.getMessage());
      }

      Matcher matcher = Pattern.compile(HL7MessageErrorHandler.missingAttributePattern)
            .matcher(e.getMessage());

      if( matcher.matches() ) {
         String attributeName = matcher.group(1);

         if( super.logger.isDebugEnabled() ) {
            super.logger.debug("MATCH: Missing attribute = " + attributeName);
         }

         if( "fieldDelimiter".equals(attributeName) ) {
            throw new HL7MessageException(this.getMessage("MISSING_FIELD_SEPARATOR"), e);
         }
         else if( "componentDelimiter".equals(attributeName)
               || "subComponentDelimiter".equals(attributeName)
               || "repeatingFieldDelimiter".equals(attributeName)
               || "escapeCharacter".equals(attributeName) ) {
            throw new HL7MessageException(this.getMessage("MISSING_ENCODING_CHARACTERS"),
                  e);
         }
      }
   }

   private void processMissingElementPatternMatch(SAXParseException e)
         throws SAXException {
      if( super.logger.isDebugEnabled() ) {
         super.logger.debug("Testing for missing element pattern match");
         super.logger.debug("Pattern: " + HL7MessageErrorHandler.missingElementPattern);
         super.logger.debug("Message: " + e.getMessage());
      }

      Matcher matcher = Pattern.compile(HL7MessageErrorHandler.missingElementPattern)
            .matcher(e.getMessage());

      if( matcher.matches() ) {
         for( int index = 1; index <= matcher.groupCount(); index++ ) {
            String elementName = matcher.group(index);

            if( super.logger.isDebugEnabled() ) {
               super.logger.debug("MATCH: Missing element = " + elementName);
            }

            if( elementName != null ) {
               try {
                  String errorMessage = this.getMessage("MISSING_"
                        + elementName.toUpperCase() + "_ELEMENT");
                  throw new HL7MessageException(errorMessage, e);
               }
               catch( NoSuchMessageException nsme ) {
                  // Do nothing
               }

               // Is the missing element a segment
               if( this.isSegment(elementName) ) {
                  String errorMessage = this.getMessage("DEFAULT_MISSING_SEGMENT");
                  throw new HL7MessageException(errorMessage, e);
               }
            }
         }

         // Throw the default message
         String errorMessage = this.getMessage("DEFAULT_MISSING_ELEMENT");
         throw new HL7MessageException(errorMessage, e);
      }
   }

   private boolean shouldIgnore(SAXParseException e) {
      if( super.logger.isDebugEnabled() ) {
         super.logger.debug("Testing for an exception that can be ignored");
      }

      String message = e.getMessage();

      return this.shouldIgnore(HL7MessageErrorHandler.ignorableErrorPattern_enumeration,
            message)
            || this.shouldIgnore(HL7MessageErrorHandler.ignorableErrorPattern_datatype,
                  message);
   }

   private boolean shouldIgnore(String pattern, String message) {
      if( super.logger.isDebugEnabled() ) {
         super.logger.debug("Pattern: " + pattern);
         super.logger.debug("Message: " + message);
      }

      Matcher matcher = Pattern.compile(pattern).matcher(message);

      return matcher.matches();
   }
}