package gov.va.med.esr.messaging.service;

// Java Classes
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang.SystemUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xml.sax.SAXException;

import gov.va.med.esr.common.model.lookup.AckType;
import gov.va.med.esr.common.model.lookup.MessageStatus;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.messaging.MessageLogEntry;
import gov.va.med.esr.common.model.messaging.MsdsErrorLogEntry;
import gov.va.med.esr.common.model.messaging.MsdsMessageLogEntry;
import gov.va.med.esr.common.model.person.Person;

// Messaging Classes

/**
 * Class to provide messaging related utility services.
 * 
 * @author Martin Francisco
 * @version 1.0
 */
public final class MessageProcessServiceUtil
{
	/**
	 * Method to create message log without formatted message body.
	 * 
	 * @param controlIdentifier
	 * @param batchControlIdentifier
	 * @param type
	 * @param status
	 * @param vaFacility
	 * @param messageBody
	 * @param person
	 * @param transmissionDate
	 * @param ackDate
	 * @param ackType
	 * @param errorText
	 * @param internalErrorText
	 * @rethe HL7 message log.
	 */
    public static MessageLogEntry createMessageLog(
        String controlIdentifier,
        String batchControlIdentifier,
        MessageType type,
        MessageStatus status,
        VAFacility vaFacility,
        String messageBody,
        Person person,
        Date transmissionDate,
        Date ackDate,
        AckType ackType,
        String errorText,
        String internalErrorText)
    {
        //MessageLogEntry logEntry = new MessageLogEntry(type, status,
        //    controlIdentifier, vaFacility, messageBody);
        MessageLogEntry logEntry = new MessageLogEntry();
        logEntry.setType(type);
        logEntry.setStatus(status);
        logEntry.setControlIdentifier(controlIdentifier);
        logEntry.setVaFacility(vaFacility);
        logEntry.setBody(messageBody);
        
        logEntry.setAckDate(ackDate);
        logEntry.setAckType(ackType);
        logEntry.setBatchControlIdentifier(batchControlIdentifier);
        logEntry.setErrorText(errorText);
        logEntry.setInternalErrorText(internalErrorText);
        logEntry.setInitiatingMessage(null);
        logEntry.setPersonId(person == null ? null : (BigDecimal)person.getPersonEntityKey().getKeyValue()); 
        logEntry.setRetransmissionCount(0);
        logEntry.setTransmissionDate(transmissionDate);

        return logEntry;
    }

    /**
     * Method to create message log with formatted message body.
     * 
     * @param controlIdentifier
     * @param batchControlIdentifier
     * @param type
     * @param status
     * @param vaFacility
     * @param messageBody
     * @param formattedMessageBody
     * @param person
     * @param transmissionDate
     * @param ackDate
     * @param ackType
     * @param errorText
     * @param internalErrorText
     * @return The message log.
     */
    public static MessageLogEntry createMessageLog(
        String controlIdentifier,
        String batchControlIdentifier,
        MessageType type,
        MessageStatus status,
        VAFacility vaFacility,
        String messageBody,
        String formattedMessageBody,
        Person person,
        Date transmissionDate,
        Date ackDate,
        AckType ackType,
        String errorText,
        String internalErrorText)
    {
        MessageLogEntry logEntry = MessageProcessServiceUtil.createMessageLog(
            controlIdentifier, batchControlIdentifier, type, status,
            vaFacility, messageBody, person, transmissionDate, ackDate,
            ackType, errorText, internalErrorText);
        logEntry.setFormattedBody(formattedMessageBody);

        return logEntry;
    }
    
    /**
     * Method to create message log with formatted message body.
     * 
     * @param controlIdentifier
     * @param batchControlIdentifier
     * @param type
     * @param status
     * @param vaFacility
     * @param messageBody
     * @param formattedMessageBody
     * @param person
     * @param transmissionDate
     * @param ackDate
     * @param ackType
     * @param errorText
     * @param internalErrorText
     * @return The message log.
     */
    public static MessageLogEntry createMessageLog(
        String controlIdentifier,
        String batchControlIdentifier,
        MessageType type,
        MessageStatus status,
        VAFacility vaFacility,
        String messageBody,
        String formattedMessageBody,
        Person person,
        Date transmissionDate,
        Date ackDate,
        AckType ackType,
        String errorText,
        String internalErrorText,
        String retransmissionInfo)
    {
        MessageLogEntry logEntry = MessageProcessServiceUtil.createMessageLog(
            controlIdentifier, batchControlIdentifier, type, status,
            vaFacility, messageBody, person, transmissionDate, ackDate,
            ackType, errorText, internalErrorText);
        logEntry.setFormattedBody(formattedMessageBody);
        logEntry.setRetransmissionInfo(retransmissionInfo);

        return logEntry;
    }    

            
    
    /**
     * Method to create message log with formatted message body.
     * 
     * @param controlIdentifier
     * @param batchControlIdentifier
     * @param type
     * @param status
     * @param vaFacility
     * @param messageBody
     * @param formattedMessageBody
     * @param person
     * @param transmissionDate
     * @param ackDate
     * @param ackType
     * @param errorText
     * @param internalErrorText
     * @param retransmissionCount
     * @param initiatingMessage
     * @return The message log.
     */
    public static MessageLogEntry createMessageLog(
        String controlIdentifier,
        String batchControlIdentifier,
        MessageType type,
        MessageStatus status,
        VAFacility vaFacility,
        String messageBody,
        String formattedMessageBody,
        Person person,
        Date transmissionDate,
        Date ackDate,
        AckType ackType,
        String errorText,
        String internalErrorText,
        int retransmissionCount,
        MessageLogEntry initiatingMessage)
    {
        MessageLogEntry logEntry = MessageProcessServiceUtil.createMessageLog(
            controlIdentifier, batchControlIdentifier, type, status,
            vaFacility, messageBody, formattedMessageBody, person, transmissionDate, ackDate,
            ackType, errorText, internalErrorText);
        logEntry.setRetransmissionCount(retransmissionCount);
        logEntry.setInitiatingMessage(initiatingMessage); 
        logEntry.setRetransmissionInfo(initiatingMessage == null ? null : 
            initiatingMessage.getRetransmissionInfo());

        return logEntry;
    }
    
    /**
	 * Method to create message log without formatted message body.
	 * 
	 * @param controlIdentifier
	 * @param batchControlIdentifier
	 * @param type
	 * @param status
	 * @param vaFacility
	 * @param messageBody
	 * @param person
	 * @param transmissionDate
	 * @param ackDate
	 * @param ackType
	 * @param errorText
	 * @param internalErrorText
	 * @rethe HL7 message log.
	 */
    public static MsdsMessageLogEntry createMsdsMessageLog(
        BigDecimal controlIdentifier,
        MessageStatus status,
        Person person,
        Date transmissionDate,
        int count,
        MsdsErrorLogEntry error)
    {        
        MsdsMessageLogEntry logEntry = new MsdsMessageLogEntry();
        logEntry.setStatus(status);
        logEntry.setControlIdentifier(controlIdentifier);
        logEntry.setMsdsError(error);        
        logEntry.setPersonId(person == null ? null : (BigDecimal)person.getPersonEntityKey().getKeyValue()); 
        logEntry.setTransmissionCount(count);
        logEntry.setTransmissionDate(transmissionDate);
        

        return logEntry;
    }
    
    public static MsdsErrorLogEntry createMsdsErrorLog(
            String errorText,
            String internalErrorText)
        {        
            MsdsErrorLogEntry logEntry = new MsdsErrorLogEntry();
            logEntry.setErrorText(errorText);
            logEntry.setInternalErrorText(internalErrorText);
            
            

            return logEntry;
        }

    /**
     * Method to format error text.
     * 
     * @param internalError
     * @return The formatted error text.
     */
    public static String formatInternalErrorText(Throwable internalError)
    {
        return ExceptionToStringBuilder.formatInternalErrorText(internalError);
    }
    
    /**
     * Method to format error text.
     * 
     * @param internalError
     * @return The formatted error text.
     */
    public static String formatInternalMSDSErrorText(Throwable internalError)
    {
        return ExceptionToStringBuilder.formatInternalMSDSErrorText(internalError);
    }
    
    /**
     * Method to format error text.
     * 
     * @param internalError
     * @return The formatted error text.
     */
    public static String formatMSDSErrorText(String error)
    {
        return ExceptionToStringBuilder.formatMSDSErrorText(error);
    }

    /**
     * Return the formated message body. 
     * 
     * @param formattedMessage
     * @return The formatted message body.
     */
    public static String getFormattedMessageBody(Object formattedMessage)
    {
        if(formattedMessage instanceof String)
        {
            return (String)formattedMessage;
        }
        else if(formattedMessage instanceof Document)
        {
            try
            {
                Document xmlDocument = (Document)formattedMessage;

                OutputFormat format = OutputFormat.createPrettyPrint();
                format.setNewLineAfterDeclaration(false);

                StringWriter stringWriter = new StringWriter(1000);
                XMLWriter xmlWriter = new XMLWriter(format);

                try
                {
                    xmlWriter.setWriter(stringWriter);
                    xmlWriter.write(xmlDocument);

                    return stringWriter.toString();
                }
                finally
                {
                    stringWriter.close();
                    xmlWriter.close();
                }
            }
            catch(Exception e)
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    /**
     * 
     *
     */
    private MessageProcessServiceUtil()
    {
        super();
    }

    /**
     * 
     * @author Martin Francisco
     * @version 1.0
     */
    private static class ExceptionToStringBuilder
    {
        private static final String ERROR_TEXT = "<error>";
        private static final String INDENT_TEXT = "  ";
        private static final String NULL_TEXT = "<null>";

        private ExceptionToStringBuilder()
        {
            super();
        }

        private static StringBuffer appendField(
            StringBuffer buffer,
            StringBuffer lineStart,
            String fieldName,
            Object value)
        {
            return ExceptionToStringBuilder.appendFieldName(buffer, lineStart,
                fieldName).append(
                (value == null) ? ExceptionToStringBuilder.NULL_TEXT : value
                    .toString());
        }

        private static StringBuffer appendFieldName(
            StringBuffer buffer,
            StringBuffer lineStart,
            String fieldName)
        {
            return buffer.append(lineStart).append(fieldName).append("=");
        }

        private static StringBuffer appendStackTrace(
            StringBuffer buffer,
            StringBuffer lineStart,
            StackTraceElement[] stackTrace)
        {
            ExceptionToStringBuilder.appendFieldName(buffer, lineStart,
                "Stack Trace");

            if(stackTrace == null)
            {
                buffer.append(ExceptionToStringBuilder.NULL_TEXT);
            }
            else
            {
                for(int index = 0; index < stackTrace.length; index++)
                {
                    buffer.append(lineStart).append(
                        ExceptionToStringBuilder.INDENT_TEXT).append(
                        stackTrace[index].toString());
                }
            }

            return buffer;
        }

        static String formatInternalErrorText(Throwable internalError)
        {
            StringBuffer buffer = new StringBuffer(4096);
            ExceptionToStringBuilder.formatInternalErrorText(0, buffer,
                internalError);
            //The implemention for storing a org.springframework.orm.hibernate3.support.ClobStringType
            //can store a max of 32766 characters. So truncate the extra characters if required.  
            if(buffer.length() > 32765)
            {
               return buffer.substring(0, 32765);
            }
            return buffer.toString();
        }
        
        static String formatInternalMSDSErrorText(Throwable internalError)
        {
            StringBuffer buffer = new StringBuffer(4096);
            ExceptionToStringBuilder.formatInternalErrorText(0, buffer,
                internalError);
            //column size is 500  
            if(buffer.length() > 499)
            {               
               return buffer.substring(0,499);            	
            }
            return buffer.toString();
        }
        
        static String formatMSDSErrorText(String error)
        {
            StringBuffer buffer = new StringBuffer(4096);
            buffer.append(error);
            //column size is 500  
            if(buffer.length() > 3999)
            {               
               int length = buffer.length();
               return buffer.substring(length - 4000);             	
            }
            return buffer.toString();
        }

        private static void formatInternalErrorText(
            int level,
            StringBuffer buffer,
            Throwable internalError)
        {
            try
            {
                if(internalError == null)
                {
                    buffer.append(ExceptionToStringBuilder.NULL_TEXT);
                }
                else
                {
                    StringBuffer lineStart = ExceptionToStringBuilder
                        .getLineStart(level);

                    buffer.append(internalError.getClass());

                    ExceptionToStringBuilder.appendField(buffer, lineStart,
                        "Message", internalError.getMessage());
                    ExceptionToStringBuilder.appendStackTrace(buffer,
                        lineStart, internalError.getStackTrace());

                    ExceptionToStringBuilder.appendFieldName(buffer, lineStart,
                        "Cause");
                    ExceptionToStringBuilder.formatInternalErrorText(level + 1,
                        buffer, internalError.getCause());

                    if(internalError instanceof DocumentException)
                    {
                        DocumentException e = (DocumentException)internalError;

                        ExceptionToStringBuilder.appendFieldName(buffer,
                            lineStart, "Nested Exception");
                        ExceptionToStringBuilder.formatInternalErrorText(
                            level + 1, buffer, e.getNestedException());
                    }

                    if(internalError instanceof SAXException)
                    {
                        SAXException e = (SAXException)internalError;

                        ExceptionToStringBuilder.appendFieldName(buffer,
                            lineStart, "Exception");
                        ExceptionToStringBuilder.formatInternalErrorText(
                            level + 1, buffer, e.getException());
                    }
                }
            }
            catch(Exception e)
            {
                buffer.append(ExceptionToStringBuilder.ERROR_TEXT);
            }
        }

        private static StringBuffer getLineStart(int level)
        {
            StringBuffer lineStart = new StringBuffer(
                SystemUtils.LINE_SEPARATOR);

            for(int index = 0; index < level; index++)
            {
                lineStart.append(ExceptionToStringBuilder.INDENT_TEXT);
            }

            return lineStart;
        }
    }
}