//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1998 - 1999
//
//  File:       treedat_.cpp
//
//--------------------------------------------------------------------------



/////////////////////////////////////////////////////////////////////////////
// Miscellanea 
LPCWSTR g_lpszNullString = L"\0";


///////////////////////////////////////////////////////////////////////
// Global Helper functions

BOOL LoadContextMenuResources(MENUMAP* pMenuMap)
{
	HINSTANCE hInstance = _Module.GetModuleInstance();
	for (int i = 0; pMenuMap->ctxMenu[i].strName; i++)
	{
		if (0 == ::LoadString(hInstance, pMenuMap->dataRes[i].uResID, pMenuMap->dataRes[i].szBuffer, MAX_CONTEXT_MENU_STRLEN*2))
			return FALSE;
		pMenuMap->ctxMenu[i].strName = pMenuMap->dataRes[i].szBuffer;
		for (WCHAR* pCh = pMenuMap->dataRes[i].szBuffer; (*pCh) != NULL; pCh++)
		{
			if ( (*pCh) == L'\n')
			{
				pMenuMap->ctxMenu[i].strStatusBarText = (pCh+1);
				(*pCh) = NULL;
				break;
			}
		}
	}
	return TRUE;
}

BOOL LoadResultHeaderResources(RESULT_HEADERMAP* pHeaderMap, int nCols)
{
	HINSTANCE hInstance = _Module.GetModuleInstance();
	for ( int i = 0; i < nCols ; i++)
	{
		if ( 0 == ::LoadString(hInstance, pHeaderMap[i].uResID, pHeaderMap[i].szBuffer, MAX_RESULT_HEADER_STRLEN))
			return TRUE;
	}
	return TRUE;
}


////////////////////////////////////////////////////////////////////////
// CTreeNode

BEGIN_TOOLBAR_MAP(CTreeNode)
END_TOOLBAR_MAP()

BOOL CTreeNode::HasContainer(CContainerNode* pContainerNode)
{
	if (m_pContainer == NULL)
		return FALSE; // root
	if (m_pContainer == pContainerNode)
		return TRUE; // got it
	return m_pContainer->HasContainer(pContainerNode);
}

HRESULT CTreeNode::GetResultViewType(CComponentDataObject* pComponentData,
                                     LPOLESTR* ppViewType, 
                                     long* pViewOptions)
{
  if (pComponentData->IsMultiSelect())
  {
    *pViewOptions = MMC_VIEW_OPTIONS_MULTISELECT;
  }
  else
  {
	  *pViewOptions = MMC_VIEW_OPTIONS_NONE;
  }
	*ppViewType = NULL;
  return S_FALSE;
}

void CTreeNode::Show(BOOL bShow, CComponentDataObject* pComponentData)
{
	if (bShow)
	{
		ASSERT(m_dwNodeFlags & TN_FLAG_HIDDEN); // must be currently hidden
		SetFlagsDown(TN_FLAG_HIDDEN,FALSE); // mark it visible
		VERIFY(SUCCEEDED(pComponentData->AddNode(this)));
	}
	else
	{
		ASSERT(!(m_dwNodeFlags & TN_FLAG_HIDDEN)); // must be currently visible
		SetFlagsDown(TN_FLAG_HIDDEN,TRUE); // mark it hidden
		VERIFY(SUCCEEDED(pComponentData->DeleteNode(this)));
		if (IsContainer())
		{
			((CContainerNode*)this)->RemoveAllChildrenFromList();
			((CContainerNode*)this)->MarkEnumerated(FALSE);
		}
	}
}


void CTreeNode::SetFlagsDown(DWORD dwNodeFlags, BOOL bSet)
{
	if (bSet)
		m_dwNodeFlags |= dwNodeFlags; 
	else
		m_dwNodeFlags &= ~dwNodeFlags;		
}

void CTreeNode::SetFlagsUp(DWORD dwNodeFlags, BOOL bSet)
{
	if (bSet)
		m_dwNodeFlags |= dwNodeFlags; 
	else
		m_dwNodeFlags &= ~dwNodeFlags;		
	if (m_pContainer != NULL)
	{
		ASSERT(m_pContainer != this);
		m_pContainer->SetFlagsUp(dwNodeFlags, bSet);
	}
}

//
// Property Page methods
//
void CTreeNode::ShowPageForNode(CComponentDataObject* pComponentDataObject) 
{
	ASSERT(pComponentDataObject != NULL);
	pComponentDataObject->GetPropertyPageHolderTable()->BroadcastSelectPage(this, -1);
}

BOOL CTreeNode::HasPropertyPages(DATA_OBJECT_TYPES, 
                                 BOOL* pbHideVerb, 
                                 CNodeList*) 
{ 
  *pbHideVerb = TRUE; 
  return FALSE; 
}

//
// Menu Item methods
//
HRESULT CTreeNode::OnAddMenuItems(IContextMenuCallback2* pContextMenuCallback2, 
									                DATA_OBJECT_TYPES type,
									                long *pInsertionAllowed,
                                  CNodeList* pNodeList)
{
	HRESULT hr = S_OK;
	LPCONTEXTMENUITEM2 pContextMenuItem = NULL;
  
  if (pNodeList->GetCount() == 1) // single selection
  {
    pContextMenuItem = OnGetContextMenuItemTable();
	  if (pContextMenuItem == NULL)
		  return hr;

    //
	  // Loop through and add each of the menu items
    //
	  for (LPCONTEXTMENUITEM2 m = pContextMenuItem; m->strName; m++)
	  {
		  if (
				  ( (*pInsertionAllowed & CCM_INSERTIONALLOWED_NEW) &&
					  (m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_NEW) ) ||
				  ( (*pInsertionAllowed & CCM_INSERTIONALLOWED_TASK) &&
					  (m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_TASK) ) ||
				  ( (*pInsertionAllowed & CCM_INSERTIONALLOWED_VIEW) &&
					  (m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_VIEW) ) ||
				  ( (*pInsertionAllowed & CCM_INSERTIONALLOWED_TOP) &&
					  (m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_TOP) )
		     )
		  {
			  // make a temporary copy that can be modified
			  CONTEXTMENUITEM2 tempItem;
			  ::memcpy(&tempItem, m, sizeof(CONTEXTMENUITEM2));
			  if (OnAddMenuItem(&tempItem, pInsertionAllowed))
			  {
				  hr = pContextMenuCallback2->AddItem(&tempItem);
				  if (FAILED(hr))
					  break;
			  }
		  }
	  }
  }
  else if (pNodeList->GetCount() > 1) // multiple selection
  {
    hr = OnAddMenuItemsMultipleSelect(pContextMenuCallback2, 
									                    type,
									                    pInsertionAllowed,
                                      pNodeList);
  }
	return hr;
}

BOOL CTreeNode::OnSetRenameVerbState(DATA_OBJECT_TYPES, 
                                     BOOL* pbHide, 
                                     CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetDeleteVerbState(DATA_OBJECT_TYPES, 
                                     BOOL* pbHide, 
                                     CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetRefreshVerbState(DATA_OBJECT_TYPES, 
                                      BOOL* pbHide, 
                                      CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetCutVerbState(DATA_OBJECT_TYPES, 
                                  BOOL* pbHide, 
                                  CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetCopyVerbState(DATA_OBJECT_TYPES, 
                                   BOOL* pbHide, 
                                   CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetPasteVerbState(DATA_OBJECT_TYPES, 
                                    BOOL* pbHide, 
                                    CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

BOOL CTreeNode::OnSetPrintVerbState(DATA_OBJECT_TYPES, 
                                    BOOL* pbHide, 
                                    CNodeList*) 
{ 
  *pbHide = TRUE; 
  return FALSE; 
}

MMC_CONSOLE_VERB CTreeNode::GetDefaultVerb(DATA_OBJECT_TYPES type, 
                                           CNodeList* pNodeList)
{ 
	ASSERT((type == CCT_SCOPE) || (type == CCT_RESULT));
	if (type == CCT_SCOPE)
		return MMC_VERB_OPEN; 
	BOOL bHideVerbDummy;
	if (HasPropertyPages(type, &bHideVerbDummy, pNodeList))
		return MMC_VERB_PROPERTIES;
	return MMC_VERB_NONE;
}


void CTreeNode::OnSetVerbState(LPCONSOLEVERB pConsoleVerb, 
                               DATA_OBJECT_TYPES type,
                               CNodeList* pNodeList)
{
  //
  // Use the virtual functions to get the verb state
  //
  BOOL bHideCut;
  BOOL bCanCut = OnSetCutVerbState(type, &bHideCut, pNodeList);
  pConsoleVerb->SetVerbState(MMC_VERB_CUT, HIDDEN, bHideCut);
  pConsoleVerb->SetVerbState(MMC_VERB_CUT, ENABLED, bCanCut);


  BOOL bHideCopy;
  BOOL bCanCopy = OnSetCopyVerbState(type, &bHideCopy, pNodeList);
  pConsoleVerb->SetVerbState(MMC_VERB_COPY, HIDDEN, bHideCopy);
	pConsoleVerb->SetVerbState(MMC_VERB_COPY, ENABLED, bCanCopy);


  BOOL bHidePaste;
  BOOL bCanPaste = OnSetPasteVerbState(type, &bHidePaste, pNodeList);
  pConsoleVerb->SetVerbState(MMC_VERB_PASTE, HIDDEN, bHidePaste);
	pConsoleVerb->SetVerbState(MMC_VERB_PASTE, ENABLED, bCanPaste);


  BOOL bHidePrint;
  BOOL bCanPrint = OnSetPrintVerbState(type, &bHidePrint, pNodeList);
	pConsoleVerb->SetVerbState(MMC_VERB_PRINT, HIDDEN, bHidePrint);
	pConsoleVerb->SetVerbState(MMC_VERB_PRINT, ENABLED, bCanPrint);

  BOOL bHideRename;
  BOOL bCanRename = OnSetRenameVerbState(type, &bHideRename, pNodeList);
	pConsoleVerb->SetVerbState(MMC_VERB_RENAME, HIDDEN, bHideRename);
	pConsoleVerb->SetVerbState(MMC_VERB_RENAME, ENABLED, bCanRename);

	// MMC_VERB_PROPERTIES
	BOOL bHideProperties;
	BOOL bHasProperties = HasPropertyPages(type, &bHideProperties, pNodeList);
	pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, bHasProperties);
	pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, HIDDEN, bHideProperties);

	// MMC_VERB_DELETE
	BOOL bHideDelete;
	BOOL bCanDelete = OnSetDeleteVerbState(type, &bHideDelete, pNodeList);
	pConsoleVerb->SetVerbState(MMC_VERB_DELETE, ENABLED, bCanDelete);
	pConsoleVerb->SetVerbState(MMC_VERB_DELETE, HIDDEN, bHideDelete);

	// MMC_VERB_REFRESH
	BOOL bHideRefresh;
	BOOL bCanRefresh = OnSetRefreshVerbState(type, &bHideRefresh, pNodeList);
	pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, bCanRefresh);
	pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, bHideRefresh);
}

HRESULT CTreeNode::OnSetToolbarVerbState(IToolbar*, 
                                         CNodeList*)
{
  HRESULT hr = S_OK;

  //
  // Set the button state for each button on the toolbar using
  // hr = pToolbar->SetButtonState(event, MMC_BUTTON_STATE, bState);
  //
  return hr;
}   

void CTreeNode::DeleteHelper(CComponentDataObject* pComponentData)
{
	ASSERT(pComponentData != NULL);
	ASSERT(m_pContainer != NULL);
	ASSERT((CTreeNode*)m_pContainer != this);
	CContainerNode* pCont = m_pContainer;
	VERIFY(m_pContainer->RemoveChildFromList(this));
	ASSERT(m_pContainer == NULL);
	m_pContainer = pCont; // not in the container's list of children, but still needed
	
	// remove from UI only if the container is visible
	if (pCont->IsVisible())
		VERIFY(SUCCEEDED(pComponentData->DeleteNode(this))); // remove from the UI
}

void CTreeNode::IncrementSheetLockCount() 
{ 
	++m_nSheetLockCount; 
	if (m_pContainer != NULL) 
		m_pContainer->IncrementSheetLockCount(); 
}

void CTreeNode::DecrementSheetLockCount() 
{ 
	--m_nSheetLockCount; 
	if (m_pContainer != NULL) 
		m_pContainer->DecrementSheetLockCount();
}

void CTreeNode::OnPropertyChange(CComponentDataObject* pComponentData, 
									BOOL, long changeMask)
{
	// function called when the PPHolder successfully updated the node
	ASSERT(pComponentData != NULL);
	VERIFY(SUCCEEDED(pComponentData->ChangeNode(this, changeMask)));
}

void CTreeNode::OnCreateSheet() 
{
	++m_nSheetCount; 
	IncrementSheetLockCount();
	SetFlagsUp(TN_FLAG_HAS_SHEET, TRUE);
}

void CTreeNode::OnDeleteSheet() 
{ 
	DecrementSheetLockCount();
	--m_nSheetCount; 
	SetFlagsUp(TN_FLAG_HAS_SHEET,FALSE);
}

////////////////////////////////////////////////////////////////////////
// CContainerNode

void CContainerNode::IncrementThreadLockCount() 
{ 
	++m_nThreadLockCount; 
	if (m_pContainer != NULL) 
		m_pContainer->IncrementThreadLockCount(); 
}

void CContainerNode::DecrementThreadLockCount() 
{ 
	--m_nThreadLockCount; 
	if (m_pContainer != NULL) 
		m_pContainer->DecrementThreadLockCount();
}

BOOL CContainerNode::OnRefresh(CComponentDataObject* pComponentData,
                               CNodeList* pNodeList)
{
  BOOL bRet = TRUE;
  if (pNodeList->GetCount() == 1) // single selection
  {
	  if (IsSheetLocked())
	  {
		  if (!CanCloseSheets())
			  return FALSE;
		  pComponentData->GetPropertyPageHolderTable()->DeleteSheetsOfNode(this);
	  }
	  ASSERT(!IsSheetLocked());

	  RemoveAllChildrenHelper(pComponentData);
	  ASSERT(!HasChildren());
	  OnEnumerate(pComponentData);
	  AddCurrentChildrenToUI(pComponentData);
	  MarkEnumerated();
  }
  else // multiple selection
  {
    POSITION pos = pNodeList->GetHeadPosition();
    while (pos != NULL)
    {
      CTreeNode* pNode = pNodeList->GetNext(pos);
      ASSERT(pNode != NULL);

      //
      // Have each node refresh itself
      //
      CNodeList nodeList;
      nodeList.AddTail(pNode);

      if (!pNode->OnRefresh(pComponentData, &nodeList))
      {
        bRet = FALSE;
      }
    }
  }
	return bRet;
}

BOOL CContainerNode::RemoveChildFromList(CTreeNode* p) 
{ 
  if (p->IsContainer())
  {
		if (m_containerChildList.RemoveNode(p))
		{
			p->m_pContainer = NULL; 
			return TRUE;
		}
  }
  else
  {
    if (m_leafChildList.RemoveNode(p))
    {
      p->m_pContainer = NULL;
      return TRUE;
    }
  }
	return FALSE;
}

void CContainerNode::RemoveAllChildrenHelper(CComponentDataObject* pComponentData)
{
	ASSERT(pComponentData != NULL);
	// remove from the UI
	VERIFY(SUCCEEDED(pComponentData->RemoveAllChildren(this)));
	// remove from memory, recursively from the bottom
	RemoveAllChildrenFromList();
}

void CContainerNode::AddCurrentChildrenToUI(CComponentDataObject* pComponentData)
{
	POSITION pos;

  //
  // Add leaves
  //
	for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
	{
		CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
		VERIFY(SUCCEEDED(pComponentData->AddNode(pCurrentChild)));
	}

  //
  // Add Containers
  //
	for( pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
	{
		CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
		VERIFY(SUCCEEDED(pComponentData->AddNode(pCurrentChild)));
	}
}

void CContainerNode::SetFlagsDown(DWORD dwNodeFlags, BOOL bSet)
{
	CTreeNode::SetFlagsDown(dwNodeFlags,bSet);
	// scan the list of children
	POSITION pos;
	for( pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
	{
		CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
		pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
	}
	for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
	{
		CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
		pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
	}
}

void CContainerNode::SetFlagsOnNonContainers(DWORD dwNodeFlags, BOOL bSet)
{
	// do not set on urselves, we are a container
	// scan the list of children
	POSITION pos;
	for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
	{
		CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
  	pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
	}

  for (pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
  {
    CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
  	((CContainerNode*)pCurrentChild)->SetFlagsOnNonContainers(dwNodeFlags,bSet);
  }
}

BOOL CContainerNode::AddChildToList(CTreeNode* p) 
{ 
  BOOL bRet = FALSE;
	p->m_pContainer = this;
  if (p->IsContainer())
  {
    bRet = NULL != m_containerChildList.AddTail(p);
  }
  else
  {
    bRet = NULL != m_leafChildList.AddTail(p);
  }
	return bRet; 
}

BOOL CContainerNode::FindChild(CTreeNode* pNode, CTreeNode** ppContainer)
{
	*ppContainer = NULL;
	if (pNode == NULL)
		return FALSE; // no sense in continuing
	if (pNode == this)
	{
		*ppContainer = m_pContainer; 
		return TRUE; // the node is ourselves
	}

  //
  // If we are looking for a leaf node search the list of leaves first
  //
  if (!pNode->IsContainer())
  {
    POSITION pos;
    for (pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
    {
      CLeafNode* pCurrentLeafNode = (CLeafNode*)m_leafChildList.GetNext(pos);
      ASSERT(pCurrentLeafNode != NULL);

      if (pCurrentLeafNode == pNode)
      {
        *ppContainer = this;
        return TRUE;
      }
    }
  }

  //
	// scan and recurse the containers if necessary
  //
	POSITION contPos;
	for( contPos = m_containerChildList.GetHeadPosition(); contPos != NULL; )
	{
		CContainerNode* pCurrentChild = (CContainerNode*)m_containerChildList.GetNext(contPos);
		ASSERT(pCurrentChild != NULL);

		if (pCurrentChild == pNode)
		{
			*ppContainer = this;
			return TRUE;  // we directly contain the node
		}

    //
		// if the current node is a container, look inside it
    //
		if (pCurrentChild->FindChild(pNode,ppContainer))
    {
			return TRUE; // got it in the recursion
    }
	}
	return FALSE; // not found
}

BOOL CContainerNode::AddChildToListAndUI(CTreeNode* pChildToAdd, CComponentDataObject* pComponentData)
{
	ASSERT(pComponentData != NULL);
	VERIFY(AddChildToList(pChildToAdd)); // at the end of the list of children
	ASSERT(pChildToAdd->GetContainer() == this); // inserted underneath

	// add to UI only if currently visible and already expanded
	if (!IsVisible() || !IsExpanded())
		return TRUE;
	return SUCCEEDED(pComponentData->AddNode(pChildToAdd)); // add to the UI
}

BOOL CContainerNode::AddChildToListAndUISorted(CTreeNode* pChildToAdd, CComponentDataObject* pComponentData)
{
	ASSERT(pComponentData != NULL);
	VERIFY(AddChildToListSorted(pChildToAdd, pComponentData));
	ASSERT(pChildToAdd->GetContainer() == this); // inserted underneath

	// add to UI only if currently visible and already expanded
	if (!IsVisible() || !IsExpanded())
		return TRUE;
	return SUCCEEDED(pComponentData->AddNodeSorted(pChildToAdd)); // add to the UI
}

BOOL CContainerNode::AddChildToListSorted(CTreeNode* p, CComponentDataObject*)
{
  //
  // Containers will be sorted with respect to containers and leaves will be
  // sorted with respect to leaves but they won't be intermingled.
  //
	p->m_pContainer = this;
  
  CNodeList* pChildNodeList = NULL;
  if (p->IsContainer())
  {
    pChildNodeList = &m_containerChildList;
  }
  else
  {
    pChildNodeList = &m_leafChildList;
  }

  //
  // Find the position to insert the node in the list in sorted order
  //
  POSITION pos = pChildNodeList->GetHeadPosition();
  while (pos != NULL)
  {
    CTreeNode* pNodeInList = pChildNodeList->GetAt(pos);
    if (_wcsicoll(p->GetDisplayName(), pNodeInList->GetDisplayName()) < 0)
    {
      break;
    }
    pChildNodeList->GetNext(pos);
  }
  if (pos == NULL)
  {
	  return NULL != pChildNodeList->AddTail(p); 
  }
  return NULL != pChildNodeList->InsertBefore(pos, p);
}

void CContainerNode::RemoveAllChildrenFromList() 
{
  RemoveAllContainersFromList();
  RemoveAllLeavesFromList();
}

int CContainerNode::Compare(CTreeNode* pNodeA, CTreeNode* pNodeB, int nCol, LPARAM)
{
	// default sorting behavior
	LPCTSTR lpszA = pNodeA->GetString(nCol);
	LPCTSTR lpszB = pNodeB->GetString(nCol);
	// cannot process NULL strings, have to use ""
	ASSERT(lpszA != NULL);
	ASSERT(lpszB != NULL);
	return _tcsicoll( (lpszA != NULL) ? lpszA : g_lpszNullString, (lpszB != NULL) ? lpszB : g_lpszNullString);
}

void CContainerNode::ForceEnumeration(CComponentDataObject* pComponentData)
{
	if (IsEnumerated())
		return;
	OnEnumerate(pComponentData);
	MarkEnumerated();
}

void CContainerNode::MarkEnumerated(BOOL bEnum) 
{ 
	ASSERT(IsContainer()); 
	if (bEnum)
		m_dwNodeFlags |= TN_FLAG_CONTAINER_ENUM;
	else
		m_dwNodeFlags &= ~TN_FLAG_CONTAINER_ENUM;
}

void CContainerNode::MarkEnumeratedAndLoaded(CComponentDataObject* pComponentData)
{
	MarkEnumerated();
	OnChangeState(pComponentData); // move to loading
	OnChangeState(pComponentData); // move to loaded
}


/////////////////////////////////////////////////////////////////////////////
// CBackgroundThread

CBackgroundThread::CBackgroundThread()
{
	m_pQueryObj = NULL;
	m_bAutoDelete = FALSE;
	m_bAbandoned = FALSE;
	m_pContNode = NULL;
	m_hEventHandle = NULL;
	ExceptionPropagatingInitializeCriticalSection(&m_cs);
	m_nQueueCountMax = 10; 
}

CBackgroundThread::~CBackgroundThread()
{
	TRACE(_T("CBackgroundThread::~CBackgroundThread()\n"));
	ASSERT(IsAbandoned() || IsQueueEmpty());
	::DeleteCriticalSection(&m_cs);
	if (m_hEventHandle != NULL)
	{
		VERIFY(::CloseHandle(m_hEventHandle));
		m_hEventHandle = NULL;
	}
	if (m_pQueryObj != NULL)
	{
		delete m_pQueryObj;
		m_pQueryObj = NULL;
	}
}

void CBackgroundThread::SetQueryObj(CQueryObj* pQueryObj) 
{ 
	ASSERT(pQueryObj != NULL);
	m_pQueryObj = pQueryObj;
	m_pQueryObj->SetThread(this);
}

BOOL CBackgroundThread::Start(CMTContainerNode* pNode, CComponentDataObject* pComponentData)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	ASSERT(m_pContNode == NULL);
	m_pContNode = pNode;

	m_hHiddenWnd = pComponentData->GetHiddenWindow();

	ASSERT(m_hEventHandle == NULL); // cannot call start twice or reuse the same C++ object
	m_hEventHandle = ::CreateEvent(NULL,TRUE /*bManualReset*/,FALSE /*signalled*/, NULL);
	if (m_hEventHandle == NULL)
		return FALSE;
	return CreateThread();
}

int CBackgroundThread::Run()
{
	ASSERT(m_pContNode != NULL);
	ASSERT(m_pQueryObj != NULL);
	TRACE(_T("CBackgroundThread::Run() started\n"));

	while (m_pQueryObj->Enumerate());

	// before exiting, have to make sure there are no items in the queue
	if (!IsQueueEmpty())
		VERIFY(PostHaveData());

	VERIFY(PostExiting());

	// wait for the main thread to acknowledge the exiting message
	WaitForExitAcknowledge();

	ASSERT(IsAbandoned() || IsQueueEmpty()); // we cannot lose items in the queue
	TRACE(_T("CBackgroundThread::Run() terminated\n"));
	return 0;
}


void CBackgroundThread::Abandon()
{
	Lock();
	TRACE(_T("CBackgroundThread::Abandon()\n"));
	m_bAutoDelete = TRUE;
	m_bAbandoned = TRUE;
	Unlock();
  VERIFY(0 != ::SetEvent(m_hEventHandle));
}

BOOL CBackgroundThread::IsAbandoned()
{
	Lock();
	BOOL b = m_bAbandoned;
	Unlock();
	return b;
}

BOOL CBackgroundThread::OnAddToQueue(INT_PTR nCount) 
{
  BOOL bPostedMessage = FALSE;
	if (nCount >= m_nQueueCountMax)
  {
		VERIFY(PostHaveData());
    bPostedMessage = TRUE;
  }
  return bPostedMessage;
}


CObjBase* CBackgroundThread::RemoveFromQueue()
{
	Lock();
	ASSERT(m_pQueryObj != NULL);
	CObjBaseList* pQueue = m_pQueryObj->GetQueue();
	CObjBase* p =  pQueue->IsEmpty() ? NULL : pQueue->RemoveHead(); 
	Unlock();
	return p;
}

BOOL CBackgroundThread::IsQueueEmpty()
{
	Lock();
	ASSERT(m_pQueryObj != NULL);
	CObjBaseList* pQueue = m_pQueryObj->GetQueue();
	BOOL bRes = pQueue->IsEmpty(); 
	Unlock();
	return bRes;
}


BOOL CBackgroundThread::PostHaveData()
{
	return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadHaveDataNotificationMessage,
							(WPARAM)m_pContNode, (LPARAM)0);
}

BOOL CBackgroundThread::PostError(DWORD dwErr) 
{ 
	return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadErrorNotificationMessage,
							(WPARAM)m_pContNode, (LPARAM)dwErr);
}

BOOL CBackgroundThread::PostExiting()
{
	return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadExitingNotificationMessage,
							(WPARAM)m_pContNode, (LPARAM)0);
}


BOOL CBackgroundThread::PostMessageToComponentDataRaw(UINT Msg, WPARAM wParam, LPARAM lParam)
{
	BOOL b = IsAbandoned();
	if (b)
  {
		return TRUE; // no need to post
  }

	ASSERT(m_pContNode != NULL);

	ASSERT(m_hHiddenWnd != NULL);
	ASSERT(::IsWindow(m_hHiddenWnd));
	return ::PostMessage(m_hHiddenWnd, Msg, wParam, lParam);
}


void CBackgroundThread::WaitForExitAcknowledge() 
{
	VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(m_hEventHandle,INFINITE)); 
}

////////////////////////////////////////////////////////////////////////
// CMTContainerNode


CMTContainerNode::~CMTContainerNode()
{
	ASSERT(m_pThread == NULL);
}


BOOL CMTContainerNode::OnEnumerate(CComponentDataObject* pComponentData, BOOL bAsync)
{
	OnChangeState(pComponentData);
	VERIFY(StartBackgroundThread(pComponentData, bAsync));
	return FALSE; // children not added, the thread will add them later
}


BOOL CMTContainerNode::OnRefresh(CComponentDataObject* pComponentData,
                                 CNodeList* pNodeList)
{
  BOOL bRet = TRUE;

  if (pNodeList->GetCount() == 1)  // single selection
  {
	  BOOL bLocked = IsThreadLocked();
	  ASSERT(!bLocked); // cannot do refresh on locked node, the UI should prevent this
	  if (bLocked)
		  return FALSE; 
	  if (IsSheetLocked())
	  {
		  if (!CanCloseSheets())
        {
           pComponentData->GetPropertyPageHolderTable()->BroadcastSelectPage(this, -1);
			  return FALSE;
        }
		  pComponentData->GetPropertyPageHolderTable()->DeleteSheetsOfNode(this);
	  }
	  ASSERT(!IsSheetLocked());

	  RemoveAllChildrenHelper(pComponentData);
	  ASSERT(!HasChildren());
	  OnEnumerate(pComponentData); // will spawn a thread to do enumeration
	  MarkEnumerated();
  }
  else // multiple selection
  {
    POSITION pos = pNodeList->GetHeadPosition();
    while (pos != NULL)
    {
      CTreeNode* pNode = pNodeList->GetNext(pos);
      ASSERT(pNode != NULL);

      CNodeList nodeList;
      nodeList.AddTail(pNode);

      if (!pNode->OnRefresh(pComponentData, &nodeList))
      {
        bRet = FALSE;
      }
    }
  }
	return TRUE;
}

void CMTContainerNode::AbandonThread(CComponentDataObject* pComponentData)
{
	if(m_pThread == NULL) // nothing running
		return;
	m_pThread->Abandon();
	m_pThread = NULL;
	pComponentData->GetRunningThreadTable()->Remove(this);
}



BOOL CMTContainerNode::StartBackgroundThread(CComponentDataObject* pComponentData, BOOL bAsync)
{
	ASSERT(m_pThread == NULL); // nothing running

	// notify the UI to change icon, if needed
	VERIFY(SUCCEEDED(pComponentData->ChangeNode(this, CHANGE_RESULT_ITEM_ICON)));
	m_pThread = CreateThreadObject();
	ASSERT(m_pThread != NULL);
	m_pThread->SetQueryObj(OnCreateQuery());
	BOOL bRes =  m_pThread->Start(this, pComponentData);
	if (bRes)
	{
		pComponentData->GetRunningThreadTable()->Add(this);
		// we need to call UpdateVerbState() because the lock count changed
		// by adding the node from the running thread table
		VERIFY(SUCCEEDED(pComponentData->UpdateVerbState(this)));
	}

  //
  // If we don't want this call to be asynchronous then we have to wait for
  // the thread to finish
  //
  if (!bAsync)
  {
    pComponentData->WaitForThreadExitMessage(this);
  }
	return bRes;
}

void CMTContainerNode::OnThreadHaveDataNotification(CComponentDataObject* pComponentDataObject)
{
	ASSERT(m_pThread != NULL);
	ASSERT(IsThreadLocked());
	// do data transfer from thread queue
	CObjBase* p = m_pThread->RemoveFromQueue();
	while (p)
	{
		// add new node to the list of children and propagate to the UI
		OnHaveData(p,pComponentDataObject);
    p = m_pThread->RemoveFromQueue();
	}
}

void CMTContainerNode::OnThreadErrorNotification(DWORD dwErr, CComponentDataObject*)
{
	ASSERT(m_pThread != NULL);
	ASSERT(IsThreadLocked());
	OnError(dwErr);
}

void CMTContainerNode::OnThreadExitingNotification(CComponentDataObject* pComponentDataObject)
{
	ASSERT(m_pThread != NULL);
	ASSERT(IsThreadLocked());
#if (TRUE)
	// let the thread know it can shut down
	m_pThread->AcknowledgeExiting();
	VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(m_pThread->m_hThread,INFINITE));
	OnChangeState(pComponentDataObject);
	delete m_pThread;
	m_pThread = NULL;
	pComponentDataObject->GetRunningThreadTable()->Remove(this);
	// we need to call UpdateVerbState() because the lock count changed
	// by removing the node from the running thread table
	VERIFY(SUCCEEDED(pComponentDataObject->UpdateVerbState(this)));

  TRACE(_T("OnThreadExitingNotification()\n"));

#else // maybe better way of doing it???
	// we are going to detach from the thread, so make copies of variables
	HANDLE hThread = m_pThread->m_hThread;
	CBackgroundThread* pThread = m_pThread;
	AbandonThread(pComponentDataObject); // sets m_pThread = NULL
	// acknowledge to thread
	pThread->AcknowledgeExiting();
	VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(hThread,INFINITE));
	OnChangeState(pComponentDataObject);
#endif

	VERIFY(SUCCEEDED(pComponentDataObject->SortResultPane(this)));
}




///////////////////////////////////////////////////////////////////////////////