/*******************************************************************************
 *  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 cdac.medinfo.sdk.hl7282.baselibs.CWE;
import cdac.medinfo.sdk.hl7282.baselibs.ST;
import cdac.medinfo.sdk.hl7282.baselibs.segmentfactory.DataElementFactory;
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.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.DelimiterMissingException;
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.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.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;

/**
 * QPD - query parameter definition<p>
 * The QPD segment defines the parameters of the query.
 * <p>Represents an HL7 QPD message segment. 
 * This segment has the following fields:</p><p>
 * QPD-1: Message Query Name (CWE)<b>Required</b><br> 
 * QPD-2: Query Tag (ST)<b>Conditional</b><br> 
 * QPD-3: User Parameters (in successive fields) (varies)<b></b><br><p> 
 * This class provides mechanism to validate message 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.
 *
 * <p>User can also create Segment through {@link SegmentFactory}
 */
 public class QPD extends Segment    
{
	
	/**
	 * Constructs the QPD segment.
	 */
	public QPD()
	{super(EnumSegments.HL7_SEG_QPD);
	}
	//Integer values used in getter/setter is unique item id defined 
	//for each attribute.

	/**
	 * Retrieves the Message Query Name. 
	 * @return CWE - messageQueryName
	 */
	public CWE getMessageQueryName() 
	{
		ISingleCollection<IDataType> objDataCollection =getDataType(1375);
		if(objDataCollection != null && objDataCollection.size() != 0) 
			return (CWE)objDataCollection.elementAt(0);
		else
			return null;
	}

	/**
	 * Retrieves the Query Tag.
	 * This field value the initiating system to identify the query and used to match response messages to the originating query. 
	 * @return ISingleCollection<ST> - queryTag
	 */
	public ST getQueryTag() 
	{
		ISingleCollection<IDataType> objDataCollection =getDataType(696);
		if(objDataCollection != null && objDataCollection.size() != 0) 
			return (ST)objDataCollection.elementAt(0);
		else
			return null;
	}

	/**
	 * Retrieves the User Parameters.
	 * @return DataType - userParameters
	 */
	public ISingleCollection<IDataType> getUserParameters() 
	{
		ISingleCollection<IDataType> objDataCollection =getDataType(1435);
		ISingleCollection<IDataType> objDataList = new SingleCollection<IDataType>(); 
		if(objDataCollection != null && objDataCollection.size() != 0)
		{
			for(IDataType objDataType : objDataCollection)
			{
				objDataList.add(objDataType);
			}
		}
		return objDataList;
	}

	/**
	 * Set the Message Query Name.
	 * @param strIdentifier Specifies of characters (the code) that uniquely identifies the item being referenced. 
	 * @param strText Specifies The descriptive or textual name of the identifier, e.g., myocardial infarction. 
	 * @param strNameOfCodingSystem Identifies the coding scheme being used in the identifier component.
	 * @param strAlternateIdentifier Specifies an alternate sequence of characters (the code) that uniquely identifies the item being referenced.
	 * @param strAlternateText Specifies the descriptive or textual name of the alternate identifier. See usage note in section introduction.
	 * @param strNameOfAlternateCodingSystem Identifies the coding scheme being used in the alternate identifier component. 
	 * @param strCodingSystemVersionID A sequence of characters that uniquely identifies a second alternate code.
	 * @param strAlternateCodingSystemVersionId This component carries the version for the coding system identified by components 4-6.
	 * @param strOriginalText The text as seen and/or selected by the user who entered the data.
	 * @param strSecondAlternateIdentifier A sequence of characters that uniquely identifies a second alternate code.
	 * @param strSecondAlternateText The descriptive or textual name of the alternate identifier.
	 * @param strNameofSecondAlternateCodingSystem Identifies the coding scheme being used in the alternate identifier component.
	 * @param strSecondAlternateCodingSystemVersionID This component carries the version for the coding system identified by components 10-12.
	 * @param strCodingSystemOID This component contains the ISO Object Identifier (OID) for the coding system or value set named in CWE.3.
	 * @param strValueSetOID This component contains the ISO Object Identifier (OID) to allow identification of the value set from which the value in CWE.1 is obtained.
	 * @param strValueSetVersionID This component carries the version for the value set identified by CWE.15. The version is a date. The date is the date/time that the value set being used was published.
	 * @param strAlternateCodingSystemOID This component contains the ISO Object Identifier (OID) for the coding system or value set named in CWE.6.
	 * @param strAlternateValueSetOID This component contains the ISO Object Identifier (OID) to allow identification of the value set from which the value in CWE.4 is obtained.
	 * @param strAlternateValueSetVersionID This component carries the version for the value set identified by CWE.18. The version is a date.
	 * @param strSecondAlternateCodingSystemOID This component contains the ISO Object Identifier (OID) for the coding system or value set named in CWE.12.
	 * @param strSecondAlternateValueSetOID This component contains the ISO Object Identifier (OID) to allow identification of the value set from which the value in CWE.10 is obtained.
	 * @param strSecondAlternateValueSetVersionID This component carries the version for the value set identified by CWE.21. The version is a date.
	 */
	public void setMessageQueryName(String strIdentifier,String strText,String strNameOfCodingSystem,
			String strAlternateIdentifier,String strAlternateText,String strNameOfAlternateCodingSystem, String strCodingSystemVersionID, String 				strAlternateCodingSystemVersionId,
			String strOriginalText, String  strSecondAlternateIdentifier, String strSecondAlternateText, 
			String strNameofSecondAlternateCodingSystem, String strSecondAlternateCodingSystemVersionID, String strCodingSystemOID, String strValueSetOID,
			String strValueSetVersionID, String strAlternateCodingSystemOID, String strAlternateValueSetOID, String strAlternateValueSetVersionID,
			String  strSecondAlternateCodingSystemOID, String strSecondAlternateValueSetOID, String strSecondAlternateValueSetVersionID)
	{
		CWE cwe  = (CWE)getDataElement(1375);
		cwe.setIdentifier(strIdentifier);
		cwe.setText(strText);
		cwe.setNameOfCodingSystem(strNameOfCodingSystem);
		cwe.setAlternateIdentifier(strAlternateIdentifier);
		cwe.setAlternateText(strAlternateText);
		cwe.setNameOfAlternateCodingSystem(strNameOfAlternateCodingSystem); 
		cwe.setCodingSystemVersionID(strCodingSystemVersionID);
		cwe.setAlternateCodingSystemVersionId(strAlternateCodingSystemVersionId);
		cwe.setOriginalText(strOriginalText);
		cwe.setSecondAlternateIdentifier(strSecondAlternateIdentifier);
		cwe.setSecondAlternateText(strSecondAlternateText);
		cwe.setNameofSecondAlternateCodingSystem(strNameofSecondAlternateCodingSystem);
		cwe.setSecondAlternateCodingSystemVersionID(strSecondAlternateCodingSystemVersionID);
		cwe.setCodingSystemOID(strCodingSystemOID);
		cwe.setValueSetOID(strValueSetOID);
		cwe.setValueSetVersionID(strValueSetVersionID);
		cwe.setAlternateCodingSystemOID(strAlternateCodingSystemOID);
		cwe.setAlternateValueSetOID(strAlternateValueSetOID);
		cwe.setAlternateValueSetVersionID(strAlternateValueSetVersionID);
		cwe.setSecondAlternateCodingSystemOID(strSecondAlternateCodingSystemOID);
		cwe.setSecondAlternateValueSetOID(strSecondAlternateValueSetOID);
		cwe.setSecondAlternateValueSetVersionID(strSecondAlternateValueSetVersionID);
		setMessageQueryName(cwe);
	}

	/**
	 * Set the Message Query Name.
	 * @param messageQueryName Object of Message Query Name.
	 */
	public void setMessageQueryName(CWE messageQueryName)
	{
		setDataType(1375,messageQueryName);
	}

	/**
	 * Set the Query Tag.
	 * This field value the initiating system to identify the query and used to match response messages to the originating query.
	 * @param strQueryTag Use as QueryTag.
	 */
	public void setQueryTag(String strQueryTag) 
	{
		ST queryTag  = (ST)getDataElement(696);
		queryTag.setValue(strQueryTag);
		setQueryTag(queryTag);
	}

	/**
	 * Set the Query Tag.
	 * This field value the initiating system to identify the query and used to match response messages to the originating query.
	 * @param queryTag Object of Query Tag.
	 */
	public void setQueryTag(ST queryTag)
	{
		setDataType(696,queryTag);
	}

	/**
	 * Set the User Parameters.
	 * @param userParameters Use as userParameters.
	 */
	public void setUserParameters(ISingleCollection<IDataType> userParameters)
	{
		setDataType(1435,userParameters);
	}

	/**
	 * 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 objIHL7Stream Object 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';
		int i =-1;
		IDataType objIDataType = null;
		try
		{
			//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;
				boolean isOptional;
				String strFieldName = null;
				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)
							{
								setUnRecognizedField(strEnumSegment, strFieldName, strField);
							}
							else
							{
								addDataType(objItemID,objIDataType);
								objIDataType = null;
							}
							cReadChar = '\0';
							cReadChar = strSegmentStream.charAt(i);
	
							if(objItemID == 703)
							{
								if(cReadChar == objIDelimiter.getFieldSeparator())
								{
									do
									{
										String strSubComponent="";
										objIDataType = objDataElementFactory.getDataElement(iValueTable, iMinLength, iMaxLength,strCLength, strDTName,HL7Constants.HL7_COMPONENT);
										do
										{
											i++;
											strSubComponent=strSubComponent+strSegmentStream.charAt(i);
										}while(strSegmentStream.charAt(i) != '~' && strSegmentStream.charAt(i)!='|' && strSegmentStream.charAt(i)!='\r');
										iRetVal = objIDataType.read(strSubComponent, objIDelimiter);
	
										if(iRetVal == 0)
										{
											if(!isOptional)
											{
												setUnRecognizedField(strEnumSegment, strFieldName,strSubComponent);
											}
											objIDataType = null;
											cReadChar = '\0';
											cReadChar = strSegmentStream.charAt(i);
											break;
										}
										else if(iRetVal == HL7Constants.HL7_FUNCTION_FAIL)
										{
											objIDataType = null;
											setUnRecognizedField(strEnumSegment, strFieldName,strSubComponent);
										}
										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)
										{
											setUnRecognizedField(strEnumSegment, strFieldName, strSubComponent);
										}
										else
										{
											addDataType(objItemID,objIDataType);
											objIDataType = null;
										}
										cReadChar = '\0';
										cReadChar = strSegmentStream.charAt(i);
	
									}while(cReadChar == objIDelimiter.getRepetitionSeparator());
								}
							}						
						}
					}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);
		}
	}

	/**
	 * Serializes HL7 Message objects.
	 * Before serializaton it validates Message as per HL7 v2.5 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 =getDataType(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(itemID == 1435 && index != iSize - 1)
						{
							objIHL7Stream.writeChar(cFSeparator);
						}

						else if(index != iSize - 1)
							objIHL7Stream.writeChar(cRSeparator);
					}
				}
				if(seqno != objItemIDList.size() - 1)
					objIHL7Stream.writeChar(cFSeparator);
			}

			//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
	{
		/*
		 * The field queryTag is conditional not yet implemented for that.
		 */
		try
		{
			super.validate();
			return true;
		}
		catch(FieldMissingException ex)
		{
			throw new InvalidSegmentException(enumSegments.getValue(),ex);
		}
	}
}
