//	Delegate.cpp: implementation of the CDelegate class.
//
//	Based on Deb initial release
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "addin.h"
#include "Delegate.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//_____________________________________________________________________________
//
BEGIN_BL_METH_MAP(CDelegate) 
    BL_METH("DefineType",		"ОпределитьТип",	3,		procDefineType,	NULL,				NULL)
    BL_METH("AddMethod",		"Добавить",			3,		NULL,			funcAdd,			NULL)
    BL_METH("RemMethod",		"Удалить",			2,		NULL,			funcRemove,			NULL)
    BL_METH("RemMethodByKey",	"УдалитьПоКлючу",	1,		NULL,			funcRemoveByKey,	NULL)
    BL_METH("Invoke",			"Вызвать",			0,		NULL,			funcInvoke,			NULL)
    BL_METH("InvokeByKey",		"ВызватьПоКлючу",	1,		NULL,			funcInvokeByKey,	NULL)
    BL_METH("Erase",			"Очистить",			0,		procErase,		NULL,				NULL)
    BL_METH("IsEmpty",			"Пустой",			0,		NULL,			funcIsEmpty,		NULL)
END_BL_METH_MAP()

BEGIN_BL_PROP_MAP(CDelegate)
//    BL_PROP("propEng", "propRus", propGet, propSet)
END_BL_PROP_MAP()

IMPLEMENT_MY_CONTEXT(CDelegate, "Delegate", "Делегат", 1, NULL, NULL, -1);
//_____________________________________________________________________________
//
LPCSTR CDelegate::m_voidKey = "";
LPCSTR CDelegate::m_defaultKey = "__default";
//_____________________________________________________________________________
//
CDelegate::CDelegate()
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	ResetType();

	int * pn = aInvokeMethods;
	*pn++ = FindMethod("Invoke");
	*pn++ = FindMethod("InvokeByKey");

	m_pExecutedContext = CBLModule::GetExecutedModule()->GetInternalData()->pGeneralContext;
}
//_____________________________________________________________________________
//
CDelegate::~CDelegate()
{
	Erase(false);
}
//_____________________________________________________________________________
//
// checks
//_____________________________________________________________________________
//
void CDelegate::CheckType()
{
	if (m_nParamCount == -1)
		RuntimeError("Перед использованием делегата необходимо установить его тип методом Делегат::ОпределитьТип()");
}

void CDelegate::CheckMethName(CString& sMethName, LPCSTR sFuncName, LPCSTR sParamNum)
{
	if (sMethName.IsEmpty())      
		RuntimeError("%s параметр метода Делегат::%s необходимо передавать название метода!", sFuncName, sParamNum);
}

void CDelegate::CheckContext(CBLContext *pCont, LPCSTR sFuncName, LPCSTR sParamNum)
{
	if (!pCont)
		RuntimeError("%s параметр метода Делегат::%s необходимо передавать агрегатные объекты 1С (воспользуйтесь: CreateObject())!", sFuncName, sParamNum);
}

int CDelegate::CheckFindMethod(CBLContext *pCont, CString& sMethName)
{
	int nMeth = pCont->FindMethod(sMethName);
	if (nMeth == -1)
		RuntimeError("В классе '%s' не найден метод '%s'", pCont->GetTypeString(), sMethName);

	return nMeth;
}
//_____________________________________________________________________________
//
// service
//_____________________________________________________________________________
//
LPCSTR CDelegate::ResolveKey(LPCSTR key)
{
	if (!key)
		key = m_voidKey;

	return key;
}

LPCSTR CDelegate::ResolveKey(CValue* pcvKey)
{
	LPCSTR key = NULL;

	if ( !pcvKey->IsEmpty() )
		key = (LPCSTR)pcvKey->GetString();
	else
		key = ResolveKey(key);

	return key;
}

CBLContext* CDelegate::GetContext(const CValue *pvCont)
{
	return pvCont->GetContext();
}
//_____________________________________________________________________________
//
// frontend определение типа
//_____________________________________________________________________________
//
BOOL CDelegate::procDefineType(CValue **params)
{
	Erase(false);
	DefineType(params[0]->GetNumeric(), params[1]->GetNumeric(), params[2]->GetNumeric());

	return TRUE;
}
//_____________________________________________________________________________
//
// backend определение типа
//_____________________________________________________________________________
//
void CDelegate::DefineType(long nParamCount, long nIsFunc, long nHonorBreakRequests)
{
	m_nParamCount	= nParamCount;
	m_nIsFunc		= nIsFunc ? 1 : 0;
	m_bHonorBreakRequests = nHonorBreakRequests ? true : false;
}
//_____________________________________________________________________________
//
void CDelegate::ResetType()
{
	DefineType(-1, 0, 0);
}
//_____________________________________________________________________________
//
// frontend добавление метода с ключом
//_____________________________________________________________________________
//
BOOL CDelegate::funcAdd(CValue &RetVal, CValue **params)
{
	LPCSTR key = ResolveKey(params[2]);

	RetVal = Add(params, key);
	return TRUE;
}
//_____________________________________________________________________________
//
// backend добавление метода по ключу
// параметры: [0]: объект, [1]; имя метода; key: ключ
//_____________________________________________________________________________
//
long CDelegate::Add(CValue **params, LPCSTR key)
{
	CheckType();

	CBLContext *pCont = GetContext(params[0]);
	CheckContext(pCont, "Добавить()", "В первый");

	CString sMethName = params[1]->GetString();
	CheckMethName(sMethName, "Добавить()", "Во второй");

	int nMeth = CheckFindMethod(pCont, sMethName);

	if (pCont->HasRetVal(nMeth) != m_nIsFunc)
		RuntimeError("В классе '%s' метод '%s' объявлен как %s",
		pCont->GetTypeString(), sMethName, m_nIsFunc == 1 ? "процедура, необходима функция" : "функция, необходима процедура");

	if (pCont->GetNParams(nMeth) != m_nParamCount)
	{
		LPSTR sParam;
		int nParamCount = m_nParamCount % 10;
		if (nParamCount == 0 || nParamCount > 4)
			sParam = "параметров";
		else if (nParamCount == 1)
			sParam = "параметр";   
		else
			sParam = "параметра";   
		
		RuntimeError("В классе '%s' метод '%s' должен принимать %d %s!", pCont->GetTypeString(), sMethName, m_nParamCount, sParam);
	}
	pCont->IncrRef();

	CValue *pvCont = new CValue(*params[0]);
	InfoEventHandlers info(pCont, nMeth, pvCont);
/*
	// ГрупповойКонтекст - особый случай
	LPCSTR szTypeString = pCont->GetTypeString();
	if ( !m_map.Compare(szTypeString, "ГрупповойКонтекст") || !m_map.Compare(szTypeString, "GroupContext"))
		m_pBLGroupContext = pCont;
*/
	key = ResolveKey(key);

	LIST *hl;
	if ( !m_map.Lookup(key, hl) || !hl)
	{
		hl = new LIST;
		m_map[key] = hl;
	}
	
	hl->insert(hl->end(), info);
	return 1;
}
//_____________________________________________________________________________
//
// frontend удаление метода объекта по всем ключам
// параметры: [0]: объект, [1]: имя метода
//_____________________________________________________________________________
//
BOOL CDelegate::funcRemove(CValue &RetVal, CValue **params)
{
	CheckType();

	CBLContext *pCont = params[0]->GetContext();
	CheckContext(pCont, "Удалить()", "В первый");

	CString sMethName = params[1]->GetString();
	CheckMethName(sMethName, "Удалить()", "Во второй");

	int nMeth = CheckFindMethod(pCont, sMethName);

	LPCSTR key;
	LIST *hl;
	long bFound = 0;

	POSITION pos = m_map.GetStartPosition();
	while( pos )
	{
		m_map.GetNextAssoc(pos, key, hl);

		ITER iter;
		while ((iter = find(hl->begin(), hl->end(), InfoEventHandlers(pCont, nMeth, 0L))) != hl->end())
		{
			bFound = 1;
			//CBLContext *pBLCont = iter->pBLCont;
			CValue *pvCont = iter->pvCont;
			CBLContext *pBLCont = GetContext(pvCont);
			if (pBLCont)
				pBLCont->DecrRef();

			delete pvCont;
			hl->erase(iter);
		}
		if (hl->empty())
		{
			delete hl;
			m_map.RemoveKey(key);
		}
	}
	RetVal = bFound;
	return TRUE;
}
//_____________________________________________________________________________
//
// frontend удаление всех методов по ключу
// параметры: [0]: ключ
//_____________________________________________________________________________
//

BOOL CDelegate::funcRemoveByKey(CValue &RetVal, CValue **params)
{
	CheckType();

	LPCSTR key = ResolveKey(params[0]);
	LIST *hl;
	long bFound = 0;

	if (m_map.Lookup(key, hl) && hl)
	{
		CONST_ITER iter;
		for (iter = hl->begin(); iter != hl->end(); iter++ )
		{
			bFound = 1;
//			CBLContext *pBLCont = iter->pBLCont;
//			pBLCont->DecrRef();
			CValue *pvCont = iter->pvCont;
			CBLContext *pBLCont = GetContext(pvCont);
			if (pBLCont)
				pBLCont->DecrRef();

			delete pvCont;
		}
		delete hl;
		m_map.RemoveKey(key);
	}
	RetVal = bFound;
	return TRUE;
}
//_____________________________________________________________________________
//
// frontend вызов всех методов без учета ключа
//_____________________________________________________________________________
//
BOOL CDelegate::funcInvoke(CValue &RetVal, CValue **params)
{
	Invoke(RetVal, params, m_bHonorBreakRequests);
	return TRUE;
}
//_____________________________________________________________________________
//
// backend вызов всех методов без учета ключа
//_____________________________________________________________________________
//
void CDelegate::Invoke(CValue &RetVal, CValue **params, bool bHonorBreakRequests)
{
	CheckType();

	LPCSTR key = ResolveKey(static_cast<LPCSTR>(NULL));
	LIST *hl;
	CValue vZero = CValue(0L);

	RetVal = 1L;

	POSITION pos = m_map.GetStartPosition();
	while( pos )
	{
		m_map.GetNextAssoc(pos, key, hl);

		CONST_ITER iter;
		for (iter = hl->begin(); iter != hl->end(); iter++ )
		{
			//CBLContext *pBLCont = iter->pBLCont;
			CBLContext *pBLCont = GetContext(iter->pvCont);
			if (pBLCont)
			{
				int nMeth = iter->nNumMethOfCall;
				if (m_nIsFunc)
				{      
					pBLCont->CallAsFunc(nMeth, RetVal, params);
					if (bHonorBreakRequests)
						if (RetVal == vZero)                  
							return;
				}
				else
				{      
					pBLCont->CallAsProc(nMeth, params);
				}
			}
		}
	}
}
//_____________________________________________________________________________
//
// frontend вызов всех методов по ключу
// параметры: [0]: ключ, [...] параметры делегата
//_____________________________________________________________________________
//
BOOL CDelegate::funcInvokeByKey(CValue &RetVal, CValue **params)
{
	CheckType();

	LPCSTR key = ResolveKey(params[0]);

	InvokeByKey(RetVal, params + 1, key, m_bHonorBreakRequests);
	return TRUE;
}
//_____________________________________________________________________________
//
// backend вызов всех методов по ключу
// параметры: [...]: параметры делегата
//_____________________________________________________________________________
//
void CDelegate::InvokeByKey(CValue &RetVal, CValue **params, LPCSTR key, bool bHonorBreakRequests)
{
	LIST *hl;
	CValue vZero = CValue(0L);
	
	RetVal = 1L;
	
	if (m_map.Lookup(key, hl) && hl)
	{
		CONST_ITER iter;
		for (iter = hl->begin(); iter != hl->end(); iter++ )
		{
			//CBLContext *pBLCont = iter->pBLCont;
			CBLContext *pBLCont = GetContext(iter->pvCont);
			if (pBLCont)
			{
				
				int nMeth = iter->nNumMethOfCall;
				
				if (m_nIsFunc)
				{
					pBLCont->CallAsFunc(nMeth, RetVal, params);
					if (bHonorBreakRequests)
						if (RetVal == vZero)                  
							return;
				}
				else
					pBLCont->CallAsProc(nMeth, params);
			}
		}
	}
	else if (key != m_defaultKey)
	{
		InvokeByKey(RetVal, params, m_defaultKey, bHonorBreakRequests);
	}
}
//_____________________________________________________________________________
//
// frontend очистка
//_____________________________________________________________________________
//
BOOL CDelegate::procErase(CValue **params)
{
	Erase(true);
	return TRUE;
}
//_____________________________________________________________________________
//
// backend очистка
//_____________________________________________________________________________
//
void CDelegate::Erase(bool bResetType)
{
	LPCSTR key;
	LIST *hl;

	POSITION pos = m_map.GetStartPosition();
	while( pos )
	{
		m_map.GetNextAssoc(pos, key, hl);

		CONST_ITER iter;
		for (iter = hl->begin(); iter != hl->end(); iter++ )
		{
			//CBLContext *pBLCont = iter->pBLCont;
			CValue *pvCont = iter->pvCont;
			CBLContext *pBLCont = GetContext(pvCont);
			if (pBLCont)
			{
				// для всех, кроме исполняемого контекста уменьшаем ссылки
				//if (pBLCont != m_pExecutedContext)
				pBLCont->DecrRef();
			}
			delete pvCont;
		}
		delete hl;
	}
	m_map.RemoveAll();

	if (bResetType)
		ResetType();
}
//_____________________________________________________________________________
//
// frontend проверка
//_____________________________________________________________________________
//
BOOL CDelegate::funcIsEmpty(CValue &RetVal, CValue **params)
{
	LPCSTR key;
	LIST *hl;

	POSITION pos = m_map.GetStartPosition();
	long bEmpty = 1;

	while( pos )
	{
		m_map.GetNextAssoc(pos, key, hl);
		if ( hl && !hl->empty() )
		{
			bEmpty = 0;
			break;
		}
	}

	RetVal = bEmpty;
	return TRUE;
}
//_____________________________________________________________________________
//
// получить число параметров делегата
//_____________________________________________________________________________
//
int CDelegate::GetDefinedParamCount()
{
	return m_nParamCount;
}
//_____________________________________________________________________________
//
// context overload
//_____________________________________________________________________________
//
int CDelegate::GetNParams(int iMethodNum) const
{
	_S_MyContextBaseInfo* pInfo=GetBaseInfo();
	if(((DWORD)iMethodNum)>=pInfo->methCount)
		return 0;

	// учет методов переменной длины
	int nParamCount = 0;
	for (int i = 0; i < nInvokeMethods; ++i)
	{
		if (iMethodNum == aInvokeMethods[i])
		{
			nParamCount = m_nParamCount;
			break;
		}
	}

	return pInfo->pMethodDef[iMethodNum].NumberOfParam + nParamCount;
}
//_____________________________________________________________________________
//
