/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.fw.hl7;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import gov.va.med.fw.hl7.constants.SegmentConstants;

/**
 * A message class encapsulating data of a HL7 message. A HL7 message is
 * formatted in a form of segments of elements delimited with special delimiter
 * character string.
 * 
 * @author Vu Le
 * @version 1.0
 */
public class Message implements Serializable {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = -5390733270095095036L;

	/**
	 * An instance of a logger to log information
	 */
	protected Log logger = LogFactory.getLog(getClass());

	/**
	 * A message data string
	 */
	private String data = null;

	/*
	 * A message type
	 */
	private String type = null;

	/**
	 * A list of message segments
	 */
	private List segments = null; 
	
	/**
	 * Flag to indicate if a segment data needs to be wrapped into multiple 
	 * lines if it exceeds the Vista specified limit of 244 characters
	 */	
	private boolean wrapSegmentData;

	/**
	 * A default constructor of an empty message
	 */
	public Message() {
		super();
	}

	/**
	 * @param data
	 * @throws InvalidMessageException
	 */
	public Message(String data) throws InvalidMessageException {
		this(data, null);
	}

	/**
	 * @param data
	 * @param type
	 * @throws InvalidMessageException
	 */
	public Message(String data, String type) throws InvalidMessageException {
		this.data = data;
		this.type = type;
	}

	/**
	 * A default constructor
	 * 
	 * @param segments
	 * @throws InvalidMessageException
	 */
	public Message(List segments) throws InvalidMessageException {
		this(segments, null);
	}

	/**
	 * A default constructor
	 * 
	 * @param segments
	 * @param type
	 * @throws InvalidMessageException
	 */
	public Message(List segments, String type) throws InvalidMessageException {
		this(MessageParser.parseSegments(segments), type);
		this.segments = segments;
	}

	/**
	 * @param segments
	 * @param type
     * @param wrapSegmentData - Flag to indicate if a segment data needs
     * 		 to be wrapped into multiple lines if it exceeds a certain limit. 
     *       This constructor will mostly be called when the message data needs 
     * 		 to be sent to outbound Vista sites.	  
	 * @throws InvalidMessageException
	 */
	public Message(List segments, String type, boolean wrapSegmentData)
            throws InvalidMessageException
    {
        this(MessageParser.parseSegments(segments, wrapSegmentData),
                type);
        this.segments = segments;
        this.wrapSegmentData = wrapSegmentData;
    }	
	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getMessageID() throws InvalidMessageException {

		String value = null;
		Segment header = getSegment(SegmentConstants.MSH);
		if (header != null) {
			// get a list of elements to get message id
			// which is the 10th element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 10) {
				value = (String) elements.get(9);
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getMessageType() throws InvalidMessageException {

		String value = null;
		Segment header = getSegment(SegmentConstants.MSH);
		if (header != null) {
			// get a list of elements to get message type
			// which is the 9th element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 9) {
				value = (String) elements.get(8);
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getSendingApplication() throws InvalidMessageException {

		String value = null;
		Segment header = getSegment(SegmentConstants.MSH);
		if (header != null) {
			// get a list of elements to get a sending application
			// which is the 3rd element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 3) {
				value = (String) elements.get(2);
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getSendingFacility() throws InvalidMessageException {
		return getSendingFacility(SegmentConstants.MSH);
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getSendingFacility(String segment)
			throws InvalidMessageException {
		if (segment == null
				|| (!SegmentConstants.MSH.equals(segment) && !SegmentConstants.BHS
						.equals(segment)))
			throw new IllegalArgumentException(
					"Can only get sending facility from MSH or BHS segments - not passed in segment: "
							+ segment);

		String value = null;
		Segment header = getSegment(segment);
		if (header != null) {
			// get a list of elements to get a sending facility
			// which is the 4th element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 4) {
				value = (String) elements.get(3); 
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getReceivingApplication() throws InvalidMessageException {

		String value = null;
		Segment header = getSegment(SegmentConstants.MSH);
		if (header != null) {
			// get a list of elements to get a receiving application
			// which is the 5th element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 5) {
				value = (String) elements.get(4);
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getReceivingFacility() throws InvalidMessageException {

		String value = null;
		Segment header = getSegment(SegmentConstants.MSH);
		if (header != null) {
			// get a list of elements to get a receiving facility
			// which is the 6th element
			List elements = header.getElements();
			if (elements != null && elements.size() >= 6) {
				value = (String) elements.get(5);
			}
		}
		return value;
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String getDelimiterCharacter() throws InvalidMessageException {
		return MessageParser.getDelimiterCharacter(getMessageData());
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public String[] getEncodingCharacters() throws InvalidMessageException {
		return MessageParser.getEncodingCharacters(getMessageData());
	}

	/**
	 * @return
	 */
	public String getMessageData()
    {
        if (segments != null && !segments.isEmpty())
        {
            this.data = MessageParser.parseSegments(segments, this.wrapSegmentData);
        }

        return data;
    }

	/**
	 * @param data
	 */
	public void setMessageData(String data) {
		this.data = data;
	}

	/**
	 * @param type
	 */
	public void setType(String type) {
		this.type = type;
	}

	/**
	 * @return
	 */
	public String getType() {
		return type;
	}

	/**
	 * @param segments
	 * @throws InvalidMessageException
	 */
	public void setSegments(List segments) throws InvalidMessageException {
		this.segments = segments;
		this.data = MessageParser.parseSegments(segments);
	}

	/**
	 * @return
	 * @throws InvalidMessageException
	 */
	public List getSegments() throws InvalidMessageException {
		if (segments == null) {
			synchronized (this) {
				segments = MessageParser.parseSegments(getDelimiterCharacter(),
						getEncodingCharacters(), getMessageData());
			}
		}
		return segments;
	}

	/**
	 * Returns the first named segment found in the segment list
	 * 
	 * @param name
	 *            The segment name
	 * @return Segment The named segment
	 * @throws InvalidMessageException
	 *             Throw if failed to return the named segment
	 */
	public Segment getSegment(String name) throws InvalidMessageException {
		return getSegment(getSegments(), name);
	}

	/**
	 * @param name
	 * @return
	 * @throws InvalidMessageException
	 */
	public List getSegments(String name) throws InvalidMessageException {
		return getSegments(getSegments(), name);
	}

	/**
	 * @param segmentName
	 * @param elementPosition
	 * @return
	 */
	public String getElement(String segmentName, int elementPosition) {

		String value = null;
		try {
			value = getElement(getSegment(segmentName), elementPosition);
		} catch (InvalidMessageException e) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to get " + segmentName
						+ " element position " + elementPosition);
			}
		}
		return value;
	}

	/**
	 * @param segment
	 * @param elementPosition
	 * @return
	 */
	public String getElement(Segment segment, int elementPosition) {
		String value = null;
		try {
			List elements = segment.getElements();
			if (elements != null && elements.size() >= elementPosition) {
				value = (String) elements.get((elementPosition - 1));
			}
		} catch (InvalidMessageException e) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to get " + segment.getName()
						+ " element position " + elementPosition);
			}
		}
		return value;
	}

	/**
	 * @param segments
	 * @param name
	 * @return
	 */
	protected Segment getSegment(List segments, String name) {
		Segment segment = null;
		if (segments != null && !segments.isEmpty() && name != null) {
			for (Iterator i = segments.iterator(); i.hasNext();) {
				Segment aSegment = (Segment) i.next();
				if (aSegment.getName().equals(name)) {
					segment = aSegment;
					break;
				}
			}
		}
		return segment;
	}

	/**
	 * @param segments
	 * @param name
	 * @return
	 */
	protected List getSegments(List segments, String name) {

		List found = null;

		if (segments != null && !segments.isEmpty() && name != null) {
			found = new ArrayList();

			for (Iterator i = segments.iterator(); i.hasNext();) {
				Segment segment = (Segment) i.next();
				if (segment.getName().equals(name)) {
					found.add(segment);
				}
			}
		}
		return found;
	}
}