/*******************************************************************************
 *  Copyright 2017 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.hl7282.baselibs.segments;

import java.io.IOException;
import java.util.Locale;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import cdac.medinfo.sdk.hl7282.baselibs.CompositeDataType;
import cdac.medinfo.sdk.hl7282.baselibs.PrimitiveDataType;
import cdac.medinfo.sdk.hl7282.baselibs.segmentfactory.DataElementFactory;
import cdac.medinfo.sdk.hl7282.baselibs.segmentfactory.SegmentMapReader;
import cdac.medinfo.sdk.hl7282.commoninterface.IDataType;
import cdac.medinfo.sdk.hl7282.commoninterface.IDelimiter;
import cdac.medinfo.sdk.hl7282.commoninterface.IHL7Stream;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegment;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegmentKey;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.ConstrainViolationException;
import cdac.medinfo.sdk.hl7282.exceptions.DelimiterMissingException;
import cdac.medinfo.sdk.hl7282.exceptions.DumpException;
import cdac.medinfo.sdk.hl7282.exceptions.FieldMissingException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidDataTypeException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidDelimiterException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidFieldException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidSegmentException;
import cdac.medinfo.sdk.hl7282.exceptions.SerializeException;
import cdac.medinfo.sdk.hl7282.unrecognized.UnRecognizedField;
import cdac.medinfo.sdk.hl7282.utils.EnumSegments;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Constants;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.IMultiValuedPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.ISegmentMap;
import cdac.medinfo.sdk.hl7282.utils.ISegmentMapReader;
import cdac.medinfo.sdk.hl7282.utils.MultiValuedPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;

/**
 * Segment is the abstract class which Declares the Methods Parse and Serialize.
 * and a vector variable which contains the list of Fields.
 * This class provides mechanism to serialize, parse and validate segment against HL7 v 2.8.2 standard.
 * The class provides accessors & mutators for the elements mentioned above.
 * The class has validate method which validates Message against HL7 v 2.8.2 standard.
 * It provides user with serialize method to serialize message object to any stream & 
 * parse method to obtain fully populated & validated Message object from any stream.
 * This class provides the Utility methods likes Update and Remove of Data Elements.
 * This class has a facility to Serialize Segment information on XML and Word Doc.
 */
public abstract class Segment implements ISegment    
{
	private ISingleCollection<UnRecognizedField> UnrecognizedFields = null;
	protected IMultiValuedPairedCollection<Integer,IDataType> objDataElementList = null;
	ISegmentMap objSegmentMap = null;
	private DataElementFactory objDataElementFactory = null;

	/** 
	 * Holds Segment Name.  
	 */
	protected ISegmentKey enumSegments;

	/**
	 * Initialize instance member variable.
	 * @param objSegmentKey enum used for SegmentName.
	 */
	public Segment(ISegmentKey objSegmentKey)
	{
		this.enumSegments = objSegmentKey;
		objDataElementList = new MultiValuedPairedCollection<Integer, IDataType>();
		objDataElementFactory = DataElementFactory.createInstance();
		UnrecognizedFields = new SingleCollection<UnRecognizedField>();
		ISegmentMapReader objSegmentMapReader = SegmentMapReader.createInstance();
		this.objSegmentMap = objSegmentMapReader.getSegmentMap(objSegmentKey.getValue());
	}

	/**
	 * Initialize instance member variable.
	 * @param objSegmentKey enum used for SegmentName.
	 * @param objSegmentMap Object of the Segment Map, containing Map of Segment.
	 */
	public Segment(ISegmentKey objSegmentKey,ISegmentMap objSegmentMap)
	{
		this.enumSegments = objSegmentKey;
		this.objDataElementList = new MultiValuedPairedCollection<Integer, IDataType>();
		this.objDataElementFactory = DataElementFactory.createInstance();
		this.objSegmentMap = objSegmentMap;
		UnrecognizedFields = new SingleCollection<UnRecognizedField>();
	}

	/**
	 * Add UnRecognizedField in list
	 * @param objUnRecognizedField object of UnRecognizedField.
	 */
	public void addUnRecognizedField(UnRecognizedField objUnRecognizedField) {
		UnrecognizedFields.add(objUnRecognizedField);
	}
	/**
	 * Retrieves list of Unrecognized field.
	 * @return Isinglecollection<UnRecognizedField> - list of UnRecognized field.
	 */
	public ISingleCollection<UnRecognizedField> getUnrecognizedFields() {
		return UnrecognizedFields;
	} 
	/**
	 * Get the Fields collection of Segment
	 * @return IMultiValuedPairedCollection&gt;Integer, IDataType&lt; - Collection of DataType of the Segment.
	 */
	public IMultiValuedPairedCollection<Integer, IDataType> getFields()
	{
		IMultiValuedPairedCollection<Integer, IDataType> objDataTypeCollection = null;
		if(objDataElementList != null)
		{
			objDataTypeCollection = new MultiValuedPairedCollection<Integer, IDataType>();
			ISingleCollection<Integer> objItemIDCollection = objSegmentMap.getItemIDList();
			for(Integer intItemID : objItemIDCollection)
			{
				ISingleCollection<IDataType> objTempDataTypeCollection = objDataElementList.get(intItemID);
				objDataTypeCollection.put(intItemID, objTempDataTypeCollection);
			}
		}

		return objDataTypeCollection;
	}

	/**
	 * Serializes HL7 Message objects.
	 * Before serializaton it validates Message as per HL7 v2.8.2 standard.
	 * For successfully serialization , Message Object should follow order of segments as per its corresponding Message Structure. 
	 * Serialize method fails if given stream is null.
	 * It mandates presence of required segments and the number of occurrences specified by the standard.
	 * This method also checks for presence of Mandatory Groups.It also verifies repeatability of Group.
	 * @param objIHL7Stream Object of HL7Stream class
	 * @param objIDelimiter Object of Delimiter class present as inner class in MSG class
	 * @return int - function status [success/failure].
	 * @throws SerializeException Thrown when serialization of Message fails.
	 * @throws IOException Signals that an I/O exception of some sort has occurred.
	 */
	public int serialize(IHL7Stream objIHL7Stream,IDelimiter objIDelimiter) throws SerializeException,IOException
	{
		try
		{
			String strSegmentName = this.enumSegments.getValue();
			ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();
			//get the Character used as the repetition Separator for the repeating field. 
			char cRSeparator = objIDelimiter.getRepetitionSeparator();
			if(cRSeparator == '\0')
				throw new DelimiterMissingException(ResourceBundleWrapper.getValue("exceptions", "RepetitionSeparatorMissing", new Locale(HL7Config.createInstance().getLocale())));			//get the Character used as the Field Seperator for Seperating the field. 
			char cFSeparator = objIDelimiter.getFieldSeparator();
			if(cFSeparator == '\0')
				throw new DelimiterMissingException(ResourceBundleWrapper.getValue("exceptions", "FieldSeparatorMissing", new Locale(HL7Config.createInstance().getLocale())));			

			//Write SegmentID on stream.
			if(strSegmentName.length() > 3)
			{
				strSegmentName = strSegmentName.substring(0,3);
			}
			objIHL7Stream.writeString(strSegmentName);	
			objIHL7Stream.writeChar(cFSeparator);

			for(int seqno = 0; seqno < objItemIDList.size();seqno++)
			{
				int itemID = objItemIDList.elementAt(seqno);

				ISingleCollection<IDataType> objDataTypeList = objDataElementList.get(itemID);

				if(objDataTypeList != null)
				{
					int iSize = objDataTypeList.size();
					for(int index = 0; index < objDataTypeList.size(); index++)
					{
						IDataType objIDataType = objDataTypeList.elementAt(index);
						objIDataType.write(objIHL7Stream,objIDelimiter);

						if(index != iSize - 1)
							objIHL7Stream.writeChar(cRSeparator);
					}
				}
				if(seqno != objItemIDList.size() - 1)
				{
					//This Code checks next elements are present or not
					for(int i= seqno+1 ; i<objItemIDList.size();i++)
					{
						int itemID1 = objItemIDList.elementAt(i);
						ISingleCollection<IDataType> objDataTypeList1 = objDataElementList.get(itemID1);
						//If next element is present then it appends Field Separator
						if(objDataTypeList1 != null)
						{
							objIHL7Stream.writeChar(cFSeparator);
							break;
						}
					}
				}
			}

			//This character shows the end of the segment.
			objIHL7Stream.writeChar('\r');
			return HL7Constants.HL7_FUNCTION_SUCCESS;
		}
		catch(InvalidSegmentException ex)
		{
			throw new SerializeException(this.getClass().getSimpleName(),ex);
		}
		catch(DelimiterMissingException ex)
		{
			throw new SerializeException(this.getClass().getSimpleName(),ex);
		}
	}

	/**
	 * Validates HL7 Message objects .
	 * Validate method checks for validation each and every segments in side the Message object against specified message Structure. 
	 * Validation checks include optionality of segment or group.
	 * It also verifies the number of times segment or group is repeated in message.
	 * e.g 
	 * {PSH} while parsing PSH segment validate method will check for its mandatory presence.
	 * [PSH] in this case validate will throw exception if one tries to enter more than one PSH segment.
	 * @return boolean - true or false.
	 * @throws InvalidSegmentException Throws when Segment Validation fails.
	 */
	public boolean validate() throws InvalidSegmentException
	{
		try
		{

			String strSegmentName = this.enumSegments.getValue();
			@SuppressWarnings("unused")
			boolean isHeader = false;
			if(EnumSegments.HL7_SEG_MSH.getValue().equals(strSegmentName) || EnumSegments.HL7_SEG_BHS.getValue().equals(strSegmentName) || EnumSegments.HL7_SEG_FHS.getValue().equals(strSegmentName))
				isHeader = true;

			if(strSegmentName.length() > 3)
			{
				strSegmentName = strSegmentName.substring(0,3);
			}

			ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();

			for(Integer iItemID : objItemIDList)
			{
				boolean isRepeatable = objSegmentMap.isRepeatable(iItemID);
				boolean isOptional = objSegmentMap.isOptional(iItemID);
				String strField = objSegmentMap.getFieldName(iItemID);
				int intOccurrence = objSegmentMap.getOccurrence(iItemID);
				ISingleCollection<IDataType> objDataTypeList = objDataElementList.get(iItemID);

				if(objDataTypeList == null || objDataTypeList.size() == 0)
				{
					if(!isOptional)
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							String strMessage = ResourceBundleWrapper.getValue("exceptions", "REQUIRED_FIELD_MISSING",new Locale(HL7Config.createInstance().getLocale()));
							HL7Logger objHLogger = HL7Logger.getLogger();
							objHLogger.log(strMessage + ":" + strField);
						}
					}
				}
				else if(objDataTypeList.size() > 1 && !isRepeatable)
				{
					if(HL7Config.createInstance().isLoggingOn())
					{
						String strMessage = ResourceBundleWrapper.getValue("exceptions", "SegmentConstrainViolated",new Locale(HL7Config.createInstance().getLocale()));
						HL7Logger objHLogger = HL7Logger.getLogger();
						objHLogger.log(strMessage + ":" + strField);
					}
				}

				if(isRepeatable && objDataTypeList != null)
				{
					//DataTypeList must be range between 0 to infinity
					if(intOccurrence == -1 && !(objDataTypeList.size() >= 1))
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							String strMessage = ResourceBundleWrapper.getValue("exceptions", "MaxLimitViolated",new Locale(HL7Config.createInstance().getLocale()));
							HL7Logger objHLogger = HL7Logger.getLogger();
							objHLogger.log(strMessage + " : " + strField);
						}
					}

					if(objDataTypeList.size() > intOccurrence)
					{
						if(intOccurrence > 1)
						{
							if(HL7Config.createInstance().isLoggingOn())
							{
								String strMessage = ResourceBundleWrapper.getValue("exceptions", "MaxLimitViolated",new Locale(HL7Config.createInstance().getLocale()));
								HL7Logger objHLogger = HL7Logger.getLogger();
								objHLogger.log(strMessage + " : " + strField);

							}
											

						}
					}
				}

				// validating all datatypes of segments
				if(objDataTypeList!=null)
				{
					for (IDataType objDataType : objDataTypeList)
					{
						if(objDataType==null)
							throw new InvalidDataTypeException(strSegmentName);
						else
							objDataType.validate();
					}
				}
			}
		}
		catch(FieldMissingException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
		catch(ConstrainViolationException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
		catch(InvalidDataTypeException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
		return true;
	}

	
	/**
	 * Parses HL7 Segment Strings from any Stream into HL7 Segment object.
	 * This method checks for the Repetition of the fields in the Segment.
	 * It also checks for the optionality of the fields as whether they are mandatory or optional.
	 * Successful parsing of the segment populates the object with the data present on the stream and provides
	 * easy accessor and mutator methods to navigate the message.
	 * It also leads to failure if version of given HL7 Message is not 2.8.2.
	 * This method raises exception if there is Segment which is not valid as per HL7 v 2.8.2 standard.
	 * @param strSegmentStream String of HL7Stream.
	 * @param objIDelimiter Object of class Delimiter.
	 * @return int - Success if segment is valid.
	 * 			   - Fail if segment in not valid.
	 * @throws InvalidSegmentException Thrown when Segment Validation Fails. 
	 * @throws IOException Signals that an I/O exception of some sort has occurred.
	 */
	public int parse(String strSegmentStream,IDelimiter objIDelimiter) throws InvalidSegmentException,IOException
	{
		
		int iRetVal = HL7Constants.HL7_FUNCTION_FAIL;
		DataElementFactory objDataElementFactory = DataElementFactory.createInstance();
		char cReadChar = '\0';
		IDataType objIDataType = null;
		try
		{
			int i = -1;
			//get field separator from IDelimiter
			char cFSeparator = objIDelimiter.getFieldSeparator();
			cReadChar = strSegmentStream.charAt(++i);
			if(cReadChar == cFSeparator)
			{
				String strEnumSegment = this.enumSegments.getValue();
				if(strEnumSegment.length() > 3)
				{
					strEnumSegment = strEnumSegment.substring(0, 3);
				}
				ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();
				int[] iValueTable = null;
				String strDTName = null;
				int iMinLength = 0;
				int iMaxLength = 0;
				String strCLength = null;
				String strFieldName = null;
				boolean isOptional;
				for(Integer objItemID : objItemIDList)
				{
					strDTName = objSegmentMap.getDataTypeName(objItemID);
					iValueTable = objSegmentMap.getValueTable(objItemID);
					iMinLength = objSegmentMap.getItemMinimumLength(objItemID);
					iMaxLength = objSegmentMap.getItemMaximumLength(objItemID);
					strCLength = objSegmentMap.getItemConformanceLength(objItemID);
					strFieldName = objSegmentMap.getFieldName(objItemID);
					isOptional=objSegmentMap.isOptional(objItemID);
					do
					{
						String strField="";
						objIDataType = objDataElementFactory.getDataElement(iValueTable, iMinLength, iMaxLength, strCLength, strDTName,HL7Constants.HL7_COMPONENT);
						if(objIDataType == null)
						{
							// if Data Type is withdrawn then skip that value by reading stream till next delimiter
							cReadChar = strSegmentStream.charAt(++i);
							strField=""+cReadChar;
							while(cReadChar != cFSeparator && cReadChar != '\r')
							{
								cReadChar=strSegmentStream.charAt(++i);
								strField= strField+cReadChar;
							}
							if(strField!=null && strField.charAt(0) != '|')
								setUnRecognizedField(strEnumSegment, strFieldName, strField);
						}
						else
						{
							do
							{
								i++;
								strField=strField+strSegmentStream.charAt(i);
							}while(strSegmentStream.charAt(i) != '~' && strSegmentStream.charAt(i)!='|' && strSegmentStream.charAt(i)!='\r');
							iRetVal = objIDataType.read(strField, objIDelimiter);
	
							if(iRetVal == 0)
							{
								if(!isOptional)
								{
									setUnRecognizedField(strEnumSegment, strFieldName, strField);
								}
								objIDataType = null;
								cReadChar = '\0';
								cReadChar = strSegmentStream.charAt(i);
								break;
							}
							else if(iRetVal == HL7Constants.HL7_FUNCTION_FAIL)
							{
								objIDataType = null;
								setUnRecognizedField(strEnumSegment, strFieldName, strField);
							}
							else if(iRetVal == -1)  
							{
								//Avoids the reading of extra characters from stream when Components data type is withdrawn
								objIDataType = null;
								cReadChar = '\0';
								cReadChar= objIDelimiter.getFieldSeparator();
								break;
							}
							else if(iRetVal == HL7Constants.HL7_WITHDRAWN_FIELD)
							{
								if(strField.charAt(0)!='|')
									setUnRecognizedField(strEnumSegment, strFieldName, strField);
							}
							else
							{
								objDataElementList.put(objItemID,objIDataType);
								objIDataType = null;
							}
							cReadChar = '\0';
							cReadChar = strSegmentStream.charAt(i);
						}
						
					}while(cReadChar == objIDelimiter.getRepetitionSeparator());

					if(cReadChar != cFSeparator)
						break;
				}
			}

			if(cReadChar == '\r')
			{
				return HL7Constants.HL7_FUNCTION_SUCCESS;
			}
			else
				return HL7Constants.HL7_INVALID_SEGMENT_TERMINATOR;
		}
		catch(InvalidDataTypeException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(), ex);
		}
		catch(FieldMissingException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
		catch(InvalidFieldException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
		catch(InvalidDelimiterException ex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();						
				objHL7Logger.log(ex.getMessage());
			}
			throw new InvalidSegmentException(this.getClass().getSimpleName(),ex);
		}
	}

	/**
	 * This method returns SegmentName.
	 * @return EnumSegments - enum used for SegmentName.
	 */
	public ISegmentKey getName() 
	{	
		return enumSegments;
	}
	/**
	 * This method Sets SegmentName.
	 * @param enumName enum used for SegmentName.
	 */
	public void setName(ISegmentKey enumName)
	{
		this.enumSegments = enumName;
	}

	/**
	 * 
	 * @param strFieldStream String from HL7Stream.
	 */

	public void moveToNextSegment(String strFieldStream)
	{
		char cTemp = '\0';
		int i = 0;
		while(cTemp != HL7Constants.HL7_CARRIAGE_RETURN)
		{
			cTemp = strFieldStream.charAt(i++);
		}
		return ;
	}

	/**
	 * Method writes the data to the Stream.
	 * @param eventWriter Object of XMLEventWriter
	 * @return int - Success or failure of the read.
	 * @throws IOException - if  IO operation fails 
	 * @throws XMLStreamException - The base exception for unexpected processing errors. This Exception class is used to report well-formedness errors as well as unexpected processing conditions. 
	 */
	public int writeToXML(XMLEventWriter eventWriter) throws IOException, XMLStreamException
	{
		validate();

		ISingleCollection<Integer> objSeqCollection = objSegmentMap.getItemIDList();
		XMLEventFactory eventFactory = XMLEventFactory.newInstance();
		XMLEvent end = eventFactory.createDTD("\n");
		XMLEvent tab = eventFactory.createDTD("\t");
		// Create Start node
		StartElement segStartElement = eventFactory.createStartElement("", "","Segment" );
		eventWriter.add(tab);
		eventWriter.add(segStartElement);

		Attribute attribSegName = eventFactory.createAttribute("Name", getName().getValue());
		eventWriter.add(attribSegName);

		for(Integer iIndex :objSeqCollection)
		{
			String strFieldName = objSegmentMap.getFieldName(iIndex);
			String strDatatypeName = objSegmentMap.getDataTypeName(iIndex);
			ISingleCollection<IDataType> objDataCollection = objDataElementList.get(iIndex);
			if(objDataCollection!=null && objDataCollection.size()>0)
			{
				for(IDataType objDataType : objDataCollection)
				{
					// Create Start node
					StartElement sElement = eventFactory.createStartElement("", "", "Field");
					eventWriter.add(tab);
					eventWriter.add(sElement);

					Attribute  attribFieldName= eventFactory.createAttribute("Name", strFieldName);
					eventWriter.add(attribFieldName);

					Attribute attribType = eventFactory.createAttribute("Type", strDatatypeName);
					eventWriter.add(attribType);

					if(objDataType.isPrimitive() == false)
					{
						CompositeDataType objCompositeDataType = (CompositeDataType)objDataType;
						objCompositeDataType.writeToXML(eventWriter);
					}
					else
					{
						PrimitiveDataType objPrimitiveDataType = (PrimitiveDataType)objDataType;
						String strValue = (String)objPrimitiveDataType.getValue();

						Attribute attribFieldValue = eventFactory.createAttribute("Value", strValue);
						eventWriter.add(attribFieldValue);
					}
					// Create End node
					EndElement SegEndElement = eventFactory.createEndElement("", "", "Field");
					eventWriter.add(SegEndElement);
					eventWriter.add(end);
				}
			}
		}
		// Create End node
		EndElement eElement = eventFactory.createEndElement("", "","Segment" );
		eventWriter.add(eElement);
		eventWriter.add(end);
		return HL7Constants.HL7_FUNCTION_SUCCESS;	
	}

	/**
	 * Method Writes the data to the Stream.
	 * @param XMLEventWriter Object of XMLEventWriter
	 * @return int - Success or failure of the read
	 * @throws IOException - if  IO operation fails 
	 * @throws XMLStreamException - The base exception for unexpected processing errors. This Exception class is used to report well-formed errors as well as unexpected processing conditions. 
	 */
	public int writeToXML_v2(XMLEventWriter eventWriter) throws IOException, XMLStreamException
	{
		validate();

		ISingleCollection<Integer> objSeqCollection = objSegmentMap.getItemIDList();
		XMLEventFactory eventFactory = XMLEventFactory.newInstance();
		XMLEvent end = eventFactory.createDTD("\n");
		XMLEvent tab = eventFactory.createDTD("\t");
		eventWriter.add(end);
		
		String segment=getName().getValue();
		// Create Start node
		StartElement segStartElement = eventFactory.createStartElement("", "",segment );
		eventWriter.add(tab);
		eventWriter.add(segStartElement);

		for(Integer iIndex :objSeqCollection)
		{

			int sequenceNo=objSegmentMap.getSequenceNo(iIndex);
			ISingleCollection<IDataType> objDataCollection = objDataElementList.get(iIndex);
			if(objDataCollection!=null && objDataCollection.size()>0)
			{
				for(IDataType objDataType : objDataCollection)
				{
					// Create Start node		
					StartElement sElement = eventFactory.createStartElement("", "", segment+"."+sequenceNo);
					eventWriter.add(end);
					eventWriter.add(tab);
					eventWriter.add(tab);
					eventWriter.add(sElement);
				
					if(objDataType.isPrimitive() == false)
					{
						CompositeDataType objCompositeDataType = (CompositeDataType)objDataType;
						eventWriter.add(end);
						objCompositeDataType.writeToXML_v2(eventWriter);
					}
					else
					{
						PrimitiveDataType objPrimitiveDataType = (PrimitiveDataType)objDataType;
						String strValue = (String)objPrimitiveDataType.getValue();
						if(!strValue.isEmpty()){
						Characters nameChars = eventFactory.createCharacters(strValue);
						eventWriter.add(nameChars);
						}
					}
					// Create End node
					EndElement eElement = eventFactory.createEndElement("", "",segment+"."+sequenceNo);
					eventWriter.add(eElement);					
				}
			}
		}
		EndElement eElement = eventFactory.createEndElement("", "",segment );
		eventWriter.add(end);
		eventWriter.add(tab);
		eventWriter.add(eElement);
		return HL7Constants.HL7_FUNCTION_SUCCESS;	
	}
	
	/**
	 * Serializes Segment information like it name, Attribute name and its values.
	 * @param objIHL7Stream Object of IHL7Stream
	 * @param objIDelimiter Object of IDelimiter
	 * @param iLevel integer shows level of an attribute.
	 * @throws DumpException Signals that dump operation is failed
	 * @throws IOException Signals that an I/O exception of some sort has occurred.
	 */
	public void dumpSegment(IHL7Stream objIHL7Stream,IDelimiter objIDelimiter, int iLevel)throws DumpException,IOException
	{

		String strSegmentName = this.enumSegments.getValue();
		ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();

		//Write SegmentID on stream.
		if(strSegmentName.length() > 3)
		{
			strSegmentName = strSegmentName.substring(0,3);
		}

		objIHL7Stream.writeString("SEGMENT :: " + strSegmentName + "\n");	

		if(objItemIDList != null)
		{
			for(int iIndex : objItemIDList)
			{
				ISingleCollection<IDataType> objDataTypeList = objDataElementList.get(iIndex);

				if(objDataTypeList != null && objDataTypeList.size() != 0)
				{
					// Writing Field Name
					String strFieldName = objSegmentMap.getFieldName(iIndex);
					for(int j = 1 ; j <= iLevel + 1; j++)
					{
						objIHL7Stream.writeChar('\t');
					}
					objIHL7Stream.writeString(strFieldName + " : ");

					int i = 0;
					for(IDataType objDataType : objDataTypeList)
					{
						if(i != 0)
							objIHL7Stream.writeString(" , ");

						if(objDataType.isPrimitive())
							objIHL7Stream.writeString(objDataType.getValue().toString());
						else
							((CompositeDataType)objDataType).dumpDataType(objIHL7Stream, objIDelimiter, iLevel);

						i++;
					}
					objIHL7Stream.writeString("\n");
				}
			}
		}
	}

	/**
	 * Removes Data Element from Data Element List
	 * @param strAttributeName String value of Attribute Name.
	 * @return True - if attribute removed successfully, else returns false.
	 */
	public int removeDataElement(String strAttributeName, boolean isForcefullyRemove)
	{
		ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();
		int iItemIDToBeRemoved = -1;
		if(objItemIDList != null)
		{
			for(int iItemID : objItemIDList)
			{
				String strFieldName = objSegmentMap.getFieldName(iItemID);
				if(strFieldName.equals(strAttributeName))
				{
					iItemIDToBeRemoved = iItemID;
					break;
				}
			}
		}
		if(iItemIDToBeRemoved == -1)
			return HL7Constants.HL7_FUNCTION_FAIL;

		return removeDataElement(iItemIDToBeRemoved , isForcefullyRemove);
	}

	/**
	 * Removes Data Element from Data Element List
	 * @param iItemIDToBeRemoved Integer value for attribute which is Item ID specified by HL7.
	 * @return True - if attribute removed successfully, else returns false.
	 */
	public int removeDataElement(int iItemIDToBeRemoved, boolean isForcefullyRemove)
	{
		int iResult = HL7Constants.HL7_FUNCTION_SUCCESS;
		if(objDataElementList != null && objDataElementList.contains(iItemIDToBeRemoved))
		{
			ISingleCollection<IDataType> objDataTypeCollection = objDataElementList.get(iItemIDToBeRemoved);
			objDataElementList.remove(iItemIDToBeRemoved);

			try
			{
				this.validate();
			}
			catch(InvalidSegmentException ise)
			{
				if(isForcefullyRemove)
				{
					if(HL7Config.createInstance().isLoggingOn())
					{
						HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "SegmentAttributeRemovedFourcefully", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeRemoved);
					}
					iResult = HL7Constants.HL7_FUNCTION_SUCCESS;
				}
				else
				{
					for(IDataType objDataType : objDataTypeCollection)
					{
						objDataElementList.put(iItemIDToBeRemoved, objDataType);
					}

					if(HL7Config.createInstance().isLoggingOn())
					{
						HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "CanNotFourcefullyRemoveSegmentAttribute", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeRemoved);
					}
					iResult = HL7Constants.HL7_FUNCTION_FAIL;
				}
			}
		}
		else
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "NullSegmentListOrSegmentAttributePresent", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeRemoved);
			}
			iResult = HL7Constants.HL7_FUNCTION_FAIL;
		}
		return iResult;
	}

	/**
	 * Update Data Element from Data Element List
	 * @param iItemIDToBeUpdated Integer value for attribute which is Item ID specified by HL7.
	 * @return True - if attribute removed successfully, else returns false.
	 */
	public int updateDataElement(int iItemIDToBeUpdated, String strSegmentStream , IDelimiter objDelimiter)throws IOException
	{
		if(!objDataElementList.contains(iItemIDToBeUpdated))
		{
			ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();
			if(objItemIDList != null)
			{
				for(int iItemID : objItemIDList)
				{
					if(iItemIDToBeUpdated == iItemID)
					{
						return performUpdation(iItemIDToBeUpdated, strSegmentStream, objDelimiter);
					}
				}
				
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "NullSegmentListOrSegmentAttributePresent", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
			else
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "NullSegmentListOrSegmentAttributePresent", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
		}

		return performUpdation(iItemIDToBeUpdated, strSegmentStream, objDelimiter);
	}

	/**
	 * Update Data Element from Data Element List
	 * @param iItemIDToBeUpdated Integer value for attribute which is Item ID specified by HL7.
	 * @param objIDataType Object of DataType.
	 * @return True - if attribute removed successfully, else returns false.
	 */
	public int updateDataElement(int iItemIDToBeUpdated, IDataType objIDataType)throws IOException
	{
		if(!objDataElementList.contains(iItemIDToBeUpdated))
		{
			ISingleCollection<Integer> objItemIDList = objSegmentMap.getItemIDList();
			if(objItemIDList != null)
			{
				for(int iItemID : objItemIDList)
				{
					if(iItemIDToBeUpdated == iItemID)
					{
						return performUpdation(iItemIDToBeUpdated, objIDataType);
					}
				}
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "NullSegmentListOrSegmentAttributePresent", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
			else
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "NullSegmentListOrSegmentAttributePresent", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
		}

		return performUpdation(iItemIDToBeUpdated, objIDataType);
	}

	private int performUpdation(int iItemIDToBeUpdated,String strSegmentStream , IDelimiter objDelimiter)throws IOException 
	{
		String strDTName = null; 
		int iMinLength = 0;
		int iMaxLength = 0;
		String strCLength = null;
		int[] iValueTable = null;
		ISingleCollection<IDataType> objTempDataTypeList = new SingleCollection<IDataType>();
		IDataType objIDataType = null;
		char cReadChar = '\0';
		int iRetVal = 0;
		int i=0;
		strDTName = objSegmentMap.getDataTypeName(iItemIDToBeUpdated);
		iMinLength = objSegmentMap.getItemMinimumLength(iItemIDToBeUpdated);
		iMaxLength = objSegmentMap.getItemMaximumLength(iItemIDToBeUpdated);
		strCLength = objSegmentMap.getItemConformanceLength(iItemIDToBeUpdated);
		iValueTable = objSegmentMap.getValueTable(iItemIDToBeUpdated);

		do
		{
			objIDataType = objDataElementFactory.getDataElement(iValueTable, iMinLength, iMaxLength, strCLength, strDTName,HL7Constants.HL7_COMPONENT);
			iRetVal = objIDataType.read(strSegmentStream, objDelimiter);

			if(iRetVal == 0 || iRetVal == HL7Constants.HL7_FUNCTION_FAIL)
			{
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
			else
			{
				objTempDataTypeList.add(objIDataType);
				objIDataType = null;
			}
			cReadChar = '\0';
			cReadChar = strSegmentStream.charAt(i++);
		}while(cReadChar == objDelimiter.getRepetitionSeparator());

		if(objTempDataTypeList.size() != 0)
		{
			String strSegmentName = this.enumSegments.getValue();
			if(EnumSegments.HL7_SEG_MSH.getValue().equals(strSegmentName) || iItemIDToBeUpdated == 2)
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "SegmentAttributeCanNotRemoved", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
			else
			{
				objDataElementList.remove(iItemIDToBeUpdated);
				objDataElementList.put(iItemIDToBeUpdated,objTempDataTypeList);
			}
		}
		return HL7Constants.HL7_FUNCTION_SUCCESS;
	}

	private int performUpdation(int iItemIDToBeUpdated,IDataType objIDataType)throws IOException 
	{
		ISingleCollection<IDataType> objTempDataTypeList = new SingleCollection<IDataType>();
		objTempDataTypeList.add(objIDataType);

		if(objTempDataTypeList.size() != 0)
		{
			String strSegmentName = this.enumSegments.getValue();
			if(EnumSegments.HL7_SEG_MSH.getValue().equals(strSegmentName) || iItemIDToBeUpdated == 2)
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger.getLogger().log(ResourceBundleWrapper.getValue("warnings", "SegmentAttributeCanNotRemoved", new Locale(HL7Config.createInstance().getLocale())) + iItemIDToBeUpdated);
				}
				return HL7Constants.HL7_FUNCTION_FAIL;
			}
			else
			{
				objDataElementList.remove(iItemIDToBeUpdated);
				objDataElementList.put(iItemIDToBeUpdated,objTempDataTypeList);
			}
		}
		return HL7Constants.HL7_FUNCTION_SUCCESS;
	}

	/**
	 * This will set the value in collection for given Item ID.
	 * @param objDataType Object of DataType.
	 * @param iItemId Integer value of Item ID.
	 */
	protected void setDataType(Integer iItemId,IDataType objDataType)
	{
		objDataElementList.remove(iItemId);
		if(objDataType != null)
			objDataElementList.put(iItemId,objDataType);
	}

	/**
	 * This will set the collection of values for given Item ID.
	 * @param objDataTypeCollection Collection of DataType.
	 * @param iItemId Integer value of Item ID.
	 */
	protected void setDataType(Integer iItemId,ISingleCollection<IDataType> objDataTypeCollection)
	{
		objDataElementList.remove(iItemId);

		if(objDataTypeCollection!=null)
		{
			for(IDataType objDataType: objDataTypeCollection)
				objDataElementList.put(iItemId,objDataType);
		}
	}

	/**
	 * This will add the value in collection for given Item ID.
	 * @param objDataType Object of DataType.
	 * @param iItemId Integer value of Item ID.
	 */
	protected void addDataType(Integer iItemId,IDataType objDataType)
	{
		if(objDataType != null)
			objDataElementList.put(iItemId,objDataType);
	}

	/**
	 * This will get the collection of DataType for the Item ID.
	 * @param iItemId Integer value of Item ID.
	 * @return ISingleCollection<IDataType> Collection of DataType.
	 */
	protected  ISingleCollection<IDataType> getDataType(Integer iItemId)
	{
		return objDataElementList.get(iItemId);
	}

	/**
	 * This method will remove the value from collection for that Item id.
	 * @param iItemId integer value of Item id.
	 */
	protected void remove(Integer iItemId)
	{
		objDataElementList.remove(iItemId);
	}

	/**
	 * This method get the DataType from the factory for the given Item Id.
	 * @param intItemId Integer value of Item ID.
	 * @return IDataType - Object of Data Type.
	 */
	protected IDataType getDataElement(int intItemId)
	{
		return objDataElementFactory.getDataElement(enumSegments.getValue(), intItemId);
	}
	
	public void setUnRecognizedField(String segmentName,String fieldName,String data)
	{
		UnRecognizedField objRecognizedField = new UnRecognizedField();
		objRecognizedField.setFieldName(fieldName);
		objRecognizedField.setSegmentName(segmentName);
		objRecognizedField.setUnRecognizedData(data);
		UnrecognizedFields.add(objRecognizedField);
	}
}
