/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.hl7;

// Java classes
import java.io.Serializable;
import java.util.List;
import java.util.ArrayList;

// Library classes
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.lang.ObjectUtils;

// Framework Classes
import gov.va.med.fw.hl7.constants.DelimeterConstants;

/** A generic segment class that represents a line of HL7 raw message data.
 * Each segment is delimited by a new line character in a HL7 message data and 
 * each segment contains a sequence of elements delimited by a field delimiter.
 * For a BHS and MSH segment only, the first element is also a field delimiter.
 * 
 * @author Vu Le
 * @version 1.0
 */
public class Segment implements Serializable {

   /**
	* A default serialize version id  
	*/
   private static final long serialVersionUID = 1L;

   /**
    * Default logger for this class
    */ 
   protected Log logger = LogFactory.getLog( getClass() );
   
   /**
    * Instant variables
    */
   private String delimiter = DelimeterConstants.DEFAULT_FIELD_DELIMITER;
   private String[] encoders = DelimeterConstants.DEFAULT_ENCODERS;
   private String elementData = null;
   private List elementList = null;
   private String name = null;
   private boolean propertyChanged = false;
   public static String NULL = "null";

   /**
    * Constructs an empty segment.  This constructor is ususally usd 
    * by derived segment classes to populate its own segment data.
    */
   public Segment() {
      super();
      elementList = new ArrayList();
   }
   
   /** Constructs a segment of elements from a raw data string, an array
    * of valid encoding character strings, and a element delimiter string.
    * This constructor is usually used to construct an inbound message 
    * from a raw message's segment data, a specific encoding characters, 
    * and a field delimiter. 
    * 
    * @param encodingChars An array of encoding characters
    * @param elementDelimiter A field delimiter
    * @param segmentData A raw data string
    * @throws InvalidSegmentException Thrown if failed to construct a segment 
    * due to missing parameters or invalid segment data to parse. 
    */
   public Segment( String[] encodingChars, 
         		   String elementDelimiter, 
         		   String segmentData ) throws InvalidSegmentException {

      if( encodingChars == null || encodingChars.length != 4 ) {
         throw new InvalidSegmentException( "Invalid encoding characters to encode a segment" );
      }
      
      if( segmentData == null ) {
         throw new InvalidSegmentException( "Invalid segment data to encode" );
      }
      
      if( elementDelimiter == null ) {
         throw new InvalidSegmentException( "Invalid segment delimiter to use for encoding segment data" );
      }
      
      int delimiterPos = segmentData.indexOf( elementDelimiter );
      if( delimiterPos == -1 ) {
         throw new InvalidSegmentException( "Invalid segment delimiter in segment: " + segmentData );
      }
      
      // Initialize delimmiter, encoders, segment name, and segment data
      delimiter = elementDelimiter; 
      encoders = encodingChars;
      name = segmentData.substring( 0, delimiterPos );
      elementData = segmentData.substring( name.length(), segmentData.length() );
      
      // convert segment data to a list of elements in order
      elementList = MessageParser.parseElements( name, delimiter, elementData );
   }
   
   /** Constructs a segment of elements from a raw data string.  This constructor 
    * is usually used to construct an inbound message from a raw message's segment 
    * data.
    * 
    * @param data A raw data string
    * @throws InvalidSegmentException Thrown if failed to construct a segment 
    * due to missing parameters or invalid segment data to parse. 
    */
   public Segment( String data ) throws InvalidSegmentException {
      this( DelimeterConstants.DEFAULT_ENCODERS, 
      		DelimeterConstants.DEFAULT_FIELD_DELIMITER, 
            data );
   }
   
   /** Constructs a segment from an array of encoding characters, a field delimiter,
    * a segment name, and a list of field data.  This constructor is usually used to
    * construct an outbound message from a list of specific segment elements, element
    * delimiter, and encoding characters.
    * 
    * @param encodingChars An array of encoding characters
    * @param elementDelimiter	A field delimiter
    * @param segmentName	A segment name
    * @param segmentElements	A list of field data
    * @throws InvalidSegmentException Thrown if failed to construct a segment 
    * due to missing parameters or invalid segment data to parse. 
    */
   public Segment( String[] encodingChars, 
         		   String elementDelimiter, 
         		   String segmentName, 
         		   List segmentElements ) throws InvalidSegmentException  {
      this( encodingChars, 
            elementDelimiter, 
            MessageParser.parseElements( segmentName, elementDelimiter, segmentElements ) );
   }
   
   /** Constructs a segment from a segment name and a list of elements.  This constructor
    * is usually used to construct an outbound message from a list of specific segment 
    * elements, and a segment name. 
    *   
    * @param name A segment name
    * @param elements A list of segment elements
    * @throws InvalidSegmentException Thrown if failed to construct a segment 
    * due to missing parameters or invalid segment data to parse. 
    */
   public Segment( String name, List elements ) throws InvalidSegmentException  {
      this( DelimeterConstants.DEFAULT_ENCODERS, 
      		DelimeterConstants.DEFAULT_FIELD_DELIMITER, 
            name,
            elements );
   }

   /** Returns a segment name. A segment name is the first element in
    * raw segment data string.
    * 
    * @return A segment name
    */
   public String getName() {
      return name;
   }

   /** Returns an HL7 string representation of this segment. For instance,
    * a BHS segment data might look like the following: 
    * BHS^~|\&^MVR^AAC^EDBeGate^200^200410011503^^~T~ORF|Z11~2.3.1~AL~AL^^5806006
    * 
    * @return A HL7 string representation of a segment.
    */
   public String getElementData() {
      if( elementData == null || propertyChanged ) {
         synchronized( this ) {
         	String name = getName();
         	String segmentData = MessageParser.parseElements( name, getElementDelimiter(), elementList ); 
         	elementData = segmentData.substring( name.length(), segmentData.length() );
         	propertyChanged = false;
         }
      }
      return elementData;
   }
   
   /** Returns an element delimiter 
    * @return An element delimiter
    */
   public String getElementDelimiter() {
      return delimiter;
   }

   /** Returns a list of elements containing in this segment.  For a BHS and MSH segment, the 
    * first element in a list of a field separater which is the same as an element delimiter.
    * 
    * @return A list of elements
    * @throws InvalidSegmentException Thrown if failed to parse a segment data into a list of elements
    */
   public List getElements() throws InvalidSegmentException {

      if( elementList == null ) {
         synchronized( this ) {
         	try {
         		elementList = MessageParser.parseElements( getName(), getElementDelimiter(), getElementData() ); 
         	}
         	catch( IllegalArgumentException e ) {
         		throw new InvalidSegmentException( "Failed to get a list of elements", e );
         	}
         }
      }
      return elementList;
   }
   
   /** Returns a component delimiter.  A default delimiter is "~"
    * @return A component delimiter
    */
   public String getComponentDelimiter() {
      return encoders[0]; 
   }
   
   /** Returns a repeat delimiter.  A default delimiter is "|"
    * @return An escape component delimiter
    */
   public String getRepeatDelimiter() {
      return encoders[1]; 
   }
   
   /** Returns an escape delimiter.  A default delimiter is "\\"
    * @return An escape delimiter
    */
   public String getEscapeDelimiter() {
      return encoders[2]; 
   }
   
   /** Returns a sub component delimiter.  A default delimiter is "&"
    * @return A sub component delimiter
    */
   public String getSubComponentDelimiter() {
      return encoders[3]; 
   }
   
   /** Returns an array of elements delimited by "&" in a raw data string
    * @param elements A raw string data containing elements delimited by "&"
    * @return An array of elements.
    */
   public String[] getSubComponents( String elements ) {
      return MessageParser.parseElement( elements, getSubComponentDelimiter() );
   }
   
   /** Returns an array of elements delimited by "|" in a raw data string
    * @param elements A raw string data containing elements delimited by "|"
    * @return An array of elements.
    */
   public String[] getRepeatComponents( String elements ) {
      return MessageParser.parseElement( elements, getRepeatDelimiter() );
   }
   
   /** Returns an array of elements delimited by "~" in a raw data string
    * @param elements A raw string data containing elements delimited by "~"
    * @return An array of elements.
    */
   public String[] getComponents( String elements ) {
      return MessageParser.parseElement( elements, getComponentDelimiter() );
   }
   
   /** Returns an array of elements delimited by "\\" in a raw data string
    * @param elements A raw string data containing elements delimited by "\\"
    * @return An array of elements.
    */
   public String[] getEscapeComponents( String elements ) {
      return MessageParser.parseElement( elements, getEscapeDelimiter() );
   }
   
   /** Sets a segment element at the specific position.  This method
    * is used by the derived segment class to set its own elements.
    * @param value A segment's element
    * @param index A sequence of the specific segment's element
    */
   protected void setElement( String value, int index ) {
      
      if( index < 0 ) {
         throw new IllegalArgumentException( "Invalid element index");
      }
      synchronized( this ) {
	      int size = elementList.size();
	      
	      // If an index of a new value is greater than the current list's size
	      // append the list with null then add a new value at the end
	      if( index > size ) {
	         for( int i=size; i<index; i++ ) {
	            elementList.add( i, null);
	         }
	         //If value == null, set double quotes
	         elementList.add( index, getSequenceValue(value)); 
	      }
	      else if( index < size ) {
	      	 // if an index of a new value is less than to the current list's size 
	      	 // remove the current value only if the new value is different from the 
	      	 // current value then add a new value to the list
	      	 Object current = elementList.get( index );
	      	 if( !ObjectUtils.equals( current, value ) ) {
	      	 	elementList.remove( index );
	      	 	elementList.add( index, getSequenceValue(value));
	      	 }
	      }
	      else {
	      	 // If the index is equal to the current list's size, simply add it
	    	  elementList.add( index, getSequenceValue(value));
	      }
	      
	      // Indicate that an element in a list has changed
	      // so that subsequent calls to a getSegmentData will 
	      // return a new segment data string
	      propertyChanged = true;
      }
   }
   
   /** Returns an element at the specific position in a segment
    * @param index A sequence of the element
    * @return String An element at the specific position
    */
   protected String getElement( int index ) {
      
      String value = null;
      if (index >= elementList.size()) {
          return null;
      }
      
      Object element = elementList.get( index );
      if( element != null ) {
          value = (element instanceof String) ? (String)element : element.toString();
      }
      return value;
   }
   
   /** Sets a segment name.  This method is used by a derived segment class
    * to set the segment name.  
    *  
    * @param value A segment name
    */
   protected void setName( String value ) {
      name = value;
   }
   
   /** Sets an element delimiter.  This method is used by a derived segment
    * class to set a segment delimiter.
    * @param input
    */
   protected void setElementDelimiter( String input ) {
      delimiter = input;
   }
   
   private String getSequenceValue(String value)
   {
	   //Work around to ensure Segment getSequenceValue method to return empty string to VistA
	   //REEG_00004330 - Change is to remove this Other Federal Agency value from VistA by sending empty string
	   //return (value == null ? "" : (value.length() == 0 ? null : value)); 
	   String newValue = value;
	   if (value == null) {
		   newValue = "";
	   } else if (value.length() == 0) {
		   newValue = null;
	   } else if (Segment.NULL.equals(value)) {
		   newValue = "";
	   }
	   return newValue;
		     
   }
}