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


import cdac.medinfo.sdk.hl7282.commoninterface.ICollectionIterator;
import cdac.medinfo.sdk.hl7282.commoninterface.ISegmentKey;
import cdac.medinfo.sdk.hl7282.utils.EnumMapModifyMode;
import cdac.medinfo.sdk.hl7282.utils.IMap;
import cdac.medinfo.sdk.hl7282.utils.IPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.ISegmentItem;
import cdac.medinfo.sdk.hl7282.utils.PairedCollection;

/**
 * This class represents a map for Segment Items which represent structure of a Message.
 * Index for map starts with 1.
 */
public final class Map implements IMap
{
	

	private IPairedCollection<Integer,ISegmentItem> objSegmentStructureList = null;
	private int segIndex = 1;
	private boolean isLocked = false;

	/**
	 * Instantiates Message Map
	 */
	public Map()
	{
		this.objSegmentStructureList = new PairedCollection<Integer, ISegmentItem>();
	}

	/**
	 * Retrieves SegmentItem from given index.
	 * Map index starts with 1.
	 * @return IPairedCollection<Integer,SegmentItem> segment structure list of this message
	 */
	public ISegmentItem getSegmentItem(int index)
	{
		if(objSegmentStructureList.contains(index))
			return objSegmentStructureList.get(index);

		return null;
	}

	/**
	 * Adds a Segment Item to Map.
	 * Sequence number should start with 1 always and skipping of sequence is not allowed
	 * @param iSequence value of sequence number
	 * @param objSegmentItem Specified Sequence Item 
	 */
	public void addSegmentItem(ISegmentItem objSegmentItem)
	{
		if(this.isLocked)
		{
			setLock(false);
		}
		
		objSegmentStructureList.put(segIndex,objSegmentItem);
		segIndex++;
	}

	/**
	 * Sets lock on IMessageMap.
	 * If lock is set then new SegmentItem can not be added in IMessageMap
	 * @param bLock value of lock
	 */
	public void setLock(boolean bLock)
	{
		if(this.isLocked && !bLock)
		this.isLocked = bLock;
	}

	/**
	 * Retrieves current status of lock for IMessageMap
	 * @return boolean - status of lock
	 */
	public boolean getLock()
	{
		return this.isLocked;
	}

	/**
	 * Adds a Segment Item to Map.
	 * Sequence number should start with 1 always and skipping of sequence is not allowed
	 * @param iSequence value of sequence number
	 * @param objSegmentItem Specified Sequence Item 
	 */
	public void addSegmentItem(ISegmentItem objSegmentItem,int index,EnumMapModifyMode enumModifyMode)
	{
		if(this.isLocked)
		{
			setLock(false);
		}
		
		switch(enumModifyMode)
		{
		case CURRENT:
		{
			objSegmentStructureList.put(segIndex,objSegmentItem);
			segIndex++;
		}
		break;
		case AFTER:
		{
			IPairedCollection<Integer,ISegmentItem> objTempSegmentStrucList = new PairedCollection<Integer, ISegmentItem>();
			int i = 1;
			while(i < segIndex && i <= index)
			{
				objTempSegmentStrucList.put(i,objSegmentStructureList.get(i));
				i++;
			}

			objTempSegmentStrucList.put(i,objSegmentItem);
			while(i < segIndex)
			{
				objTempSegmentStrucList.put(i + 1, objSegmentStructureList.get(i));
				i++;
			}
			this.objSegmentStructureList = objTempSegmentStrucList;
			segIndex++;
		}
		break;
		case REPLACE:
		{
			if(objSegmentStructureList.contains(index))
				objSegmentStructureList.put(index,objSegmentItem);
		}
		break;
		}
	}

	/**
	 * This method returns the size of Message Map
	 * @return int - number of SegmentItem available in Map 
	 */
	public int size()
	{
		if(objSegmentStructureList != null)
			return objSegmentStructureList.size();

		return 0;
	}

	/**
	 * Verifies whether Segment or Group is member of HL7 Message or not according to Message Structure. 
	 * @param enumSearchSegment Object of ISegmentKey provides name of Segment or Group.
	 * @return True - if Given Segment or Group is part of HL7 message Structure else returns false.
	 */
	public boolean isMember(ISegmentKey enumSearchSegment)
	{
		ICollectionIterator<ISegmentItem> objSegmentItemIterator = this.objSegmentStructureList.iterator();
		while(objSegmentItemIterator.hasNext())
		{
			ISegmentItem objSegmentItem = objSegmentItemIterator.next();
			ISegmentKey enumSegmentKey = objSegmentItem.getName();

			// if SearchName is group 
			if(objSegmentItem.isGroup())
			{
				//if group name is same as SearchName
				if(enumSegmentKey.getValue().equals(enumSearchSegment.getValue()))
					return true;
				else
				{
					IMap objMessageMap = objSegmentItem.getGroupItemMessageMap();
					if(isMember(enumSearchSegment,objMessageMap))
						return true;
					else
						return false;
				}
			}
			else                       //if SearchName is segment
			{
				//is segment name is same as SearchName
				if(enumSegmentKey.getValue().equals(enumSearchSegment.getValue()))
					return true;
			}
		}
		return false;
	}

	/**
	 * Verifies whether Segment or Group is member of HL7 Message or not according to Message Structure. 
	 * @param enumSearchSegment Object of ISegmentKey provides name of Segment or Group.
	 * @param objMessageMap Object of IMessageMap.
	 * @return True - if Given Segment or Group is part of HL7 message Structur else returns false.
	 */
	private boolean isMember(ISegmentKey enumSearchSegment, IMap objMessageMap)
	{
		int i = 1;
		int iSegStructureNumber = objMessageMap.size();
		while(i <= iSegStructureNumber)
		{
			ISegmentItem objSegmentItemOfGroup = objMessageMap.getSegmentItem(i);
			ISegmentKey objSegmentKeyOfGroupSeg = objSegmentItemOfGroup.getName();

			if(objSegmentItemOfGroup.isGroup())
			{
				if(objSegmentKeyOfGroupSeg.getValue().equals(enumSearchSegment.getValue()))
					return true;
				else
				{
					IMap objMessageMapInner = objSegmentItemOfGroup.getGroupItemMessageMap();
					isMember(enumSearchSegment, objMessageMapInner);
				}
			}
			else
			{
				if(objSegmentKeyOfGroupSeg.getValue().equals(enumSearchSegment.getValue()))
					return true;
			}
			i++;
		}
		return false;
	}

	/**
	 * Verifies whether Segment or Group is present at the first level of HL7 message structure or not.
	 * @param enumSearchSegment Object of ISegmentKey provides name of Segment or Group.
	 * @return True - if Given Segment or Group is present at first level of HL7 message Structur else returns false.
	 */
	public boolean isFirstLevelMember(ISegmentKey enumSearchSegment)
	{
		ICollectionIterator<ISegmentItem> objSegmentItemIterator = this.objSegmentStructureList.iterator();
		while(objSegmentItemIterator.hasNext())
		{
			ISegmentItem objSegmentItem = objSegmentItemIterator.next();
			ISegmentKey enumSegmentKey = objSegmentItem.getName();

			if(enumSegmentKey.getValue().equals(enumSearchSegment.getValue()))
			{
				return true;
			}
		}
		return false;
	}

	/**
	 * Generates clone of Map
	 * @return IMessageMap - instance of Map
	 */
	public IMap clone()
	{
		IMap objNewMessageMap = new Map();
		int i = 1;
		while(i <= size())
		{
			objNewMessageMap.addSegmentItem(objSegmentStructureList.get(i));
			i++;
		}
		objNewMessageMap.setLock(true);
		return objNewMessageMap;
	}
	
	/**
	 * Retrieves Map of specified Group name
	 * @param enumGroupName Object of ISegmentKey provides name of Group.
	 * @return IMessageMap Object of IMessageMap.
	 */
	public IMap getGroupMap(ISegmentKey enumGroupName)
	{
		for(ISegmentItem objSegmentItem : objSegmentStructureList)
		{
			ISegmentKey enumSegmentKey = objSegmentItem.getName();
			if(enumSegmentKey.equals(enumGroupName))
			{
				return objSegmentItem.getGroupItemMessageMap();
			}
		}			
		return null;
	}
}