// HistoryParser.cpp: implementation of the CHistoryParser class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include "HistoryParser.h"
#include "Filestuff.h"


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



extern CMSInfoHistoryCategory catHistorySystemSummary;
extern CMSInfoHistoryCategory catHistoryResources;
extern CMSInfoHistoryCategory catHistoryComponents;
extern CMSInfoHistoryCategory catHistorySWEnv;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CHistoryParser::CHistoryParser(CComPtr<IXMLDOMDocument> pDoc) : m_pDoc(pDoc)
{

}

void CHistoryParser::DeleteAllInstances()
{
	for(POSITION pos = this->m_listInstances.GetHeadPosition();pos;)
	{
		if (!pos)
		{
			return;
		}
		CInstance* pInci = (CInstance*) m_listInstances.GetNext(pos);
		delete pInci;
	}
	m_listInstances.RemoveAll();
}

CHistoryParser::~CHistoryParser()
{
	DeleteAllInstances();
}


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
// takes a CTime, which comes from timestamp element of the Delta or Snaphot
// node of which pInstanceNode is a child;  an Instance node, and a string 
// containing the WMI class of the Instance
//////////////////////////////////////////////////////////////////////

CInstance::CInstance(CTime tmstmp, CComPtr<IXMLDOMNode> pInstanceNode,CString strClass) : m_tmstamp(tmstmp), m_strClassName(strClass)
{
	CComPtr<IXMLDOMNodeList> pPropList;
	HRESULT hr;
	//Get node data, add each PROPERTY name and VALUE to m_mapNameValue
	if (strClass.CompareNoCase(_T("Win32_PNPAllocatedResource")) == 0)
	{
		hr = ProcessPNPAllocatedResource(pInstanceNode);
		ASSERT(SUCCEEDED(hr) && "failed to process Win32_PNPAllocatedResource");
		return;
	}
	hr = pInstanceNode->selectNodes(CComBSTR("PROPERTY"),&pPropList);
	if (FAILED(hr) || !pPropList)
	{
		ASSERT(0 && "could not get property list from Instance node");
		return;
	}
	long lListLen;
	hr = pPropList->get_length(&lListLen);
	CComPtr<IXMLDOMNode> pVNode;
	CComBSTR bstrValue;
	CComVariant varName;
	for(long i = 0; i < lListLen; i++)
	{
		hr = pPropList->nextNode(&pVNode);
		if (FAILED(hr) || !pVNode)
		{
			return;
		}
		CComPtr<IXMLDOMElement> pElement;
		hr = pVNode->QueryInterface(IID_IXMLDOMElement,(void**) &pElement);
		if (FAILED(hr) || !pElement)
		{
			return;
		}
		hr = pElement->getAttribute(L"NAME",&varName);
		ASSERT(SUCCEEDED(hr));
		hr = pVNode->get_text(&bstrValue);
		ASSERT(SUCCEEDED(hr));
		USES_CONVERSION;
		m_mapNameValue.SetAt(OLE2T(varName.bstrVal)  ,OLE2T(bstrValue));
		pVNode.Release();
	}
	pPropList.Release();
	return;

}

//////////////////////////////////////////////////////////////////////////////////////////
//Refresh is called for selected category when category selection or delta range changes
//////////////////////////////////////////////////////////////////////////////////////////

HRESULT CHistoryParser::Refresh(CMSInfoHistoryCategory* pHistCat,int nDeltasBack)
{
	nDeltasBack++;
	this->m_fChangeLines = FALSE;// v-stlowe 2/28/2001
	DeleteAllInstances();
	m_pHistCat = pHistCat;
	CComPtr<IXMLDOMNodeList> pDeltaList;
	HRESULT hr;
	hr = this->GetDeltaAndSnapshotNodes(pDeltaList);
	if (FAILED(hr) || !pDeltaList)
	{
		return E_FAIL;
	}

	if (pHistCat == &catHistoryComponents)
	{
		DeleteAllInstances();
		
		hr = ProcessDeltas(pDeltaList,"Win32_DriverVXD",nDeltasBack);
		ASSERT(SUCCEEDED(hr));
		DeleteAllInstances();
		pDeltaList->reset();
		hr = ProcessDeltas(pDeltaList,"Win32_CodecFile",nDeltasBack);
		ASSERT(SUCCEEDED(hr));
		DeleteAllInstances();
		pDeltaList->reset();
		hr = ProcessDeltas(pDeltaList,"Win32_LogicalDisk",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		hr = ProcessDeltas(pDeltaList,"Win32_NetworkProtocol",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		hr = ProcessDeltas(pDeltaList,"Win32_Printer",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		hr = ProcessDeltas(pDeltaList,"Win32_PortResource",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		hr = ProcessDeltas(pDeltaList,"Win32_PnPEntity",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
	}
	else if (pHistCat == &catHistorySystemSummary)
	{
		DeleteAllInstances();
		hr = ProcessDeltas(pDeltaList,"Win32_ComputerSystem",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		hr = ProcessDeltas(pDeltaList,"Win32_OperatingSystem",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
		//hr = ProcessDeltas(pDeltaList,"Win32_Win32_LogicalMemoryConfiguration",nDeltasBack);
		hr = ProcessDeltas(pDeltaList,"Win32_LogicalMemoryConfiguration",nDeltasBack); //v-stlowe 2/28/2001
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
	}
	else if(pHistCat == &catHistoryResources)
	{
		DeleteAllInstances();
		hr = ProcessDeltas(pDeltaList,"Win32_PNPAllocatedResource",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		ASSERT(SUCCEEDED(hr));
	}
	else if (pHistCat == &catHistorySWEnv)
	{
		DeleteAllInstances();
		hr = ProcessDeltas(pDeltaList,"Win32_ProgramGroup",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		hr = ProcessDeltas(pDeltaList,"Win32_StartupCommand",nDeltasBack);
		DeleteAllInstances();
		pDeltaList->reset();
		
	}
	if (!m_fChangeLines)
	{
#ifdef A_STEPHL2
		::MessageBox(NULL,"!m_fChangeLines)","",MB_OK);
#endif
		m_fChangeLines = TRUE;
		CString strMSG;
		strMSG.LoadString(IDS_DELTANOCHANGES);//this would be the place to change messaging for situation where summary has no changes
		m_pHistCat->InsertLine(-1, strMSG, _T(""), _T(""), _T(""));
	}
	pDeltaList.Release();
	return hr;
}



//////////////////////////////////////////////////////////////////////////////////////////
//Gets the value appropriate to use as a description for the class
//////////////////////////////////////////////////////////////////////////////////////////
CString CInstance::GetInstanceDescription()
{
	CString strDescName = GetDescriptionForClass(m_strClassName);
	CString strInstDesc;
	VERIFY(m_mapNameValue.Lookup(strDescName,strInstDesc));
	return strInstDesc;

}

//////////////////////////////////////////////////////////////////////////////////////////
//Gets the value that can be used to uniquely identify a specific instance of a class
//////////////////////////////////////////////////////////////////////////////////////////
CString CInstance::GetInstanceID()
{
	CString strIDName = GetIDForClass(m_strClassName);
	CString strInstID;
	VERIFY(m_mapNameValue.Lookup(strIDName,strInstID));
	return strInstID;

}

//////////////////////////////////////////////////////////////////////////////////////////
//used to deal with antecedent\dependant relationship classes in Win32_PNPAllocatedResource classes
//////////////////////////////////////////////////////////////////////////////////////////

HRESULT CInstance::ProcessPropertyDotReferenceNodes(CComPtr<IXMLDOMNode> pInstanceNameNode,CString* pstrClassName, CString* pstrKeyName,CString* pstrKeyValue)
{
	USES_CONVERSION;
	HRESULT hr;
	CComPtr<IXMLDOMElement> pNameElement;
	hr = pInstanceNameNode->QueryInterface(IID_IXMLDOMElement,(void**) &pNameElement);
	if (FAILED(hr) | !pNameElement)
	{
		ASSERT(0 && "could not QI pNode for Element");
		return E_FAIL;
	}
	CComVariant varClassName;
	hr = pNameElement->getAttribute(L"CLASSNAME",&varClassName);
	pNameElement.Release();
	if (FAILED(hr))
	{
		ASSERT(0 && "could not get CLASSNAME element");
	}
	*pstrClassName = OLE2T(varClassName.bstrVal);
	CComPtr<IXMLDOMNode> pKeybindingNode;
	hr = pInstanceNameNode->selectSingleNode(CComBSTR("KEYBINDING"),&pKeybindingNode);
	if (FAILED(hr) || !pKeybindingNode)
	{
		ASSERT(0 && "could not get antecedent node");
	}
	CComBSTR bstrKeyValue;
	hr = pKeybindingNode->get_text(&bstrKeyValue);
	ASSERT(SUCCEEDED(hr) && "failed to get keybinding value");
	*pstrKeyValue = OLE2T(bstrKeyValue);
	hr = pKeybindingNode->QueryInterface(IID_IXMLDOMElement,(void**) &pNameElement);
	if (FAILED(hr) | !pNameElement)
	{
		ASSERT(0 && "could not QI pNode for Element");
		return E_FAIL;
	}
	CComVariant varKeybindingName;
	hr = pNameElement->getAttribute(CComBSTR("NAME"),&varKeybindingName);
	if (FAILED(hr))
	{
		ASSERT(0 && "could not get NAME attribute from pNameElement");
	}

	*pstrKeyName = OLE2T(varKeybindingName.bstrVal);
	return hr;
}

//////////////////////////////////////////////////////////////////////////////////////////
//used to deal with antecedent\dependant relationship classes in Win32_PNPAllocatedResource classes
//////////////////////////////////////////////////////////////////////////////////////////

HRESULT CInstance::ProcessPNPAllocatedResource(CComPtr<IXMLDOMNode> pInstanceNode)
{

	HRESULT hr;
	CComPtr<IXMLDOMNodeList> pPropDotRefList;
	hr = pInstanceNode->selectNodes(CComBSTR("PROPERTY.REFERENCE/VALUE.REFERENCE/INSTANCEPATH/INSTANCENAME"),&pPropDotRefList);
	if (FAILED(hr) || !pPropDotRefList)
	{
		ASSERT(0 && "PROPERTY.REFERENCE nodes not found");
		return E_FAIL;
	}

	//get antecedent node
	CComPtr<IXMLDOMNode> pInstanceNameNode;
	hr = pPropDotRefList->nextNode(&pInstanceNameNode);
	if (FAILED(hr) || !pInstanceNameNode)
	{
		ASSERT(0 && "could not get antecedent node");
	}
	CString strAntecedentName,strResourceName,strResourceValue;
	hr = ProcessPropertyDotReferenceNodes(pInstanceNameNode,&strAntecedentName,&strResourceName,&strResourceValue);
	m_mapNameValue.SetAt(_T("ANTECEDENT"),strAntecedentName);
	m_mapNameValue.SetAt(strResourceName,strResourceValue);
	if (FAILED(hr))
	{
		return hr;
	}
	CString strPNPEntity,strKeyname,strDeviceIDval;
	pInstanceNameNode.Release();
	hr = pPropDotRefList->nextNode(&pInstanceNameNode);
	if (FAILED(hr) || !pInstanceNameNode)
	{
		return hr;
	}
	hr = ProcessPropertyDotReferenceNodes(pInstanceNameNode,&strPNPEntity,&strKeyname,&strDeviceIDval);
	CComPtr<IXMLDOMDocument> pDoc;
	hr = pInstanceNode->get_ownerDocument(&pDoc);
	if (FAILED(hr) || !pDoc)
	{
		ASSERT(0 && "could not get owner doc from pInstanceNode");
		return E_FAIL;
	}
	CString strPNPDeviceName = GetPNPNameByID(pDoc,CComBSTR(strDeviceIDval));
	if (FAILED(hr))
	{
		return hr;
	}
	ASSERT(strPNPEntity.CompareNoCase("Win32_PnPEntity") == 0 && "unexpected value for Dependent classname");
	ASSERT(strKeyname.CompareNoCase("DeviceID") == 0 && "unexpected value for Dependent Keybinding name");
	//we will create an arificial attribute "ASSOCNAME", which will be used to identify this device.
	m_mapNameValue.SetAt(_T("ASSOCNAME"),strAntecedentName + ":" + strDeviceIDval);
	m_mapNameValue.SetAt(_T("DeviceID"),strDeviceIDval);
	m_mapNameValue.SetAt(_T("DeviceName"),strPNPDeviceName);

	return hr;
}


//////////////////////////////////////////////////////////////////////////////////////////
//Retrives a value used to select appropriate description value for the class
//////////////////////////////////////////////////////////////////////////////////////////
CString CInstance::GetDescriptionForClass(CString strClass)
{
	//lookup a key which can uniquely identify an instance of a given class
	//for example, DeviceID for Printers
	if (strClass.CompareNoCase(_T("Win32_LogicalDisk")) == 0)
	{
		return "DeviceID";
	}
	if (strClass.CompareNoCase(_T("Win32_CodecFile")) == 0)
	{
		return "Description";
	}
	if (strClass.CompareNoCase(_T("Win32_ComputerSystem")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_OperatingSystem")) == 0)
	{
		return "Caption";
	}
	if (strClass.CompareNoCase(_T("Win32_LogicalMemoryConfiguration")) == 0)
	{
		return "TotalPhysicalMemory";
	}
	if (strClass.CompareNoCase(_T("Win32_PortResource")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_NetworkProtocol")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_Printer")) == 0)
	{
		return "DeviceID";
	}
	if (strClass.CompareNoCase(_T("Win32_PnPEntity")) == 0)
	{
		return "Description";
	}
	if (strClass.CompareNoCase(_T("Win32_StartupCommand")) == 0)
	{
		return "Command";
	}
	if (strClass.CompareNoCase(_T("Win32_ProgramGroup")) == 0)
	{
		return "GroupName";
	}
	if (strClass.CompareNoCase(_T("Win32_PNPAllocatedResource")) == 0)
	{
		//this is an artificial string created in CInstance::ProcessPNPAllocatedResource
		return "DeviceName";
	}
	if (strClass.CompareNoCase(_T("Win32_DriverVXD")) == 0)
	{
		return "Name";
	}

	return "";
}


//////////////////////////////////////////////////////////////////////////////////////////
//used to determine which mapped value to use to ID instances of the clas
//////////////////////////////////////////////////////////////////////////////////////////
CString CInstance::GetIDForClass(CString strClass)
{
	//lookup a key which can uniquely identify an instance of a given class
	//for example, DeviceID for Printers
	if (strClass.CompareNoCase(_T("Win32_LogicalDisk")) == 0)
	{
		return "DeviceID";
	}
	if (strClass.CompareNoCase(_T("Win32_CodecFile")) == 0)
	{
		return "Description";
	}
	if (strClass.CompareNoCase(_T("Win32_OperatingSystem")) == 0)
	{
		return "Caption";
	}
	if (strClass.CompareNoCase(_T("Win32_LogicalMemoryConfiguration")) == 0)
	{
		return "TotalPhysicalMemory";
	}
	if (strClass.CompareNoCase(_T("Win32_ComputerSystem")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_PortResource")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_NetworkProtocol")) == 0)
	{
		return "Name";
	}
	if (strClass.CompareNoCase(_T("Win32_Printer")) == 0)
	{
		return "DeviceID";
	}
	if (strClass.CompareNoCase(_T("Win32_PnPEntity")) == 0)
	{
		return "DeviceID";
	}
	if (strClass.CompareNoCase(_T("Win32_PNPAllocatedResource")) == 0)
	{
		//this is an artificial string created in CInstance::ProcessPNPAllocatedResource
		return "ASSOCNAME";
	}

	if (strClass.CompareNoCase(_T("Win32_ProgramGroup")) == 0)
	{
		return "GroupName";
	}
	if (strClass.CompareNoCase(_T("Win32_StartupCommand")) == 0)
	{
		return "Command";
	}
	if (strClass.CompareNoCase(_T("Win32_DriverVXD")) == 0)
	{
		return "Name";
	}

	return "";
}




//////////////////////////////////////////////////////////////////////////////////////////
//used when rolling back through history list, to find previous instance of a given class
//////////////////////////////////////////////////////////////////////////////////////////
CInstance* CHistoryParser::FindPreviousInstance(CInstance* pNewInstance)
{
	//for each existing instance pOld
	for(POSITION pos = m_listInstances.GetHeadPosition( );;)
	{
		if (!pos)
		{
			return NULL;
		}
		CInstance* pOld = (CInstance*) m_listInstances.GetNext(pos);
		if (pOld->GetClassName() == pNewInstance->GetClassName())
		{
			if (pOld->GetInstanceID() == pNewInstance->GetInstanceID())
			{
				return pOld;
			}
		}
		
	}
	return NULL;
}


void CHistoryParser::CreateChangeStrings(CInstance* pOld, CInstance* pNew)
{
	CTimeSpan tmsDelta;
	COleDateTime olTime(pNew->m_tmstamp.GetTime());
	if (!pOld )
	{
		ASSERT(pNew );
		tmsDelta = CTime::GetCurrentTime() - pNew->m_tmstamp;
		//change string should be "Delete"
		m_pHistCat->InsertRemoveLine(pNew->m_tmstamp ,pNew->GetClassFriendlyName(),pNew->GetInstanceDescription());

		m_fChangeLines = TRUE;
		return;
	}
	else if (!pNew)
	{
		ASSERT(pOld);
		tmsDelta = CTime::GetCurrentTime() - pOld->m_tmstamp;
		//change string should be "New"
		m_pHistCat->InsertAddLine(pNew->m_tmstamp,pOld->GetClassFriendlyName(),pOld->GetInstanceDescription());
		//v-stlowe 3/12/2001
		m_fChangeLines = TRUE;
		return;
	}
	else
	{

		ASSERT(pOld && pNew && "both pointers can't be null");
		tmsDelta = CTime::GetCurrentTime() - pNew->m_tmstamp;
		//for each Name&Value pair, get the name, and then use it to examine 
		//the associated value in pCompare's map
		CString strName, strValue,strCompareValue;

		if (pNew->GetChangeType().CompareNoCase(_T("New")) == 0)
		{
			tmsDelta = CTime::GetCurrentTime() - pNew->m_tmstamp;
			//change string should be "added"
			m_pHistCat->InsertAddLine(pNew->m_tmstamp ,pNew->GetClassFriendlyName(),pNew->GetInstanceDescription());
			m_fChangeLines = TRUE;
			return;
		}
		else if (pNew->GetChangeType().CompareNoCase(_T("Delete")) == 0)
		{
			tmsDelta = CTime::GetCurrentTime() - pNew->m_tmstamp;
			//change string should be "Deleted"
			m_pHistCat->InsertRemoveLine(pNew->m_tmstamp,pNew->GetClassFriendlyName(),pNew->GetInstanceDescription());
			m_fChangeLines = TRUE;
			return;
		}

		for(POSITION pos = pNew->m_mapNameValue.GetStartPosition();;pNew->m_mapNameValue.GetNextAssoc(pos,strName, strValue))
		{
			strCompareValue = _T("");
			if (!pOld->m_mapNameValue.Lookup(strName,strCompareValue))
			{
				//ASSERT(0 && "value not found in delta");
				//return E_FAIL;
				if (strName.CompareNoCase(_T("Change")) == 0)
				{
					VERIFY(pNew->m_mapNameValue.Lookup(strName,strCompareValue));
					if (strCompareValue.CompareNoCase(_T("New")) == 0)
					{
						m_pHistCat->InsertAddLine(pNew->m_tmstamp,pNew->GetClassFriendlyName(),pNew->GetInstanceDescription());
						m_fChangeLines = TRUE;
					}
					ASSERT(1);
				}
				continue;
			}
			else
			{
				pOld->m_mapNameValue.RemoveKey(strName);
			}
			
			if (strValue != strCompareValue)
			{

				m_pHistCat->InsertChangeLine(pNew->m_tmstamp,pNew->GetClassFriendlyName(),pNew->GetInstanceDescription(),strName,strValue,strCompareValue);
				m_fChangeLines = TRUE;

			}
			if(!pos)
			{
				break;
			}
		}
		//handle values that are mapOldInstance, and not the other map
		if (!pOld->m_mapNameValue.IsEmpty())
		{
			for(pos = pOld->m_mapNameValue.GetStartPosition();;pOld->m_mapNameValue.GetNextAssoc(pos,strName, strValue))
			{
				pOld->m_mapNameValue.GetNextAssoc(pos,strName, strValue);
				pNew->m_mapNameValue.SetAt(strName,strValue);
				if (!pos)
				{
					break;
				}
			}
		}


	}
}


//////////////////////////////////////////////////////////////////////////////////////////
//once the previous instance has been processed, previous instance should be removed and this instance should be added to list
//////////////////////////////////////////////////////////////////////////////////////////
void CHistoryParser::ResetInstance(CInstance* pOld, CInstance* pNew)
{
	POSITION pos = this->m_listInstances.Find(pOld);
	m_listInstances.SetAt(pos,pNew);
	delete pOld;

}


//////////////////////////////////////////////////////////////////////////////////////////
//Used to process a single instance from either history or snapshot
//////////////////////////////////////////////////////////////////////////////////////////
void CHistoryParser::ProcessInstance(CInstance* pNewInstance)
{
	//see if instance is in list of instances
	CInstance* pOld = FindPreviousInstance(pNewInstance);
	if (pOld)
	{
		CreateChangeStrings(pOld,pNewInstance);
		ResetInstance(pOld,pNewInstance);
	}
	//if this is from a Snapshot, just add it
	//if it is from a Delta, it should have a change type of "add", and we 
	//want to create a change string for it.
	else
	{
		CString strChange;
		if (pNewInstance->GetValueFromMap(_T("Change"),strChange))
		{
			//we have new Delta instance
			CreateChangeStrings(NULL,pNewInstance);
			m_listInstances.AddTail(pNewInstance);
		}
		else
		{
			//Instance is in snapshot, so we don't generate change lines
			m_listInstances.AddTail(pNewInstance);
		}
	}
}

/**************************************************************************
returns list of deltas and the snapshot node
/**************************************************************************/
HRESULT CHistoryParser::GetDeltaAndSnapshotNodes(CComPtr<IXMLDOMNodeList>& pDeltaList)
{
	CComPtr<IXMLDOMNode> pDataCollNode;
	HRESULT hr;
	hr = GetDataCollectionNode(m_pDoc,pDataCollNode);
	if (FAILED(hr) || !pDataCollNode)
	{
		//ASSERT(0 && "could not get datacollection node");
		return E_FAIL;
	}
	//all nodes directly under DATACOLLECTION should be either deltas or the snapshot
	hr = pDataCollNode->selectNodes(CComBSTR("*"),&pDeltaList);
	if (FAILED(hr) || !pDeltaList)
	{
		ASSERT(0 && "could not get pDeltaList");
		return E_FAIL;
	}
#ifndef _DEBUG
	return hr;
#endif
	long ll;
	hr = pDeltaList->get_length(&ll);
	return hr;
}

//////////////////////////////////////////////////////////////////////////////////////////
//gets IXMLDOMNodeList of instances from a specific delta or snapshot node
//////////////////////////////////////////////////////////////////////////////////////////
HRESULT CHistoryParser::GetInstanceNodeList(CString strClass,CComPtr<IXMLDOMNode> pDeltaNode, CComPtr<IXMLDOMNodeList>& pInstanceList)
{
	HRESULT hr;
	//CComBSTR bstrQuery;
	// the query will have to be in the form:
	// CIM/DECLARATION/DECLGROUP.WITHPATH/VALUE.OBJECTWITHPATH/INSTANCE[@CLASSNAME $ieq$ "WIN32_CODECFILE"]
	// or
	// CIM/DECLARATION/DECLGROUP.WITHPATH/VALUE.OBJECTWITHPATH/INSTANCE[@CLASSNAME $ieq$ "Win32_ComputerSystem"]
	// because we are querying a node, rather than a document (with which we could get
	// away with specifying only INSTANCE in the query

	//v-stlowe 1/29/2001 to fix Prefix whistler bug #279519
	//bstrQuery += "CIM/DECLARATION/DECLGROUP.WITHPATH/VALUE.OBJECTWITHPATH/INSTANCE[@CLASSNAME $ieq$ ";
	CComBSTR bstrQuery("CIM/DECLARATION/DECLGROUP.WITHPATH/VALUE.OBJECTWITHPATH/INSTANCE[@CLASSNAME $ieq$ ");
	
	//end v-stlowe
	bstrQuery += "\"";
	bstrQuery += CComBSTR(strClass);
	bstrQuery += "\"]";
	hr = pDeltaNode->selectNodes(bstrQuery,&pInstanceList);
	if (FAILED(hr) || !pInstanceList)
	{
		ASSERT(0 && "Could not get node list");
		return E_FAIL;
	}

	if (FAILED(hr))
	{
		ASSERT(0 && "Could not get node list length");
		
	}
	
	return hr;
}



//for a given Snapshot or Delta node, get all instances of a given class
HRESULT CHistoryParser::ProcessDeltaNode(CComPtr<IXMLDOMNode> pDeltaNode,CString strClass)
{
	CString strTime;
	HRESULT hr;
	int nTimeZone;
	hr = GetTimeStampFromFromD_or_SNodeNode(pDeltaNode, &strTime,nTimeZone);
	ASSERT(SUCCEEDED(hr) && "error getting timestamp for node");
	CTime tmDelta = GetDateFromString(strTime,nTimeZone);
	//TD: check for valid time range...
	//get list of all nodes of given class
	CComPtr<IXMLDOMNodeList> pInstanceNodeList;
	hr = GetInstanceNodeList(strClass,pDeltaNode,pInstanceNodeList);
	if (FAILED(hr) | ! pInstanceNodeList)
	{
		ASSERT(0 && "could not get instance list from Delta node");
		return E_FAIL;
	}
	//step through list, getting each instance
	long lListLen;
	hr = pInstanceNodeList->get_length(&lListLen);
	for(long i = 0;i < lListLen;i++)
	{
		CComPtr<IXMLDOMNode> pInstanceNode;
		hr = pInstanceNodeList->nextNode(&pInstanceNode);
		if (FAILED(hr) || ! pInstanceNode)
		{
			ASSERT(0 && "could not get node from instance list");
			return E_FAIL;
		}
		CInstance * pInstance = new CInstance(tmDelta,pInstanceNode,strClass);
		ProcessInstance(pInstance);
	}
	return hr;
}


//*************************************************************************
//Takes a list of delta nodes, and the name of a class
//**************************************************************************

HRESULT CHistoryParser::ProcessDeltas(CComPtr<IXMLDOMNodeList> pDeltaList,CString strClassName,int nDeltasBack)
{
	//for each node in list pNode
	long lListLen;
	HRESULT hr;
	hr = pDeltaList->get_length(&lListLen);
	if (FAILED(hr))
	{
		ASSERT(0 && "couldn't get list length");
	}
	if (0 == lListLen)
	{
		pDeltaList.Release();
		return S_FALSE;
	}
	
	for (long i = 0;i  < lListLen && i <= nDeltasBack;i++)
	{
		CComPtr<IXMLDOMNode> pNode;
		hr= pDeltaList->nextNode(&pNode);
		if (FAILED(hr) || !pNode)
		{ 
			ASSERT(0 && "could not get next delta node");
			pDeltaList.Release();
			return E_FAIL;
		}

		
//	here's problem  If we're using nDeltasBack method, do we need to compare dates?
/*		CTime tmDelta = GetDeltaTime(pNode);
		if (GetDeltaTime(pNode) >= this->m_tmBack)
		{
*/
			hr = ProcessDeltaNode(pNode,strClassName);
			if (FAILED(hr))
			{
				pDeltaList.Release();
				return hr;
			}
//		}
	}
	pDeltaList.Release();
	return S_OK;

}


//*************************************************************************
//Gets the DATACOLLECTION node, beneath which both the SNAPSHOT and the DELTA nodes reside
//**************************************************************************


HRESULT GetDataCollectionNode(CComPtr<IXMLDOMDocument> pXMLDoc,CComPtr<IXMLDOMNode>& pDCNode)
{
	//TD: find a way to do case-insensitive queries.
	HRESULT hr;
	if (!pXMLDoc)
	{
		return S_FALSE;
	}
	CComPtr<IXMLDOMNodeList> pNodeList;
	
	//find a change property; that way we know we have a delta
	hr = pXMLDoc->getElementsByTagName(CComBSTR("PROPERTY[@NAME $ieq$ \"CHANGE\"]"),&pNodeList);
	if (FAILED(hr) || !pNodeList)
	{
		ASSERT(0 && "Could not get node list");
		return E_FAIL;
	}
	CComPtr<IXMLDOMNode> pNode;
	hr = pNodeList->nextNode(&pNode);
	if (FAILED(hr) || !pNode)
	{
//		ASSERT(0 && "Could not get node from node list");
		return E_FAIL;
	}
	//loop till we get a node called "DATACOLLECTION"
	CComPtr<IXMLDOMNode> pParentNode;
	for(int i = 0;;i++)
	{
		hr = pNode->get_parentNode(&pParentNode);
		if (FAILED(hr) || !pParentNode)
		{
			ASSERT(0 && "Could not find DATACOLLECTION node");
			pDCNode = NULL;
			return E_FAIL;
		}
		pNode.Release();
		CComBSTR bstrName;
		pParentNode->get_nodeName(&bstrName);
		USES_CONVERSION;
		if (CString(bstrName).CompareNoCase(_T("DATACOLLECTION")) == 0)
		{
			pDCNode = pParentNode;
			return S_OK;
		}
		pNode = pParentNode;
		pParentNode.Release();
	}
	
	return S_OK;
}


//////////////////////////////////////////////////////////////////////////////////////////
//get timestamp of a delta or snapshot node
//////////////////////////////////////////////////////////////////////////////////////////
CTime GetDeltaTime(CComPtr<IXMLDOMNode> pDorSNode)
{
	CString strTime;
	int nTimeZone;
	GetTimeStampFromFromD_or_SNodeNode(pDorSNode,&strTime,nTimeZone);
	return GetDateFromString(strTime,nTimeZone);
}

//////////////////////////////////////////////////////////////////////////////////////////
//takes string format used in XML blob, creates a CTime
//////////////////////////////////////////////////////////////////////////////////////////



HRESULT GetTimeStampFromFromD_or_SNodeNode(CComPtr<IXMLDOMNode> pDorSNode,CString* pString, int& nTimeZone)
{
	HRESULT hr;
	CComVariant varTS;
	CComPtr<IXMLDOMElement> pTimestampElement;
	hr = pDorSNode->QueryInterface(IID_IXMLDOMElement,(void**) &pTimestampElement);
	if (FAILED(hr) || !pTimestampElement)
	{
		ASSERT(0 && "could not get attribute element");
	}
	hr = pTimestampElement->getAttribute(L"Timestamp_T0",&varTS);
	if (FAILED(hr) )
	{
		ASSERT(0 && "could not get timestamp value from attribute");
	}
	if (1 == hr)
	{
		//this may be snapshot node...try "Timestamp"
		hr = pTimestampElement->getAttribute(L"Timestamp",&varTS);
		if (FAILED(hr) )
		{
			ASSERT(0 && "could not get timestamp value from attribute");
		}
	}
	CComVariant varTzoneDeltaSeconds;
	hr = pTimestampElement->getAttribute(L"TimeZone",&varTzoneDeltaSeconds);
	if (FAILED(hr) ) //this will happen when loading WinME xml, which has no timezone info
	{
		varTzoneDeltaSeconds = 0;
	}
	//make sure we have an integer type
	hr = varTzoneDeltaSeconds.ChangeType(VT_INT);
	if (FAILED(hr) ) 
	{
		varTzoneDeltaSeconds = 0;
	}
	nTimeZone = varTzoneDeltaSeconds.intVal;
	USES_CONVERSION;
	pTimestampElement.Release();
	*pString = OLE2T(varTS.bstrVal);
	return hr;
}

//////////////////////////////////////////////////////////////////////
// utility functions
//////////////////////////////////////////////////////////////////////
CTime GetDateFromString(const CString& strDate, int nTimeZone)
{
	//requires linking to Shlwapi.lib
	CString strDateCopy(strDate);
	CString strDateSegment;

	//year is the 4 leftmost digits of date string
	strDateSegment = strDateCopy.Left(4);
	int nYear;
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nYear));
//	ASSERT(nYear == 1999 || nYear == 2000);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 4);
	
    //month is now the 2 leftmost digits of remaining date string
	int nMonth;
	strDateSegment = strDateCopy.Left(2);
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nMonth));
	ASSERT(nMonth >= 1 && nMonth <= 12);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 2);


	//day is now the 2 leftmost digits of remaining date string
	int nDay;
	strDateSegment = strDateCopy.Left(2);
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nDay));
	ASSERT(nDay >= 1 && nDay <= 31);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 2);

	//hour is now the 2 leftmost digits of remaining date string
	int nHour;
	strDateSegment = strDateCopy.Left(2);
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nHour));
	ASSERT(nHour >= 0 && nHour <= 24);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 2); 
	
	//Minute is now the 2 leftmost digits of remaining date string
	int nMin;
	strDateSegment = strDateCopy.Left(2);
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nMin));
	ASSERT(nMin >= 0 && nMin <= 59);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 2); 
	 

		//Minute is now the 2 leftmost digits of remaining date string
	int nSec;
	strDateSegment = strDateCopy.Left(2);
	VERIFY(StrToIntEx(strDateSegment,STIF_DEFAULT ,&nSec));
	ASSERT(nSec >= 0 && nSec <= 59);
	strDateCopy = strDateCopy.Right(strDateCopy.GetLength() - 2); 
	

	CTime tmTime(nYear,nMonth,nDay,nHour,nMin,nSec);
#ifdef _V_STLOWE
	CString strFMT;
	CString strTime;
	strFMT.LoadString(IDS_TIME_FORMAT);
	strTime =tmTime.FormatGmt("%A, %B %d, %Y");

#endif
	//Adjust for time zone
	CTimeSpan tspan(0,0,nTimeZone,0);
	tmTime -= tspan;

#ifdef _V_STLOWE
	strFMT.LoadString(IDS_TIME_FORMAT);
	strTime =tmTime.FormatGmt("%A, %B %d, %Y");

#endif
	return  tmTime;
}



//////////////////////////////////////////////////////////////////////////////////////////
//finds timestamp string for a given delta or snapshot node
//////////////////////////////////////////////////////////////////////////////////////////

CString GetPNPNameByID(CComPtr<IXMLDOMDocument> pDoc,CComBSTR bstrPNPID)
{
	HRESULT hr;
	CComPtr<IXMLDOMNodeList> pNodeList;
	CComBSTR bstrQuery("INSTANCE[@CLASSNAME $ieq$ \"WIN32_PNPeNTITY\"] /PROPERTY[@NAME $ieq$ \"Description\"]");
	hr = pDoc->getElementsByTagName(bstrQuery,&pNodeList);
	if (FAILED(hr) || !pNodeList)
	{
		ASSERT(0 && "WIN32_PNPeNTITY error getting node list");
		return "";
	}
	
	long lListLen;
	hr = pNodeList->get_length(&lListLen);
	ASSERT(lListLen > 0 && "No WIN32_PNPeNTITY nodes found to match query");
	for(long i = 0; i < lListLen;i++)
	{
		CComPtr<IXMLDOMNode> pNode;
		hr = pNodeList->nextNode(&pNode);
		if (FAILED(hr) || !pNode)
		{
			ASSERT(0 && "could not get next node from list");
			return "";
		}

		USES_CONVERSION;
		CComPtr<IXMLDOMNode> pIDNode;
		hr = pNode->get_nextSibling(&pIDNode);
		if (FAILED(hr) || !pNode)
		{
			ASSERT(0 && "could not get next node from list");
			return "";
		}
		//see if node's DeviceID subnode matches bstrPNPID
		CComBSTR bstrDeviceID;
		hr = pIDNode->get_text(&bstrDeviceID);
		ASSERT(SUCCEEDED(hr) && "could not get text from ID node");
		if (bstrDeviceID == bstrPNPID)
		{
			CComBSTR bstrDeviceDesc;
			hr = pNode->get_text(&bstrDeviceDesc);
			ASSERT(SUCCEEDED(hr) && "could not get text from Desc node");
			return OLE2T(bstrDeviceDesc);
		}
	}
	



	return "";
}

//////////////////////////////////////////////////////////////////////////////////////////
//returns true if any changes have been entered into CMSInfocategory data
//////////////////////////////////////////////////////////////////////////////////////////
BOOL CHistoryParser::AreThereChangeLines()
{
	return this->m_fChangeLines;
}

//////////////////////////////////////////////////////////////////////////////////////////
//gets (from resources strings) a human-readable name for a the class wrapped the the instance
//////////////////////////////////////////////////////////////////////////////////////////

CString CInstance::GetClassFriendlyName()
{
	CString strClassName = GetClassName();
	if (strClassName.CompareNoCase(_T("Win32_PNPAllocatedResource")) == 0)
	{
		VERIFY(m_mapNameValue.Lookup(_T("ANTECEDENT"),strClassName) && _T("Could not find antecedent"));
	}
	CString strFriendlyName;
	if (strClassName.CompareNoCase(_T("Win32_CodecFile")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_CODEC_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_ComputerSystem")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_COMPUTERSYSTEM_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_LogicalMemoryConfiguration")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_LOGICALMEMEORY_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_LogicalDisk")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_LOGICALDISK_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_IRQResource")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_IRQRESOURCE_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_DriverVXD")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_DRIVERVXD_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_DMAChannel")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_DMACHANNEL_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_DeviceMemoryAddress")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_DEVICEMEMORYADDRESS_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_NetworkProtocol")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_NETWORKPROTOCOL_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_OperatingSystem")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_OPERATINGSYSTEM_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_PNPAllocatedResource")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_PNPALLOCATEDRESOURCE_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_PNPEntity")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_PNPENTITY_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_PortResource")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_PORTRESOURCE_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_Printer")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_PRINTER_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_ProgramGroup")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_PROGRAMGROUP_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	if (strClassName.CompareNoCase(_T("Win32_StartupCommand")) == 0)
	{			
		VERIFY(strFriendlyName.LoadString(IDS_STARTUPCOMMAND_DESC) && _T("could not find string resource"));
		return strFriendlyName;
	}
	ASSERT(0 && "Unknown strClassName");
	return "";
}