

package gov.va.med.cds.util;


import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exception.MllpIllegalEncodingException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;


public class MllpUtil
{

    /** Start of block byte definition. */
    public static final byte SOB = 0x0B;

    /** End of block byte definition. */
    public static final byte EOB = 0x1C;

    /** ASCII code for new line. */
    public static final byte LF = 0x0A;

    /** ASCII code for carriage return. */
    public static final byte CR = 0x0D;


    /**
     * Strips the MLLP protocol characters off from the message.
     * 
     * @param buffers The incoming buffers containing the MLLP encoded message.
     * @return ByteBuffer The buffer containing the decoded message.
     * @throws IOException
     */
    public static ByteBuffer decode( ByteBuffer[] buffers, Charset inCharset, Charset outCharset )
    {
    	return decode(buffers, false, inCharset, outCharset);
    }
    
    /**
     * Strips the MLLP protocol characters off from the message. It is expected that the 
     * Bytes in the ByteBuffer array input parameter will be ISO-8859-1 encoded and that the 
     * output ByteBuffer will be UTF-16 encoded.
     * 
     * @param buffers The incoming buffers containing the MLLP encoded message.
     * @param ignoreEOB If true, return the decoded bytes regardless of whether EOB character
     *                  was found or not.
     * @return ByteBuffer The buffer containing the decoded message.
     * @throws IOException
     */
    public static ByteBuffer decode( ByteBuffer[] buffers, boolean ignoreEob, Charset inCharset, Charset outCharset )
    {
        boolean eobFound = false;

        StringBuffer buffer = new StringBuffer();

        for ( ByteBuffer byteBuffer : buffers )
        {
            try {

                InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(byteBuffer.array(), 0, byteBuffer.limit()), inCharset);
            	int i = -1;

                // Reads until the buffer is empty or until a packet
                // is fully reassembled.
        	
				while ( (i = reader.read()) != -1 )
				{
					char c = (char)i;

				    // Copies into the temporary buffer
				    if ( eobFound )
				    {
				        if ( i == 0 || i != (int)CR )
				        {
				            throw new MllpIllegalEncodingException( ErrorCodeEnum.MLLP_ILLEGAL_ENCODING_EXCEPTION );
				        }
				        else
				        {
				            continue;
				        }

				    }

				    // this indicates the start of a packet
				    if ( c == ((char)SOB) )
				    {
				        continue;
				    }
				    if ( c == ((char)EOB) )
				    {
				        eobFound = true;
				        continue;
				    }

				    buffer.append( c );
				}
			} catch (IOException e) {
				throw new MllpIllegalEncodingException( ErrorCodeEnum.MLLP_ILLEGAL_ENCODING_EXCEPTION, e );
			}			
		}
        
        // If the EOB character was found, return the decoded message; otherwise return 
        // null which serves to indicate that a complete MLLP message sent in as a 
        // parameter.
        return  (eobFound || ignoreEob) ? doCharacterEncoding(buffer.toString(), outCharset)  : null;
       
    }
    

    /**
     * Adds the MLLP protocol characters to the message data. Input to encode method is
     * expected to be the default character set for the system. The output ByteBuffer will 
     * be ISO-8859-1 encoded.
     * 
     * @param byteBuffer
     * @return The ByteBuffer containing the encoded message data.
     * @throws IOException
     */
    public static ByteBuffer encode( ByteBuffer byteBuffer, Charset inCharset, Charset outCharset )
    {
        StringBuffer buffer = new StringBuffer();
        
      
        // first decode to ensure we have a non encoded message
        byteBuffer = decode( new ByteBuffer[] { byteBuffer }, true, inCharset, inCharset );
        InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(byteBuffer.array(), 0, byteBuffer.limit()), inCharset);
        
        buffer.append( (char)SOB );
        
        int i = -1;
        int last = 0x00;

        // replace any
        try
        {
            while ( (i = reader.read()) != -1 )
            {
                char c = (char)i;
                
                if ( i == LF )
                {
                    // this is to ensure that we don't get two
                    // carriage returns in a row.
                    if ( last != CR && last != LF )
                    {
                        buffer.append( (char)CR );
                    }
                    last = i;
                    continue;
                }

                buffer.append( c );
                last = i;
            }
        }
        catch ( IOException e )
        {
            throw new MllpIllegalEncodingException( ErrorCodeEnum.MLLP_ILLEGAL_ENCODING_EXCEPTION, e );
        }        
        
        buffer.append( (char)CR )
              .append( (char)EOB )
              .append( (char)CR );

        buffer.trimToSize();
        return doCharacterEncoding(buffer.toString(), outCharset);
    }
    
    private static ByteBuffer doCharacterEncoding(String str, Charset encodingCharset)
    {
        byte[] buff = encodingCharset.encode( str ).array();
        
        // go reverse order through the byte array counting the number of bytes 
        // that are null at the end until the first non-null character is reached.
        
        
        return ByteBuffer.wrap(Arrays.copyOf( buff, (buff.length - countTrailingNullBytes( buff ))));
    }
    
    
    private static int countTrailingNullBytes( byte[] buff )
    {
        int countNulls = 0;
        for(int i = (buff.length-1); i != 0; i--)
        {
            if(buff[i] != 0x00)
            {
                break;
            }
            
            countNulls++;
        }
        
        return countNulls;
    }

}
