/*******************************************************************************
 *  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.messages;

import java.util.Locale;

import cdac.medinfo.sdk.hl7282.commoninterface.ISegmentSet;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.exceptions.MessageNotSupportedException;
import cdac.medinfo.sdk.hl7282.utils.EnumMessageCode;
import cdac.medinfo.sdk.hl7282.utils.EnumTriggerEvent;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;
import cdac.medinfo.sdk.hl7282.utils.StandardMethods;

/**
 * MessageFactory class provides support for creation and population of messages.
 * It creates and populates supported message according to the available list of Source and Recipient. 
 */
public final class MessageFactory 
{
	

	private static MessageFactory objMessageFactory = null;
	private ISingleCollection<IMessageSource> objIMessageSourceCollection = null;
	private ISingleCollection<IMessageRecepient> objIMessageRecepientCollection = null;
	private LocalExtensionAgent objLocalExtensionAgent = null;

	private MessageFactory()
	{
		objIMessageSourceCollection = new SingleCollection<IMessageSource>();
		objIMessageRecepientCollection = new SingleCollection<IMessageRecepient>();
	}

	public static MessageFactory createInstance()
	{
		if(objMessageFactory == null)
			objMessageFactory = new MessageFactory();

		return objMessageFactory;
	}

	/**
	 * Sets LocalExtensionAgent.
	 * Local Extension Agent provides support for extended messages
	 * @param objAgent instance of Local Extension Agent
	 */
	public void setLocalExtensionAgent(LocalExtensionAgent objAgent)
	{
		this.objLocalExtensionAgent = objAgent;
	}

	/**
	 * Retrieves Local Extension Agent.
	 * @return LocalExtensionAgent - instance of Local Extension Agent
	 */
	public LocalExtensionAgent getLocalExtensionAgent() 
	{
		return objLocalExtensionAgent;
	}

	/**
	 * Adds given Message Source to Message Factory. 
	 * @param objIMessageSource Given Message Source
	 */
	public void addMessageSource(IMessageSource objIMessageSource)
	{
		objIMessageSourceCollection.add(objIMessageSource);
	}

	/**
	 * Adds given Collection of Message Sources to Message Factory.  
	 * @param objIMessageSourceCollection Given Collection of Message Sources
	 */
	public void addMessageSources(ISingleCollection<IMessageSource> objIMessageSourceCollection)
	{
		this.objIMessageSourceCollection = objIMessageSourceCollection;
	}	

	/**
	 *  Retrieves SingleCollection of Message Sources from Message Factory.  
	 * @return ISingleCollection- Given Collection of Message Sources
	 */
	public ISingleCollection<IMessageSource> getMessageSources()
	{
		return	this.objIMessageSourceCollection; 
	}

	/**
	 * Adds given Collection of Message Recipients to Message Factory.
	 * @param objIMessageRecepient Given Message Recipient
	 */
	public void addMessageRecepient(IMessageRecepient objIMessageRecepient)
	{
		this.objIMessageRecepientCollection.add(objIMessageRecepient);
	}

	/**
	 * Adds given Message Recipient to Message Factory.
	 * @param objIMessageRecepientCollection Given Collection of Message Recipients 
	 */
	public void addMessageRecepients(ISingleCollection<IMessageRecepient> objIMessageRecepientCollection) 
	{
		this.objIMessageRecepientCollection = objIMessageRecepientCollection;
	}


	/**
	 * Retrieves Message Recipients from Message Factory.
	 * @return ISingleCollection- Given Collection of Message Recipients 
	 */
	public ISingleCollection<IMessageRecepient> getMessageRecepients() 
	{
		return this.objIMessageRecepientCollection;
	}

	/**
	 * Creates instance of message for which sending is supported through available sources and recipients. 
	 * @param strMessageCode value of Message Code 
	 * @param strTriggerEvent value of Trigger Event
	 * @return Message - instance of Message
	 * @throws MessageNotSupportedException If message is not supported through available Sources and Recipients
	 */
	public Message createSendSupportedMessage(String strMessageCode,String strTriggerEvent)throws MessageNotSupportedException
	{
		Message objMessage = null;

		if(objIMessageRecepientCollection.size()>0 || objIMessageSourceCollection.size()>0)
		{
			if(objLocalExtensionAgent != null)
			{
				if(objLocalExtensionAgent.isMsgLocallyRegistered(strMessageCode, strTriggerEvent) || EnumMessageCode.ACK.getValue().equals(strMessageCode))
					return objMessage = objLocalExtensionAgent.createMessage(strMessageCode, strTriggerEvent);
			}

			EnumMessageCode enumMessageCode = StandardMethods.getEnumMessageCode(strMessageCode);
			EnumTriggerEvent enumTriggerEvent = StandardMethods.getEnumTriggerEvent(strTriggerEvent);

			if(enumMessageCode==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidMessageCode", new Locale(HL7Config.createInstance().getLocale())));
			else if(enumTriggerEvent==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidTriggerEvent", new Locale(HL7Config.createInstance().getLocale())));

			IMessageSource objMsgSource = getSendSupportedSource(enumMessageCode, enumTriggerEvent);
			IMessageRecepient objMsgRecepient = getSendSupportedRecepient(enumMessageCode, enumTriggerEvent);

			if(enumMessageCode==null || enumTriggerEvent==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions","MessageNotSupported",new Locale(HL7Config.createInstance().getLocale())) + strMessageCode + "_" + strTriggerEvent);

			if(objMsgSource != null)
				objMessage = objMsgSource.createMessage(enumMessageCode, enumTriggerEvent);
			else if(objMsgRecepient != null)
				objMessage = objMsgRecepient.createMessage(enumMessageCode, enumTriggerEvent);

			return objMessage;
		}
		else
			throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions","MessageNotSupported",new Locale(HL7Config.createInstance().getLocale())) + strMessageCode + "_" + strTriggerEvent);
	}

	/**
	 * Populates instance of message using provided SegmentSet.
	 * Population checks for support capability of available sources and recipients.
	 * @param strMessageCode value of Message Code 
	 * @param strTriggerEvent value of Trigger Event
	 * @param objISegmentSet instance of SegmentSet
	 * @return Message - instance of Message
	 * @throws MessageNotSupportedException
	 */
	public Message populateReceiveSupportedMessage(String strMessageCode,String strTriggerEvent,ISegmentSet objISegmentSet)throws MessageNotSupportedException
	{
		Message objMessage = null;
		if(objIMessageRecepientCollection.size()>0 || objIMessageSourceCollection.size()>0)
		{
			if(objLocalExtensionAgent != null)
			{
				if(objLocalExtensionAgent.isMsgLocallyRegistered(strMessageCode, strTriggerEvent) || EnumMessageCode.ACK.getValue().equals(strMessageCode))
					return objMessage = objLocalExtensionAgent.populateMessage(strMessageCode, strTriggerEvent, objISegmentSet);
			}

			EnumMessageCode enumMessageCode = StandardMethods.getEnumMessageCode(strMessageCode);
			EnumTriggerEvent enumTriggerEvent = StandardMethods.getEnumTriggerEvent(strTriggerEvent);

			if(enumMessageCode==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidMessageCode", new Locale(HL7Config.createInstance().getLocale())));
			else if(enumTriggerEvent==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidTriggerEvent", new Locale(HL7Config.createInstance().getLocale())));

			IMessageSource objMsgSource = getReceiveSupportedSource(enumMessageCode, enumTriggerEvent);
			IMessageRecepient objMsgRecepient = getReceiveSupportedRecepient(enumMessageCode, enumTriggerEvent);

			if(enumMessageCode==null || enumTriggerEvent==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions","MessageNotSupported",new Locale(HL7Config.createInstance().getLocale())) + strMessageCode + "_" + strTriggerEvent);

			if(objMsgSource != null)
				objMessage = objMsgSource.populateMessage(enumMessageCode, enumTriggerEvent, objISegmentSet);
			else if(objMsgRecepient != null)
				objMessage = objMsgRecepient.populateMessage(enumMessageCode, enumTriggerEvent, objISegmentSet);
			return objMessage; 
		}
		else
			throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions","MessageNotSupported",new Locale(HL7Config.createInstance().getLocale())) + strMessageCode + "_" + strTriggerEvent);
	}

	/**
	 * Verifies whether given message code and trigger event is supported by sources for sending.
	 * It retrieves the Message Source if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageSource instance of Message Source
	 */
	//It seems that this method is returning boolean but returning IMessageSource.
	//In later version of SDK this method should be removed.
	public IMessageSource isSendSupportedBySource(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		return getSendSupportedSource(enumMessageCode,enumTriggerEvent);
	}

	/**
	 * Verifies whether given message code and trigger event is supported by sources for sending.
	 * It retrieves the Message Source if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageSource instance of Message Source
	 */
	public IMessageSource getSendSupportedSource(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		for(IMessageSource objIMessageSource : objIMessageSourceCollection)
		{
			if(objIMessageSource.canSend(enumMessageCode,enumTriggerEvent))
				return objIMessageSource;
		}
		return null;
	}

	/**
	 * Verifies whether given message code and trigger event is supported by recipients for sending.
	 * It retrieves the Message Recipient if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageRecepient instance of Message Recipient
	 */
	//It seems that this method is returning boolean but returning IMessageRecepient.
	//In later version of SDK this method should be removed.
	public IMessageRecepient isSendSupportedByRecepient(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		return getSendSupportedRecepient(enumMessageCode,enumTriggerEvent);
	}
	/**
	 * Verifies whether given message code and trigger event is supported by recipients for sending.
	 * It retrieves the Message Recipient if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageRecepient instance of Message Recipient
	 */
	//It seems that this method is returning boolean but returning IMessageRecepient.
	//In later version of SDK this method should be removed.
	public IMessageRecepient getSendSupportedRecepient(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		for(IMessageRecepient objIMessageRecepient : objIMessageRecepientCollection)
		{
			if(objIMessageRecepient.canSend(enumMessageCode,enumTriggerEvent))
				return objIMessageRecepient;
		}
		return null;
	}

	/**
	 * Verifies whether given message code and trigger event is supported by sources for receiving.
	 * It retrieves the Message Source if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageSource instance of Message Source
	 */
	//It seems that this method is returning boolean but returning IMessageSource.
	//In later version of SDK this method should be removed.
	public IMessageSource isReceiveSupportedBySource(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		return getReceiveSupportedSource(enumMessageCode,enumTriggerEvent);
	}
	/**
	 * Verifies whether given message code and trigger event is supported by sources for receiving.
	 * It retrieves the Message Source if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageSource instance of Message Source
	 */
	public IMessageSource getReceiveSupportedSource(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		for(IMessageSource objIMessageSource : objIMessageSourceCollection)
		{
			if(objIMessageSource.canReceive(enumMessageCode,enumTriggerEvent))
				return objIMessageSource;
		}
		return null;
	}
	/**
	 * Verifies whether given message code and trigger event is supported by recipients for receiving.
	 * It retrieves the Message Recipient if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageRecepient instance of Message Recipient
	 */
	//It seems that this method is returning boolean but returning IMessageRecepient.
	//In later version of SDK this method should be removed.
	public IMessageRecepient isReceiveSupportedByRecepient(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		return getReceiveSupportedRecepient(enumMessageCode,enumTriggerEvent);
	}
	/**
	 * Verifies whether given message code and trigger event is supported by recipients for receiving.
	 * It retrieves the Message Recipient if message is supported else returns null.
	 * @param enumMessageCode value of message code
	 * @param enumTriggerEvent value of trigger event
	 * @return IMessageRecepient instance of Message Recipient
	 */
	public IMessageRecepient getReceiveSupportedRecepient(EnumMessageCode enumMessageCode,EnumTriggerEvent enumTriggerEvent)
	{
		for(IMessageRecepient objIMessageRecepient : objIMessageRecepientCollection)
		{
			if(objIMessageRecepient.canReceive(enumMessageCode,enumTriggerEvent))
				return objIMessageRecepient;
		}
		return null;
	}
	/**
	 * Checks whether the message is supported for sending using available sources.
	 * For ack it checks for available recipients also.
	 * @param messageToSend instance of message
	 * @return true - if supported
	 * 		   false - otherwise 
	 */
	public boolean isMessageSendSupported(Message messageToSend) 
	{
		//check that message is locally extended then it is supported else check through source and recipient.
		if(objLocalExtensionAgent != null && objLocalExtensionAgent.isMsgLocallyRegistered(messageToSend.getMessageCode(),messageToSend.getTriggerEvent()))
			return true;
		else
		{
			EnumMessageCode enumMessageCode = StandardMethods.getEnumMessageCode(messageToSend.getMessageCode());
			EnumTriggerEvent enumTriggerEvent = StandardMethods.getEnumTriggerEvent(messageToSend.getTriggerEvent());

			if(enumMessageCode==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidMessageCode", new Locale(HL7Config.createInstance().getLocale())));
			else if(enumTriggerEvent==null)
				throw new MessageNotSupportedException(ResourceBundleWrapper.getValue("exceptions", "InvalidTriggerEvent", new Locale(HL7Config.createInstance().getLocale())));

			if(((objMessageFactory.getSendSupportedSource(enumMessageCode,enumTriggerEvent)) != null) || (objMessageFactory.getSendSupportedRecepient(enumMessageCode,enumTriggerEvent) != null))
				return true;
		}
		return false;
	}
}
