// EverestDataSources.cpp: implementation of the CEverestDataSources class.
//
// created by Unwinder
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "EverestDataSources.h"
//////////////////////////////////////////////////////////////////////
#include <float.h>
//////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CEverestDataSources::CEverestDataSources()
{
	m_strID		= "";
	m_strValue	= "";
}
//////////////////////////////////////////////////////////////////////
CEverestDataSources::~CEverestDataSources()
{
	Uninit();
}
//////////////////////////////////////////////////////////////////////
BOOL CEverestDataSources::Init(LPCSTR lpFilename)
{
	Uninit();

	char szBuf[MAX_PATH];
		//local string buffer

	for (DWORD dwSrc=0; dwSrc<INT_MAX; dwSrc++)
		//loop through all sources
	{
		CString strSrc;	strSrc.Format("Source%d", dwSrc);
			//format source section name

		GetPrivateProfileString(strSrc, "ID", "", szBuf, MAX_PATH, lpFilename);
			//read source ID from .cfg
		if (!strlen(szBuf))
			//break the loop if source doesn't exist (i.e. ID is empty)
			break;

		LPEVEREST_DATA_SOURCE_DESC lpDesc = new EVEREST_DATA_SOURCE_DESC; ZeroMemory(lpDesc, sizeof(EVEREST_DATA_SOURCE_DESC));
			//allocate and zap new source descriptor

		strcpy(lpDesc->szID, szBuf);
			//init ID

		GetPrivateProfileString(strSrc, "Name"		, "", lpDesc->szName	, sizeof(lpDesc->szName)	, lpFilename);
			//read source name
		GetPrivateProfileString(strSrc, "Dim"		, "", lpDesc->szDim		, sizeof(lpDesc->szDim)		, lpFilename);
			//read source dimension
		GetPrivateProfileString(strSrc, "MaxLimit"	, "", szBuf				, sizeof(szBuf)				, lpFilename);
			//read maximum limit
		sscanf(szBuf, "%f", &lpDesc->fltMaxLimit);
			//scan maximum limit string and convert it to float
		GetPrivateProfileString(strSrc, "MinLimit"	, "", szBuf				, sizeof(szBuf)				, lpFilename);
			//read mimimum limit
		sscanf(szBuf, "%f", &lpDesc->fltMinLimit);
			//scan minimum limit string and convert it to float
		GetPrivateProfileString(strSrc, "GridDim"	, "", szBuf				, sizeof(szBuf)				, lpFilename);
			//read grid dimension
		sscanf(szBuf, "%f", &lpDesc->fltGridDim);
			//scan grid dimension string and convert it to float
		GetPrivateProfileString(strSrc, "Desc"		, "", lpDesc->szDesc	, sizeof(lpDesc->szDesc)	, lpFilename);
			//read source description from .cfg
		GetPrivateProfileString(strSrc, "Group"		, "", lpDesc->szGroup	, sizeof(lpDesc->szGroup)	, lpFilename);
			//read source group from .cfg

		lpDesc->fltData		= FLT_MAX;
			//reset source data
		
		AddTail(lpDesc);
			//add descriptor to the list
	}

	return TRUE;
}
//////////////////////////////////////////////////////////////////////
void CEverestDataSources::Uninit()
{
	POSITION pos = GetHeadPosition();

	while (pos)
		delete GetNext(pos);

	RemoveAll();
}
//////////////////////////////////////////////////////////////////////
#define PARSER_STATE_VAL		0
#define PARSER_STATE_TAG_HEAD	1
#define PARSER_STATE_TAG_TAIL	2
//////////////////////////////////////////////////////////////////////
BOOL CEverestDataSources::ParseXML(LPCSTR lpText)
{
	if (!lpText)
		//validate input text ptr
		return FALSE;

	CStringList			strValStack;
		//item value stack
	CStringList			strTagStack;
		//item tag stack

	CString	strVal	= "";
		//current item value accumulator
	CString	strTag	= "";
		//current item tag accumulator

	int		nState	= PARSER_STATE_VAL;
		//parser state
	char	symbol	= *lpText;
		//current symbol

	while (symbol)
		//loop through all symbols in the line till trailing NULL-terminator
	{
		switch (symbol)
			//process symbols depending on the current parser state
		{
		case '<':
			//process tag initiator symbol
			switch (nState)
			{
			case PARSER_STATE_VAL:
				nState	= PARSER_STATE_TAG_HEAD;
					//switch parser to tag head state
				strTag	= "";
					//reset tag name accumulator
				break;
			case PARSER_STATE_TAG_HEAD:
			case PARSER_STATE_TAG_TAIL:
				return FALSE;
					//tag initiator symbol is not allowed inside tags
			}
			break;
		case '>':
			//process tag terminator symbol
			switch (nState)
			{
			case PARSER_STATE_VAL:
				return FALSE;
					//tag terminator symbol is not allowed outside tags
			case PARSER_STATE_TAG_HEAD:

				//handle tag head 

				strTagStack.AddTail(strTag);
					//push tag accumulator
				strValStack.AddTail(strVal);
					//push value accumulator
				nState	= PARSER_STATE_VAL;
					//switch parser to value state
				strVal	= "";
					//reset value accumulator
				break;
			case PARSER_STATE_TAG_TAIL:

				//handle tag tail

				if (strTagStack.IsEmpty())
					//return error if there are no open begin tags in tag stack
					return FALSE;
				if (stricmp(strTagStack.RemoveTail(), strTag))
					//return error if the last open begin tag in tag stack doesn't
					//match with end tag we're vurrently processing
					return FALSE;

				ParseXMLElement(strTag, strVal, strTagStack.GetCount());
					//element is completely declared now so we can parse it

				nState	= PARSER_STATE_VAL;
					//swicth parser to value state
				strVal	= strValStack.RemoveTail();
					//pop the previous value from value stack
				break;
			}
			break;
		case '/':
			//process tag tail initiator symbol
			switch (nState)
			{
			case PARSER_STATE_VAL:
				strVal += symbol;
					//append value accumulator if the parser is in value state now
				break;
			case PARSER_STATE_TAG_HEAD:
				if (strlen(strTag))
					return FALSE;	
					//tag tail initiator symbol is not allowed inside tag head name
				else
					nState = PARSER_STATE_TAG_TAIL;
						//switch parser to tag tail state
				break;
			case PARSER_STATE_TAG_TAIL:
					//tag tail initiator symbol is not allowed inside tag tail name
				return FALSE;
			}
			break;
		default:
			//process the rest symbols
			switch (nState)
			{
			case PARSER_STATE_VAL:
				strVal += symbol;
					//append value accumulator if the parser is in value state now
				break;
			case PARSER_STATE_TAG_HEAD:
			case PARSER_STATE_TAG_TAIL:
				strTag += symbol;
					//append tag accumulator if the parser is in tag state now
				break;
			}
			break;
		}

		symbol = *++lpText;
			//modify current symbol ptr and precache new symbol to be processed on the
			//next iteration
	}

	return TRUE;
}
//////////////////////////////////////////////////////////////////////
void CEverestDataSources::ParseXMLElement(LPCSTR lpTag, LPCSTR lpVal, int nLevel)
{
	switch (nLevel)
		//elements are handled differently depending on their level:

		//level 1 elements defining source properties are parsed first, then level 0
		//element defining whole source is parsed
	{
	case 0:
		//parse level 0 element defining whole source (at this step we've already
		//parsed and saved child level 1 elements defining source properties)

		if (strlen(m_strID		) &&
			strlen(m_strValue	))
			UpdateSource(m_strID, m_strValue);
				//update source if both ID and value are defined

		m_strID		= "";
			//reset source ID
		m_strValue	= "";
			//reset source value
		break;

	case 1:
		//parse level 1 element defining source properties (id, name, value etc.)

		if (!stricmp(lpTag, "id"))
			m_strID = lpVal;
				//save source id

		if (!stricmp(lpTag, "value"))
			m_strValue = lpVal;
				//save source value
		break;	
	}
}
//////////////////////////////////////////////////////////////////////
BOOL CEverestDataSources::UpdateSource(LPCSTR lpID, LPCSTR lpValue)
{
	POSITION pos = GetHeadPosition();

	while (pos)
		//loop through all source and find desired source by ID
	{
		LPEVEREST_DATA_SOURCE_DESC lpDesc = GetNext(pos);
			//get current source descriptor

		if (!stricmp(lpID, lpDesc->szID))
			//compare source ID with specified one
			return (sscanf(lpValue, "%f", &lpDesc->fltData) == 1);
				//scan value string and store FP data into source descriptor
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
void CEverestDataSources::UpdateSources()
{
	HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "EVEREST_SensorValues");
		//try to open everest shared memory mapping file

	if (hMapFile)
	{
		LPCSTR pMapAddr = (LPCSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
			//try to map view of file to get access to Everest XML-styled shared memory
		
		if (pMapAddr)
		{
			m_strCache = pMapAddr;
				//precache Everest XML-styled shared memory

			ParseXML(m_strCache);
				//parse cached Everest XML-styled shared memory

			UnmapViewOfFile(pMapAddr);
		}
		else
			InvalidateSources();
				//invalidate sources if we cannot map view of file (Everest is shutting down)

		CloseHandle(hMapFile);
	}
	else
		InvalidateSources();
			//invalidate sources if we cannot map view of file (Everest is not running)
}
//////////////////////////////////////////////////////////////////////
LPEVEREST_DATA_SOURCE_DESC CEverestDataSources::GetSourceDesc(DWORD dwSource)
{
	POSITION pos = FindIndex(dwSource);

	if (pos)
		return GetAt(pos);

	return NULL;
}
//////////////////////////////////////////////////////////////////////
FLOAT CEverestDataSources::GetSourceData(DWORD dwSource)
{
	LPEVEREST_DATA_SOURCE_DESC lpDesc = GetSourceDesc(dwSource);
		//try to find source descriptor by index

	if (lpDesc)
	{
		if (lpDesc->fltData != FLT_MAX)
			//check if source data is valid 
		{
			FLOAT fltData	= lpDesc->fltData;
			lpDesc->fltData = FLT_MAX;
				//reset data to force it to be reread from Everest on the next call
			return fltData;
				//return source data
		}
		else
		{
			UpdateSources();
				//if source data was invalid, we'll try reread it from Everest

			if (lpDesc->fltData != FLT_MAX)
				//check if source data became valid 
			{
				FLOAT fltData	= lpDesc->fltData;
				lpDesc->fltData = FLT_MAX;
					//reset data to force it to be reread from Everest on the next call
				return fltData;
					//return source data
			}
		}
	}

	return FLT_MAX;
		//data is invalid, Everest is not loaded or the source is not supported by hardware
}
//////////////////////////////////////////////////////////////////////
void CEverestDataSources::InvalidateSources()
{
	POSITION pos = GetHeadPosition();

	while (pos)
		//loop through all source and find desired source by ID
	{
		LPEVEREST_DATA_SOURCE_DESC lpDesc = GetNext(pos);
			//get current source descriptor

		lpDesc->fltData = FLT_MAX;
			//rest source data
	}
}
//////////////////////////////////////////////////////////////////////
LPCSTR CEverestDataSources::GetCache()
{
	return m_strCache;
}
//////////////////////////////////////////////////////////////////////
