#include <crtdbg.h>
#include "PropertiesInterface.h"
#include "GroupPropertyInternal.h"
#include "Log.h"
#include "IntPropertyInternal.h"
#include "EnumPropertyInternal.h"
#include "SIStoragePropertyInternal.h"
#include "SIShadowPropertyInternal.h"
#include "NDTSError.h"

SCO_API NDTS::SCO::GroupPropertyInternal::GroupPropertyInternal(char *pShortName, char *pLongName, int flags) : PropertyInternal(pShortName, pLongName, flags)
{
	this->inIntGroupChange = false;
	this->inSIGroupChange = false;
	isArray = false;
	pGroupHooksObject = 0;	
	isFlatPropertyListConstructed = false;
}

namespace NDTS
{
	namespace SCO
	{
	SCO_API GroupPropertyInternal::~GroupPropertyInternal(void)
	{
		for(vector<PropertyInternal*>::iterator  it = allPropertiesVector.begin(); it != allPropertiesVector.end();  it++)
		{
			delete *it;
		}
		
	}

	SCO_API void GroupPropertyInternal::initComplete()
	{
		PropertyInternal::initComplete();

		// The values are applied in a change group at the top level
		// to sort out any dependencies, e.g. isScanning must be set first.
		static bool nested = false;
		bool isTopLevelGroup = false;
		if (!nested)
		{
			isTopLevelGroup = true;
			nested = true;
			beginGroupChange();
		}

		for(vector<PropertyInternal*>::iterator  it = allPropertiesVector.begin(); it != allPropertiesVector.end();  it++)
		{
			(*it)->initComplete();
		}
		if (isTopLevelGroup)
			commitGroupChange();
	}

 
	SCO_API void GroupPropertyInternal::addProperty(PropertyInternal *pProperty)
	{
		_ASSERTE(!isInitComplete);
		allPropertiesVector.push_back(pProperty);
		IntPropertyInternal *pIntProp = dynamic_cast<IntPropertyInternal *>(pProperty);
		// The visibleProperties only contains ints that are not shadowed
		if (!pIntProp || pIntProp->isVisible)
		{
			visiblePropertiesVector.push_back(pProperty);
		}
		pProperty->pParent = this;

		// Build a list of sub groups as this makes some operations more efficient
		GroupPropertyInternal *pGroupProp = dynamic_cast<GroupPropertyInternal*>(pProperty);
		if (pGroupProp)
			subGroups.push_back(pGroupProp);
	}
 

	SCO_API int GroupPropertyInternal::getNumberOfProperties()
	{
		return (int)visiblePropertiesVector.size();
	}

	SCO_API Property *GroupPropertyInternal::getPropertyByNumber(int index)
	{
		if (index < 0 || index >= (int)visiblePropertiesVector.size())
		{
			pLog->addGeneralErrorMessage("getPropertyByNumber: Property index %d is out of range", index);
			return visiblePropertiesVector[0];
		}

		return visiblePropertiesVector[index];
	}

	// Finds the property in this group or sub group and returns null with no error message if not found
	SCO_API Property *GroupPropertyInternal::getPropertyByNameInternal(const char *pShortName)
	{
		// Don't allow a search if this group is an array
		if (this->isArray)
		{
			NDTS_LogLine(NDTS_LLWarning, "getPropertyByName should not be called on an array - group name is %s", this->getShortName());
			return 0;
		}
		// Search this group
		for(vector<PropertyInternal*>::iterator  it = visiblePropertiesVector.begin(); it != visiblePropertiesVector.end();  it++)
		{

			if (!strcmp((*it)->getShortName(), pShortName))
				return *it;
		}

		// Not found - search our sub groups (recursively)
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			// Don't search sub groups that are arrays
			if (!(*it)->isArray)
			{
				Property *pResult = (*it)->getPropertyByNameInternal(pShortName);
				if (pResult)
					return pResult;
			}
		}

		// Still not found - return null
		// Don't log here because we may be doing a recursive search into a sub group that hasn't got the property
		//NDTS_LogLine(NDTS_LLWarning, "getPropertyByNameInternal string %s not found", pShortName);
		return 0;
	}

	// Finds the property in this group or sub group.  If not found, return the first property in the group and log an error
	SCO_API Property *GroupPropertyInternal::getPropertyByName(const char *pShortName)
	{
		Property *pResult = getPropertyByNameInternal(pShortName);
		if (pResult)
			return pResult;

		pLog->addGeneralErrorMessage("getPropertyByName: Property %s not found", pShortName);
		//return visiblePropertiesVector[0];
		return 0;
	}

	SCO_API void GroupPropertyInternal::beginGroupChange()
	{
		// Apply the change to our sub-groups
		inIntGroupChange = true;
		inSIGroupChange = true;
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			(*it)->beginGroupChange();
		}

	}

	int GroupPropertyInternal::getNumberOfUncommittedChanges()
	{
		int count = 0;
		for(vector<PropertyInternal*>::iterator  it = allPropertiesVector.begin(); it != allPropertiesVector.end();  it++)
		{
			IntPropertyInternal *pIntProp = dynamic_cast<IntPropertyInternal*>(*it);
			if (pIntProp && pIntProp->getHaveUncommittedChange())
				count++;
			// Count both the SI and Int changes so that e.g. setting rep rate to zero
			// the clamp happens in the Int property rather than clamping to MAX_INT in the SI stage of the commit
			// and then to the actual max in the Int commit.
			SIShadowPropertyInternal *pSIProp = dynamic_cast<SIShadowPropertyInternal*>(*it);
			if (pSIProp && pSIProp->getHaveUncommittedChange())
				count++;
			GroupPropertyInternal *pGroupProp = dynamic_cast<GroupPropertyInternal*>(*it);
			if (pGroupProp)
				count += pGroupProp->getNumberOfUncommittedChanges();
		}
		return count;
	}

	SCO_API void GroupPropertyInternal::commitGroupChange()
	{

		if (getNumberOfUncommittedChanges() < 2)
		{
			// If one or no properties have changed, apply the change directly
			// This gets round the problem where IW changes a single property that needs clamping and does
			// a group commit, resulting in another property getting clamped, e.g. set range > max and rep rate incorrectly reduced 
			setCommitGroupChangeState(GSTClearSIGroupChange);
			setCommitGroupChangeState(GSTClearIntGroupChange);
			setCommitGroupChangeState(GSTSIShadowProperty);
			setCommitGroupChangeState(GSTCommitRemainingInts);
		}
		else
		{
			// Proper group change where we step through the group change states in the correct order
			for (int i=0; i < NumGroupChangeStates; i++)
			{
				setCommitGroupChangeState((GroupChangeState)(i));
			}
		}
	}

	void GroupPropertyInternal::setCommitGroupChangeState(enum GroupChangeState state)
	{
		// At each state change, the sub-groups are processed recursively before this group.
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			(*it)->setCommitGroupChangeState(state);
		}

		switch(state)
		{
		case GSTClearSIGroupChange:
			// Set the inGroupChange back to false so that int value changes from now on are written directly back to the SDK
			inSIGroupChange = false;
			break;
		case GSTSIShadowProperty:
			// The values from the SIStoragePropertyInternal are written immediately, they ignore the isInGroupChange
			// Their min and max never change and so they can be written as they are sent from the app rather than in a group commit.
			// they are therefore already set before any SIShadowProp values are applied.
			// The values from SIShadowProeprty are applied in the first stage of a commitGroupChanges.  
			// These will cause uncommitted changes on thier shadowed IntPropertyInternal values
			for(vector<PropertyInternal*>::iterator  it = allPropertiesVector.begin(); it != allPropertiesVector.end();  it++)
			{
				SIShadowPropertyInternal *pProp = dynamic_cast<SIShadowPropertyInternal*>(*it);
				if (pProp && pProp->getHaveUncommittedChange())
				{
					pProp->setSIValue(pProp->getUncommittedSIValue());
				}
			}
			break;
		case GSTClearIntGroupChange:
			// Set the inGroupChange back to false so that int value changes from now on are written directly back to the SDK
			inIntGroupChange = false;
			break;
		case GSTRunHook:
			// The preCommit hook is called so that properties in the commit list may be written or adjusted to 
			// avoid bounds check or other failure later on.
			if (pGroupHooksObject)
				pGroupHooksObject->preCommit();
			break;
		case GSTCommitRemainingInts:
			// Commit any outstanding int properties that have not been committed explicitly by the hook
			for(vector<PropertyInternal*>::iterator  it = allPropertiesVector.begin(); it != allPropertiesVector.end();  it++)
			{
				IntPropertyInternal *pProp = dynamic_cast<IntPropertyInternal*>(*it);
				if (pProp && pProp->getHaveUncommittedChange())
				{
					pProp->setValue(pProp->getUncommittedValue());
				}
			}
			break;
		default:
			_ASSERTE(0);
			break;
		}
	}

	bool GroupPropertyInternal::isInIntGroupChange()
	{
		return inIntGroupChange;
	}

	bool GroupPropertyInternal::isInSIGroupChange()
	{
		return inSIGroupChange;
	}


	// Property change list - done on a per group basis
	void GroupPropertyInternal::addChangeItemToGroup(Property *pProperty, ChangeTypeFlags changeFlag)
	{
		_ASSERTE(isInitComplete);

		// If the property is already in the change list, or in the new flag
		for(vector<PropertyChangeItem>::iterator  it = changeList.begin(); it != changeList.end();  it++)
		{
			if (it->pPropertyHandle == pProperty)
			{
				int newFlags = it->changeTypeFlags;
				newFlags |= changeFlag;
				it->changeTypeFlags = (ChangeTypeFlags)newFlags;
				return;
			}

		}
		// Property not already in the list 
		PropertyChangeItem pc;
		pc.changeTypeFlags = changeFlag;
		pc.pPropertyHandle = pProperty;
		changeList.push_back(pc);
	}

	PropertyChangeItem *GroupPropertyInternal::getChangeItemByIndex(int index)
	{
		_ASSERTE(isInitComplete);
		_ASSERTE(index >= 0);
		int currentGroupIndex = index;
		// Items are ordered with our entries first, followed by each sub group
		if (currentGroupIndex < (int)changeList.size())
			return &changeList[index];

		currentGroupIndex -= (int)changeList.size();
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			if (currentGroupIndex < (*it)->getNumberOfChangeListEntries())
				return (*it)->getChangeItemByIndex(currentGroupIndex);
			currentGroupIndex -= (*it)->getNumberOfChangeListEntries();
		}
		// Should have always found the entry by now.
		_ASSERTE(0);
		return 0;
	}

	void GroupPropertyInternal::clearChangeItemLog()
	{
		_ASSERTE(isInitComplete);
		changeList.clear();
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			(*it)->clearChangeItemLog();
		}
	}

	int GroupPropertyInternal::getNumberOfChangeListEntries()
	{	
		_ASSERTE(isInitComplete);
		int total = (int)changeList.size();
		for(vector<GroupPropertyInternal*>::iterator  it = subGroups.begin(); it != subGroups.end();  it++)
		{
			total += (*it)->getNumberOfChangeListEntries();
		}
		return total;
	}

	void GroupPropertyInternal::setGroupHooks(GroupHooks *pGroupHooksObject)
	{
		this->pGroupHooksObject = pGroupHooksObject;
	}

	bool GroupPropertyInternal::isOkToSet(PropertyInternal *pProp)
	{
		if (pGroupHooksObject)
			return pGroupHooksObject->isOkToSet(pProp);
		if (pParent)
			return pParent->isOkToSet(pProp);
		return true;
	}

	void GroupPropertyInternal::postSetUpdate(IntPropertyInternal *pProp)
	{
		if (pGroupHooksObject)
			pGroupHooksObject->postSetUpdate(pProp);
		if (pParent)
			pParent->postSetUpdate(pProp);
	}

	void GroupPropertyInternal::postEventUpdate(EventPropertyInternal *pProp)
	{
		if (pGroupHooksObject)
			pGroupHooksObject->postEventUpdate(pProp);
		if (pParent)
			pParent->postEventUpdate(pProp);
	}

	bool GroupPropertyInternal::getIsArray()
	{
		return isArray;
	}

    // ---------------- new add on 28 Aug, 2008 ---------------------------------------
	void GroupPropertyInternal::buildFlatPropertyByNumber()
	{
		addGroupToFlatPropertyByNumber(this);
		isFlatPropertyListConstructed = true;
	}

	//this function apply to all group properties include card, step, gate groups, so it is a common property
	void GroupPropertyInternal::addGroupToFlatPropertyByNumber(GroupProperty *pGroup)
	{
		for (int i=0; i < pGroup->getNumberOfProperties(); i++)
		{
			IntProperty *pIntProp = pGroup->getProperty<IntProperty>(i);
			EnumProperty *pEnumProp = pGroup->getProperty<EnumProperty>(i);
			SIProperty *pSIProp = pGroup->getProperty<SIProperty>(i);
			EventProperty *pEventProp = pGroup->getProperty<EventProperty>(i);
			GroupProperty *pGroupProp = pGroup->getProperty<GroupProperty>(i);
			if (pIntProp && !pEnumProp)
				flatIntPropertyVector.push_back(pIntProp);
			else if(pEnumProp)
				flatEnumPropertyVector.push_back(pEnumProp);
			else if(pSIProp)
				flatSIPropertyVector.push_back(pSIProp);
			else if(pEventProp)
				flatEventPropertyVector.push_back(pEventProp);
			else if (pGroupProp && !pGroupProp->getIsArray())
				addGroupToFlatPropertyByNumber(pGroupProp);
		}
	}

	////SCO_API is because called from outside of program
	//SCO_API IntProperty *GroupPropertyInternal::getFlatIntPropertyByNumber(int index)
	//{
	//	if (!isFlatPropertyListConstructed)
	//		buildFlatPropertyByNumber();

	//	return flatIntPropertyVector[index];
	//}
   
	SCO_API int GroupPropertyInternal::getFlatNumberOfIntProperties()
	{
		if (!isFlatPropertyListConstructed)
			buildFlatPropertyByNumber();

		return static_cast<int>(flatIntPropertyVector.size());
	}

	SCO_API int GroupPropertyInternal::getFlatNumberOfEnumProperties()
	{
		if (!isFlatPropertyListConstructed)
			buildFlatPropertyByNumber();
 
		return static_cast<int>(flatEnumPropertyVector.size());
	}

	SCO_API int GroupPropertyInternal::getFlatNumberOfSIProperties()
	{
		if (!isFlatPropertyListConstructed)
			buildFlatPropertyByNumber();

		return static_cast<int>(flatSIPropertyVector.size());
	}

	SCO_API int GroupPropertyInternal::getFlatNumberOfEventProperties()
	{
		if (!isFlatPropertyListConstructed)
			buildFlatPropertyByNumber();

		return static_cast<int>(flatEventPropertyVector.size());
	}
    
	SCO_API Property *GroupPropertyInternal::getFlatPropertyLocal(const char *pTypeName, int localIndex)
	{
		std::string typeName(pTypeName);
		Property *pProperty(0);

		if (!isFlatPropertyListConstructed)
			buildFlatPropertyByNumber();

		if (typeName.compare("class NDTS::SCO::IntProperty") == 0)
			pProperty = flatIntPropertyVector[localIndex];
		else if (typeName.compare("class NDTS::SCO::EnumProperty") == 0)
			pProperty = flatEnumPropertyVector[localIndex];
		else if (typeName.compare("class NDTS::SCO::SIProperty") == 0)
			pProperty = flatSIPropertyVector[localIndex];
		else
			pProperty = flatEventPropertyVector[localIndex];
        
		return pProperty ;
	}

	// ---------------- new add on 28 Aug, 2008 ---------------------------------------

}};