package cdac.medinfo.sdk.hl7282.hl7net.auxiliaryprotocol.sequence;

import java.util.Iterator;
import java.util.Locale;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.logging.Level;

import cdac.medinfo.sdk.hl7282.baselibs.NM;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSA;
import cdac.medinfo.sdk.hl7282.baselibs.segments.MSH;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegment;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegmentSet;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidMessageException;
import cdac.medinfo.sdk.hl7282.exceptions.InvalidSequenceException;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ISequenceAgent;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageStructure;
import cdac.medinfo.sdk.hl7282.utils.EnumSegments;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;

/**
 * This class keeps the records and provides the necessary Sequence Number to the Messages.
 * Checks and verifies whether the Sequence Number of Messages are correctly placed or not.
 */
public class ReceivingSequenceAgent implements ISequenceAgent
{

	/** Instance of Receiving Sequence Agent **/
	private static ReceivingSequenceAgent objReceivingSequenceAgent = null;

	/** Queue to maintain Sequence Number of Receiving System **/
	private Queue<Integer> objQueue;

	/** Queue size **/
	private int intQueueLength = 1;

	/** Initial(first) value of Queue **/
	private int intCurrentSeqNuminQueue = 1;

	/** Keeps the current sequence number of received message **/
	private int intCurrentSequenceNo;

	/** Temporarily keeps the current sequence number of received message **/
	private int intTempCurrentSequenceNo = 0;

	/** To check the sequence numbering **/
	private int intCheckResSeqNo;

	/** Boolean variable to tell about sequence numbering is start or not **/
	public boolean bStartSequencing = false;

	/**
	 * Instantiates a new Receiving Sequence Agent.
	 */
	private ReceivingSequenceAgent()
	{
		objQueue = new PriorityQueue<Integer>(intQueueLength);
	}

	/**
	 * Create new instance or old one depends on existing of instance variable.
	 * @return ReceivingSequenceAgent - instance of Receiving Sequence Agent.
	 */
	public static ReceivingSequenceAgent createInstance()
	{
		if(objReceivingSequenceAgent == null)
			objReceivingSequenceAgent = new ReceivingSequenceAgent();
		return objReceivingSequenceAgent;
	}

	/**
	 * Sets the initial value in Queue.
	 * @param value - initial value of Queue.
	 */
	public void setInitialQueueValue(int value)
	{
		intCurrentSeqNuminQueue = value;
	}

	/**
	 * Sets Queue length.
	 * @param length - length of Queue.
	 */
	public void setQueueLength(int length) 
	{
		//if length of Queue set zero or negative by user, then SequenceAgent sets default 1 as a Queue length.
		if(length > 0)
		{
			intQueueLength = length;
		}
		else
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_InvalidSequenceNumber",new Locale(HL7Config.createInstance().getLocale()));
				objHL7Logger.log(Level.WARNING,strLogMessage);
			}
		}		
	}

	/**
	 * Starts the Sequence Numbering
	 * @param flag - To start set true.
	 */
	public void setStartSequencing(boolean flag)
	{
		bStartSequencing = flag;
	}

	/**
	 * Ends the Sequence Numbering
	 * @param flag - To end set false.
	 */
	public void setEndSequencing(boolean flag)
	{
		bStartSequencing = flag;
	}

	/**
	 * Process and populate the sequence numbering.
	 * @param objSegmentSet - Segment set to which populate.
	 */
	public void processSend(ISegmentSet objSegmentSet) throws InvalidMessageException
	{
		MSH objMSHSegment = (MSH)objSegmentSet.getSegment(0);
		ISingleCollection<ISegment> objSegmentCollection = objSegmentSet.getList();
		MSA objMSA = null;
		NM nmMessageSeqNo = objMSHSegment.getSequenceNumber();
		String strMessageSeqNo = null;
		
		//for sequenceLogging
		String strSequenceLog = null;
		
		if(nmMessageSeqNo != null)
			strMessageSeqNo = (String)nmMessageSeqNo.getValue();

		if(objSegmentCollection!= null && objSegmentCollection.size()>0)
		{
			int iSegSetSize = objSegmentCollection.size();
			for(int iRead=0;iRead<iSegSetSize;iRead++)
			{
				ISegment objSegment = null;
				objSegment = objSegmentCollection.elementAt(iRead);
				if(objSegment == null)
					throw new InvalidMessageException(this.getClass().getSimpleName());
				EnumSegments enumSegments = (EnumSegments) objSegment.getName();
				switch(enumSegments)
				{
				case HL7_SEG_MSA:
					objMSA = (MSA)objSegment;
					break;
				}
			}
		}

		Iterator<Integer> objIterator = objQueue.iterator();
		while(objIterator.hasNext())
		{
			intCurrentSeqNuminQueue = objIterator.next();
		}

		//If receiving system send ACK
		if(objMSA != null && objMSHSegment.getMessageType().getMessageStructure().getValue().equals(EnumMessageStructure.ACK.getValue()))
		{
			//If system send message to other system with sequencing
			if(bStartSequencing)
			{
				if(intCurrentSequenceNo == 0)
				{
					if(intTempCurrentSequenceNo == 0)
					{
						objMSA.setExpectedSequenceNumber("" + -1);
					}
					else
					{
						intTempCurrentSequenceNo++;
						objMSA.setExpectedSequenceNumber("" + intTempCurrentSequenceNo);
					}
				}
				else if(intCurrentSequenceNo == -1)
				{
					objMSA.setExpectedSequenceNumber("" + -1);
				}
				else
				{
					intCurrentSequenceNo++;
					objMSA.setExpectedSequenceNumber("" + intCurrentSequenceNo);
				}
			}
			//If system send message to other system without sequencing
			else
			{
				if(intCurrentSequenceNo != 0)
				{
					if(Integer.parseInt(strMessageSeqNo) != intCurrentSequenceNo)
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							HL7Logger objHL7Logger = HL7Logger.getLogger();
							String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_InvalidSequenceNumber",new Locale(HL7Config.createInstance().getLocale()));
							objHL7Logger.log(Level.WARNING,strLogMessage);
						}				
					}
				}
			}
		}		
		//If initiating system send Response message
		else 
		{
			//If system send message to other system with sequencing
			if(bStartSequencing)
			{
				//for the first time system tries to send response message to other system
				if(objQueue.size() == 0)
				{
					if(strMessageSeqNo == null || Integer.parseInt(strMessageSeqNo) < 0)
					{
						objQueue.add(intCurrentSeqNuminQueue);
						objMSHSegment.setSequenceNumber("" + intCurrentSeqNuminQueue);
					}
					else if(Integer.parseInt(strMessageSeqNo) == 0 || Integer.parseInt(strMessageSeqNo) == -1)
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							HL7Logger objHL7Logger = HL7Logger.getLogger();
							String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_Resynchronization",new Locale(HL7Config.createInstance().getLocale()));
							objHL7Logger.log(Level.WARNING,strLogMessage);
						}
					}
					else
					{
						intCurrentSeqNuminQueue = Integer.parseInt(strMessageSeqNo);
						objQueue.add(intCurrentSeqNuminQueue);
						objMSHSegment.setSequenceNumber("" + intCurrentSeqNuminQueue);
					}
				}
				//If already system send any response message to other system
				else
				{
					if(Integer.parseInt(strMessageSeqNo) == 0 || Integer.parseInt(strMessageSeqNo) == -1)
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							HL7Logger objHL7Logger = HL7Logger.getLogger();
							String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_Resynchronization",new Locale(HL7Config.createInstance().getLocale()));
							objHL7Logger.log(Level.WARNING,strLogMessage);
						}
					}
					else
					{
						objQueue.add(intCurrentSeqNuminQueue + 1);
						intCurrentSeqNuminQueue++;
						objMSHSegment.setSequenceNumber("" + intCurrentSeqNuminQueue);
					}
				}
			}
			//If system send message to other system without sequencing
			else
			{
					if(strMessageSeqNo != null)
					{
						if(objQueue.size() != 0)
						{
							if(Integer.parseInt(strMessageSeqNo) != intCurrentSeqNuminQueue)
							{
								if(HL7Config.createInstance().isLoggingOn())
								{
									HL7Logger objHL7Logger = HL7Logger.getLogger();
									String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_InvalidSequenceNumber",new Locale(HL7Config.createInstance().getLocale()));
									objHL7Logger.log(Level.WARNING,strLogMessage);
								}
								throw new InvalidSequenceException(objSegmentSet.toString());
							}
						}
					}
					else //if system tries to send message without setting any value
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							HL7Logger objHL7Logger = HL7Logger.getLogger();
							String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_InvalidSequenceNumber",new Locale(HL7Config.createInstance().getLocale()));
							objHL7Logger.log(Level.WARNING,strLogMessage);
						}
						throw new InvalidSequenceException(objSegmentSet.toString());
					}
				
			}
		}
		
		//Log for the Sequence Number
		if(objMSHSegment.getMessageType().getMessageStructure().getValue().equals(EnumMessageStructure.ACK.getValue()))
		{
			if(objMSA != null)
				strSequenceLog = objMSA.getExpectedSequenceNumber().toString();
		}
		else
			strSequenceLog = objMSHSegment.getSequenceNumber().toString();

		if(HL7Config.createInstance().isLoggingOn())
		{
			String strSendingMessage = ResourceBundleWrapper.getValue("exceptions", "Sequence_SendingMessage",new Locale(HL7Config.createInstance().getLocale())) + strSequenceLog;
			HL7Logger objHLogger = HL7Logger.getLogger();
			objHLogger.log(strSendingMessage);
		}
	}

	/**
	 * Invoked when system receive message.
	 * @param objSegmentSet - Segment set filled with Sequence Number.
	 * @throws InvalidMessageException - Thrown When Message Populated is Invalid.
	 */
	public void processReceive(ISegmentSet objSegmentSet) throws InvalidMessageException 
	{
		MSH objMSHSegment = (MSH)objSegmentSet.getSegment(0);
		ISingleCollection<ISegment> objSegmentCollection = objSegmentSet.getList();
		MSA objMSA = null;
		String strExpectedSeqNo = null;
		NM nmMessageSeqNo = objMSHSegment.getSequenceNumber();
		String strMessageSeqNo = null;
		
		//for sequenceLogging
		String strSequenceLog = null;
		
		if(nmMessageSeqNo != null)
			strMessageSeqNo = (String)nmMessageSeqNo.getValue();

		if(objSegmentCollection!= null && objSegmentCollection.size()>0)
		{
			int iSegSetSize = objSegmentCollection.size();
			for(int iRead=0;iRead<iSegSetSize;iRead++)
			{
				ISegment objSegment = null;
				objSegment = objSegmentCollection.elementAt(iRead);
				if(objSegment == null)
					throw new InvalidMessageException(this.getClass().getSimpleName());
				EnumSegments enumSegments = (EnumSegments) objSegment.getName();
				switch(enumSegments)
				{
				case HL7_SEG_MSA:
					objMSA = (MSA)objSegment;
					break;
				}
			}
		}

		Iterator<Integer> objIterator = objQueue.iterator();
		while(objIterator.hasNext())
		{
			intCheckResSeqNo = objIterator.next();
		}


		//If Initiating system receive ACK
		if(objMSHSegment.getMessageType().getMessageStructure().getValue().equals(EnumMessageStructure.ACK.getValue()))
		{
			strExpectedSeqNo = (String)objMSA.getExpectedSequenceNumber().getValue();

			//If system receive message from other system with sequencing
			if(bStartSequencing)
			{
				if(Integer.parseInt(strExpectedSeqNo) == -1)
				{
					objQueue.clear();
				}
				else
				{
					if(intCheckResSeqNo + 1 != Integer.parseInt(strExpectedSeqNo))
					{
						if(HL7Config.createInstance().isLoggingOn())
						{
							HL7Logger objHL7Logger = HL7Logger.getLogger();
							String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_InvalidSequenceNumber",new Locale(HL7Config.createInstance().getLocale()));
							objHL7Logger.log(Level.WARNING,strLogMessage);
						}
					}
				}
			}
			else //If system receive message from other system without sequencing
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_NotSetSequencenumber",new Locale(HL7Config.createInstance().getLocale()));
					objHL7Logger.log(Level.WARNING,strLogMessage);
				}
			}			
		}
		else
		{		
			//If system receive message from other system with sequencing
			if(bStartSequencing)
			{
				if(strMessageSeqNo != null)
				{
					if(Integer.parseInt(strMessageSeqNo) != 0 && Integer.parseInt(strMessageSeqNo) != -1)
					{
						intTempCurrentSequenceNo = Integer.parseInt(strMessageSeqNo);
					}
					intCurrentSequenceNo = Integer.parseInt(strMessageSeqNo);
				}
			}
			else //If system receive message from other system without sequencing
			{
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					String strLogMessage = ResourceBundleWrapper.getValue("warnings","Sequence_NotSetSequencenumber",new Locale(HL7Config.createInstance().getLocale()));
					objHL7Logger.log(Level.WARNING,strLogMessage);
				}
			}
		}
		
		//Log for the Sequence Number
		if(objMSHSegment.getMessageType().getMessageStructure().getValue().equals(EnumMessageStructure.ACK.getValue()))
		{
			if(objMSA != null)
				strSequenceLog = objMSA.getExpectedSequenceNumber().toString();
		}
		else
			strSequenceLog = objMSHSegment.getSequenceNumber().toString();

		if(HL7Config.createInstance().isLoggingOn())
		{
			String strSendingMessage = ResourceBundleWrapper.getValue("exceptions", "Sequence_RecievedMessage",new Locale(HL7Config.createInstance().getLocale())) + strSequenceLog;
			HL7Logger objHLogger = HL7Logger.getLogger();
			objHLogger.log(strSendingMessage);
		}
	}
}
