/*
	MappingManager.cpp

	Manages the shared memory/mapping used by the performance monitor extensions.

*/
// @doc

#include "win32types.h"
#include "winperf.h"
#include "pyperfmon.h"
#include "tchar.h"

MappingManager::MappingManager()
{
	m_hMappedObject = NULL;
	m_pMapBlock = NULL;
	m_pControl = NULL;
}

MappingManager::~MappingManager()
{
	if (m_pControl)
		m_pControl->supplierStatus = SupplierStatusStopped;
	UnmapViewOfFile(m_pMapBlock);
	CloseHandle(m_hMappedObject);
}

BOOL MappingManager::CheckStatus()
{
	if (m_pMapBlock == NULL)
	{
		PyErr_SetString(PyExc_ValueError, "The file mapping has not been initialised correctly");
		return FALSE;
	}
	return TRUE;
}

BOOL MappingManager::Init(const TCHAR *szServiceName, const TCHAR *szMappingName /* = NULL */, const TCHAR *szEventSourceName /* = NULL */)
{
	TCHAR szGlobalMapping[MAX_PATH + 10] = _T("");

	if (szMappingName == NULL)
		szMappingName = szServiceName;
	if (szEventSourceName == NULL)
		szEventSourceName = szServiceName;

	// Use a TerminalServices friendly "Global\\" prefix if supported.
	OSVERSIONINFO info;
	info.dwOSVersionInfoSize = sizeof(info);
	GetVersionEx(&info);
	if (info.dwMajorVersion > 4)
		// 2000 or later - "Global\\" prefix OK.
		_tcscpy(szGlobalMapping, _T("Global\\"));

	_tcscat(szGlobalMapping, szMappingName);

	m_hMappedObject = CreateFileMapping(INVALID_HANDLE_VALUE,
										NULL,
										PAGE_READWRITE,
										0,
										4096,
										szGlobalMapping);
	if (m_hMappedObject == NULL)
	{
		PyWin_SetAPIError("CreateFileMapping");
		return FALSE;
	}
	// map the section and assign the counter block pointer
	// to this section of memory
	//
	m_pMapBlock = MapViewOfFile(m_hMappedObject,
								FILE_MAP_ALL_ACCESS,
								0,
								0,
								0);
	if (m_pMapBlock == NULL)
	{
		// Failed to Map View of file
		PyWin_SetAPIError("MapViewOfFile");
		return FALSE;
	}

	m_pControl = (MappingManagerControlData *)m_pMapBlock;
	m_pControl->ControlSize = sizeof(MappingManagerControlData);
	m_pControl->TotalSize = sizeof(MappingManagerControlData);
	_tcsncpy(m_pControl->ServiceName, szServiceName, MMCD_SERVICE_SIZE);
	m_pControl->ServiceName[MMCD_SERVICE_SIZE] = _T('\0');

	_tcsncpy(m_pControl->EventSourceName, szEventSourceName, MMCD_EVENTSOURCE_SIZE);
	m_pControl->EventSourceName[MMCD_EVENTSOURCE_SIZE] = _T('\0');
	m_pControl->supplierStatus = SupplierStatusRunning;
	return TRUE;
}

void *MappingManager::AllocChunk(DWORD numBytes)
{
	if (!CheckStatus())
		return NULL;
	void *result = ((BYTE *)m_pMapBlock) + (m_pControl->TotalSize);
	m_pControl->TotalSize += numBytes;
	return result;
}

// @pymethod <o PyPerfMonManager>|perfmon|PerfMonManager|Creates a new PERF_OBJECT_TYPE object
PyObject *PerfmonMethod_NewPerfMonManager(PyObject *self, PyObject *args)
{
	PyObject *ret = NULL;
	PyObject *obServiceName;
	PyObject *obPerfObTypes;
	PyObject *obEventSourceName = Py_None;
	PyObject *obMappingName = Py_None;
	TCHAR *szMappingName = NULL;
	TCHAR *szEventSourceName = NULL;
	TCHAR *szServiceName = NULL;
	MappingManager *m_pmm = NULL;
	PyPerfMonManager *pPOT = NULL;

	if (!PyArg_ParseTuple(args, "OO|OO:PerfMonManager",
						  &obServiceName,	  // @pyparm <o PyUnicode>|serviceName||The name of the service for which data is being provided.
						  &obPerfObTypes,	  // @pyparm [<o PyPERF_OBJECT_TYPE>, ...]|seqPerfObTypes||A sequence of objects to use in the performance monitor.  At this stage, len(seqPerfObTypes) must == 1.
						  &obMappingName,	  // @pyparm <o PyUnicode>|mappingName|None|The name of the mapping to open.  This must be the same as the DLL name providing the information.  If None, the serviceName is used.
						  &obEventSourceName)) // @pyparm <o PyUnicode>|eventSourceName|None|The name used by the DLL for error messages in the registry.  If None, the serviceName is used.
		goto done;
	// @comm The application need not be a service, but it must have an entry in the
	// Services section of the registry.  This limits the performance monitor to being able to
	// provide only one 'counter type', but still many counters within that type.
	// See the documentation for the Performance Monitor API for more details.
	if (!PyWinObject_AsTCHAR(obServiceName, &szServiceName, FALSE))
		goto done;

	if (!PyWinObject_AsTCHAR(obEventSourceName, &szEventSourceName, TRUE))
		goto done;

	if (!PyWinObject_AsTCHAR(obMappingName, &szMappingName, TRUE))
		goto done;

	m_pmm = new MappingManager();
	if (m_pmm == NULL)
	{
		PyErr_SetString(PyExc_MemoryError, "Allocating memory for MappingManager");
		goto done;
	}
	if (!m_pmm->Init(szServiceName, szMappingName, szEventSourceName))
		// Init has set Python error
		goto done;

	pPOT = new (PyPerfMonManager);
	if (pPOT == NULL)
	{
		PyErr_SetString(PyExc_MemoryError, "Allocating MappingManager or PERF_OBJECT_TYPE");
	}
	if (!pPOT->Init(m_pmm, obPerfObTypes))
		goto done;
	// we worked!
	ret = pPOT;
done:
	if (szMappingName)
		PyWinObject_FreeTCHAR(szMappingName);
	if (szServiceName)
		PyWinObject_FreeTCHAR(szServiceName);
	if (szEventSourceName)
		PyWinObject_FreeTCHAR(szEventSourceName);
	if (ret == NULL)
	{ // we have an error
		if (m_pmm)
			delete m_pmm;
		if (pPOT)
			delete pPOT;
	}
	return ret;
}

// @pymethod |PyPerfMonManager|Close|Closes the performance monitor manager.
PyObject *PyPerfMonManager::Close(PyObject *self, PyObject *args)
{
	if (!PyArg_ParseTuple(args, ":Close"))
		return NULL;
	PyPerfMonManager *This = (PyPerfMonManager *)self;
	This->Term();
	Py_INCREF(Py_None);
	return Py_None;
}

// @object PyPerfMonManager|A Python object
struct PyMethodDef PyPerfMonManager::methods[] = {
	{"Close", PyPerfMonManager::Close, 1}, // @pymeth Close|Closes all counters.
	{NULL}};

PyTypeObject PyPerfMonManager::type =
	{
		PYWIN_OBJECT_HEAD
		"PyPerfMonManager",
		sizeof(PyPerfMonManager),
		0,
		PyPerfMonManager::deallocFunc, /* tp_dealloc */
		0,							   /* tp_print */
		0,							   /* tp_getattr */
		0,							   /* tp_setattr */
		0,							   /* tp_compare */
		0,							   /* tp_repr */
		0,							   /* tp_as_number */
		0,							   /* tp_as_sequence */
		0,							   /* tp_as_mapping */
		0,							   /* tp_hash */
		0,							   /* tp_call */
		0,							   /* tp_str */
		PyObject_GenericGetAttr,	   /* tp_getattro */
		PyObject_GenericSetAttr,	   /* tp_setattro */
		0,							   /* tp_as_buffer */
		Py_TPFLAGS_DEFAULT,			   /* tp_flags */
		0,							   /* tp_doc */
		0,							   /* tp_traverse */
		0,							   /* tp_clear */
		0,							   /* tp_richcompare */
		0,							   /* tp_weaklistoffset */
		0,							   /* tp_iter */
		0,							   /* tp_iternext */
		PyPerfMonManager::methods,	 /* tp_methods */
		PyPerfMonManager::members,	 /* tp_members */
		0,							   /* tp_getset */
		0,							   /* tp_base */
		0,							   /* tp_dict */
		0,							   /* tp_descr_get */
		0,							   /* tp_descr_set */
		0,							   /* tp_dictoffset */
		0,							   /* tp_init */
		0,							   /* tp_alloc */
		0,							   /* tp_new */
};

#define OFF(e) offsetof(PyPerfMonManager, e)

/*static*/ struct PyMemberDef PyPerfMonManager::members[] = {
	{NULL} /* Sentinel */
};

PyPerfMonManager::PyPerfMonManager(void)
{
	ob_type = &type;
	_Py_NewReference(this);
	m_pmm = NULL;
	m_obPerfObTypes = NULL;
}

PyPerfMonManager::~PyPerfMonManager()
{
	Term();
}

void PyPerfMonManager::Term()
{
	// Get each of our objects to terminate before we delete the memory...
	if (m_obPerfObTypes)
	{
		PyObject *obType = PySequence_GetItem(m_obPerfObTypes, 0);
		PyPERF_OBJECT_TYPE *pPerfOb;
		if (obType)
		{
			if (PyWinObject_AsPyPERF_OBJECT_TYPE(obType, &pPerfOb, FALSE))
				pPerfOb->Term();
		}
		Py_XDECREF(obType);
		Py_DECREF(m_obPerfObTypes);
		m_obPerfObTypes = NULL;
	}

	// Then cleanup our mapping
	if (m_pmm)
	{
		delete m_pmm;
		m_pmm = NULL;
	}
}

// Initialize the mapping with the Python object definitions.
BOOL PyPerfMonManager::Init(MappingManager *pmm, PyObject *obPerfObTypes)
{
	if (PySequence_Length(obPerfObTypes) != 1)
	{
		PyErr_SetString(PyExc_ValueError, "The sequence of PyPERF_OBJECT_TYPEs must have 1 item!");
		return NULL;
	}
	PyObject *obType = PySequence_GetItem(obPerfObTypes, 0);
	PyPERF_OBJECT_TYPE *pPerfOb;
	if (!obType)
		return FALSE;
	BOOL ok = TRUE;
	ok = ok && PyWinObject_AsPyPERF_OBJECT_TYPE(obType, &pPerfOb, FALSE);
	ok = ok && pPerfOb->InitMemoryLayout(pmm, this);
	Py_DECREF(obType);
	m_pmm = pmm;
	m_obPerfObTypes = obPerfObTypes;
	Py_INCREF(m_obPerfObTypes);
	return ok;
}

/*static*/ void PyPerfMonManager::deallocFunc(PyObject *ob)
{
	delete (PyPerfMonManager *)ob;
}
