/*======================================================================================//
|  Process Control                                                                      //
|                                                                                       //
|Copyright (c) 1998  Sequent Computer Systems, Incorporated.  All rights reserved.      //
|                                                                                       //
|File Name:    RootFolder.cpp                                                           //
|                                                                                       //
|Description:  Class implemention for the root node                                     //
|                                                                                       //
|Created:      Paul Skoglund 07-1998                                                    //
|                                                                                       //
|Rev History:                                                                           //
|                                                                                       //
|=======================================================================================*/


#include "StdAfx.h"

#include "BaseNode.h"
#include "ProcConLibMsg.h"
#include "RootPages.h"

#include "Container.h"


using std::list<CBaseNode *>;

const GUID         CRootFolder::m_GUID   =   {0xff9baf5f,0x064e,0x11d2,{0x80, 0x14,0x00,0x10,0x4b,0x9a,0x31,0x06} };
const TCHAR *const CRootFolder::m_szGUID = _T("{ff9baf5f-064e-11d2-8014-00104b9a3106}");


///////////////////////////////////////////////////////////////////////////
// CRootFolder

const CONTEXTMENUITEMBYID CRootFolder::TaskMenuItems[] = 
{ 
  { IDS_ROOT_CONNECT, ID_ROOT_CONNECT, ID_ROOT_CONNECT, CCM_INSERTIONPOINTID_PRIMARY_TOP },
  { 0,                0,               0,               0 },
};

CRootFolder::CRootFolder() : CBaseNode(ROOT_NODE), m_ID(0), m_ParentID(0), m_NodeList(0), 
                             m_bUseLocalComputer(TRUE), 
														 m_bDirty(FALSE), 
														 m_hPC(0), m_PCLastError(0), m_ipConsole2(NULL)
{
  LoadStringHelper(m_name, IDS_ROOT_FOLDER);
  m_longname = m_name;

  //LoadStringHelper(m_TypeDescriptionStr, IDS_TYPE_DESCRIPTION);
  LoadStringHelper(m_DescriptionStr,     IDS_DESCRIPTION);

  memset(m_Computer, 0, sizeof(m_Computer));
}

CRootFolder::~CRootFolder()
{
  ATLTRACE( _T("~CRootFolder <%s>\n"), GetNodeName() );

  FreeNodes();
  if (m_hPC)
    VERIFY( PCClose(m_hPC) );

  ATLTRACE( _T("~CRootFolder end\n"));
}

void CRootFolder::FreeNodes()
{
  ATLTRACE( _T("CRootFolder::FreeNodes()\n"));
  for (list<CBaseNode *>::iterator i = m_NodeList.begin(); i != m_NodeList.end(); ++i)
  {
    (*i)->Release();
    //delete *i;
  }  
  m_NodeList.clear();
}

const PCid CRootFolder::GetPCid()
{
  ASSERT(!GetParentNode());

  /*
  if (
      // retry code now in the ProcConLib/service
      m_PCLastError == ERROR_NO_DATA            ||
      m_PCLastError == ERROR_BROKEN_PIPE        ||
      m_PCLastError == ERROR_BAD_PIPE           ||
      m_PCLastError == ERROR_PIPE_NOT_CONNECTED ||
      m_PCLastError == ERROR_NETNAME_DELETED 
      )
  {
    m_PCLastError = 0;
    if (m_hPC)
      VERIFY( PCClose(m_hPC));
    m_hPC = 0;
  }
  */

  if (m_hPC)
    return m_hPC;

  ATLTRACE(_T("Opening %s.\n"), GetComputerDisplayName());

  m_hPC = PCOpen(m_bUseLocalComputer ? NULL : GetComputerName(), NULL, COM_BUFFER_SIZE);

  if (!m_hPC)
  {
    m_PCLastError = PCGetLastError(m_hPC);
    ATLTRACE(_T("Unable to open connection to %s, Error(0x%lX).\n"),
      GetComputerDisplayName(), m_PCLastError );
  }

  return m_hPC;
}

BOOL CRootFolder::ReportPCError(PCULONG32 nLastError)
{
	TCHAR *pMsgBuf = FormatErrorMessageIntoBuffer(nLastError);

  if ( pMsgBuf )
  {
    int ret = 0;
    ATLTRACE( (TCHAR *) pMsgBuf );
    ATLTRACE( _T("\n") );

    if (m_ipConsole2)
      m_ipConsole2->MessageBox(pMsgBuf, NULL, MB_OK | MB_ICONWARNING, &ret);

    LocalFree(pMsgBuf);

    return TRUE;
  }

  ATLTRACE(_T("Message Problem: Error (0x%lX).\n"), nLastError );

  return FALSE;
}

BOOL CRootFolder::ReportPCError()
{
  return ReportPCError( GetLastPCError() );
}

PCULONG32 CRootFolder::GetLastPCError()
{
  if (m_hPC) // don't clear an open error...
    m_PCLastError = PCGetLastError(m_hPC);

  return m_PCLastError;
}

void CRootFolder::GetComputerConnectionInfo(COMPUTER_CONNECTION_INFO &out)
{
  out.bLocalComputer = m_bUseLocalComputer;
	memcpy(out.RemoteComputer, m_Computer, sizeof(m_Computer));
}

void CRootFolder::SetComputerName(TCHAR Computer[SNAPIN_MAX_COMPUTERNAME_LENGTH + 1])
{
  Config((Computer[0] == 0), Computer);
}

void CRootFolder::Config(BOOL bUseLocal, TCHAR Computer[SNAPIN_MAX_COMPUTERNAME_LENGTH + 1])
{
  m_bDirty = TRUE;

  m_bUseLocalComputer = bUseLocal;
  memset(m_Computer, 0, sizeof(m_Computer));
  _tcsncpy(m_Computer, Computer, ARRAY_SIZE(m_Computer) - 1);

  // change the node's display name...
  m_longname = m_name;
  if (m_bUseLocalComputer)
  {
    ITEM_STR tempstr;
    LoadStringHelper(tempstr, IDS_LOCAL);
    m_machinedisplayname = tempstr;
  }
  else
  {
    m_machinedisplayname.reserve((SNAPIN_MAX_COMPUTERNAME_LENGTH + 3) * sizeof(TCHAR));
    m_machinedisplayname = _T(" (");
    m_machinedisplayname+= m_Computer;
    m_machinedisplayname+= _T(")");
  }
  m_longname += m_machinedisplayname;
  // finish change node name...

  if (m_hPC)
    VERIFY( PCClose(m_hPC));
  m_hPC = 0;
}

HRESULT CRootFolder::IsDirty() const
{
  if (m_bDirty)
    return S_OK;
  return S_FALSE;
}

HRESULT CRootFolder::Load(IStream *pStm)
{
  if (!pStm)
    return E_POINTER;

  ASSERT(sizeof(WCHAR) == sizeof(TCHAR));  // conversions need to go to single byte characters

  ULONG BytesRead = 0;
  WCHAR sIn[SNAPIN_MAX_COMPUTERNAME_LENGTH + 1] = { 0 };

  HRESULT hr = pStm->Read(sIn, sizeof(sIn), &BytesRead);

  if (hr == S_OK && BytesRead)
  {
    SetComputerName(sIn);
    m_bDirty = FALSE;
  }

//pStm->Release();

  return hr;
}

HRESULT CRootFolder::Save(IStream *pStm, BOOL fClearDirty)
{
  if (!pStm)
    return E_POINTER;

#ifndef _UNICODE
#error "need to convert output to unicode prior to writing to disk"
#endif
  ULONG BytesWritten = 0;

  HRESULT hr = pStm->Write(GetComputerName(), ((SNAPIN_MAX_COMPUTERNAME_LENGTH + 1) * sizeof(WCHAR)), &BytesWritten);

  if (hr == S_OK && fClearDirty)
    m_bDirty = FALSE;
  
//  pStm->Release();
  return hr;
}

HRESULT CRootFolder::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
  if (!pcbSize)
    return E_POINTER;

  (*pcbSize).LowPart = (SNAPIN_MAX_COMPUTERNAME_LENGTH + 1) * sizeof(WCHAR);
  (*pcbSize).HighPart = 0;

  return S_OK;
}

LPCTSTR CRootFolder::GetNodeName()
{ 
  if (!m_ParentID)
    return m_longname.c_str();
  else
    return m_name;
}

HRESULT CRootFolder::GetDisplayInfo(RESULTDATAITEM &ResultItem)
{
  if (ResultItem.bScopeItem)
  {
    if( ResultItem.mask & RDI_STR )
    {
			if (0 == ResultItem.nCol)
				ResultItem.str = const_cast<LPOLESTR>(GetNodeName());
      //else if (m_ParentID && 1 == ResultItem.nCol)
      //  ResultItem.Str = m_TypeDescriptionStr;
      else if (2 == ResultItem.nCol)
        ResultItem.str = m_DescriptionStr;
      else 
				ResultItem.str = _T("");
    }
    if (ResultItem.mask & RDI_IMAGE)
      ResultItem.nImage = sImage();
    return S_OK;
  }
  return E_UNEXPECTED;
}

LPCTSTR CRootFolder::GetComputerName() const
{
  return m_Computer;
}

LPCTSTR CRootFolder::GetComputerDisplayName() const
{ 
  return m_machinedisplayname.c_str();
}

HRESULT CRootFolder::OnHelpCmd(IDisplayHelp *ipDisplayHelp)
{
	if (!ipDisplayHelp)
		return E_UNEXPECTED;
  
	ipDisplayHelp->ShowTopic(const_cast<TCHAR *>(HELP_overview));

	return S_OK;
}

//IComponentData::Notify -- MNCN_EXPAND
HRESULT CRootFolder::OnParentExpand(
  BOOL         bExpanded,        // [in] TRUE is we are expanding
  HSCOPEITEM   hID,              // [in] Points to the HSCOPEITEM
  IConsoleNameSpace2 *ipConsoleNameSpace2
)
{
  ASSERT(ipConsoleNameSpace2);

  if(!ipConsoleNameSpace2)
    return E_UNEXPECTED; 

  if (bExpanded) 
  {
    ASSERT(m_NodeList.size() == 0);
    ASSERT(m_ID == 0);

    m_ParentID     = hID;

    SCOPEDATAITEM sdi = {0};
 		sdi.mask       =	SDI_STR       |
                      SDI_PARAM     |   // lParam is valid
				              SDI_IMAGE     |   // nImage is valid
				              SDI_OPENIMAGE |   // nOpenImage is valid
                      SDI_CHILDREN  |   // cChildren is valid
				              SDI_PARENT;  
    sdi.displayname = (LPOLESTR)MMC_CALLBACK;
    sdi.nImage      = sImage();
    sdi.nOpenImage  = sOpenImage();
    sdi.cChildren   = GetChildrenCount();
    sdi.lParam      = reinterpret_cast <LPARAM> (this);
    sdi.relativeID  = hID;
    HRESULT hr = ipConsoleNameSpace2->InsertItem(&sdi);
    if (hr == S_OK)
      m_ID = sdi.ID;    
  }

  return S_OK;  // return has no meaning to MMC 

} // end OnParentExpand()


//IComponentData::Notify -- MNCN_EXPAND
HRESULT CRootFolder::OnExpand(
  BOOL         bExpanded,        // [in] TRUE is we are expanding
  HSCOPEITEM   hID,              // [in] Points to the HSCOPEITEM
  IConsoleNameSpace2 *ipConsoleNameSpace2
)
{
  ASSERT(ipConsoleNameSpace2);

  if(!ipConsoleNameSpace2)
    return E_UNEXPECTED; 

  if (bExpanded) 
  {
    ASSERT(m_NodeList.size() == 0);
    ASSERT(m_ID == 0 || m_ID == hID);

    m_ID = hID;   // Cache the root node handle

    // 3/7/2001 (PAS)
    // around 2410 MMC behavior changed and the proccon folder icon 
    // wasn't always displayed correctly...the root node image used to be 
    // handled differently (ISnapinAbout::GetStaticFolderImage()) but 
    // possibly is now handled like other scope items.
    
    SCOPEDATAITEM sdi = {0};
 		sdi.mask        =	SDI_STR       |
				              SDI_IMAGE     |   // nImage is valid
				              SDI_OPENIMAGE;    // nOpenImage is valid
    sdi.ID          = hID;
    sdi.displayname = (LPOLESTR)MMC_CALLBACK;
    sdi.nImage      = sImage();
    sdi.nOpenImage  = sOpenImage();

    VERIFY( S_OK == ipConsoleNameSpace2->SetItem(&sdi) );
    VERIFY( S_OK == AddNodes(ipConsoleNameSpace2) );    
  }

  return S_OK;  // return has no meaning to MMC 

} // end OnExpand()

HRESULT CRootFolder::AddNodes(IConsoleNameSpace2 *ipConsoleNameSpace2)
{ 
  VERIFY( S_OK == AddNode(ipConsoleNameSpace2, new CRuleFolder(this)   ) );

	VERIFY( S_OK == AddNode(ipConsoleNameSpace2, new CProcessFolder(this) ) );

	VERIFY( S_OK == AddNode(ipConsoleNameSpace2, new CJobFolder(this)     ) );

  return S_OK; 
}

HRESULT CRootFolder::AddNode(IConsoleNameSpace2 *ipConsoleNameSpace2, CBaseNode *pSubNode)
{
  HRESULT  hr = S_OK;

  SCOPEDATAITEM sdi = {0};

  if (!pSubNode)
    return E_OUTOFMEMORY;
 
  // Place folder into the scope pane
  sdi.mask        = SDI_STR       |   // Displayname is valid
				            SDI_PARAM     |   // lParam is valid
				            SDI_IMAGE     |   // nImage is valid
				            SDI_OPENIMAGE |   // nOpenImage is valid
                    SDI_CHILDREN  |   // cChildren is valid
				            SDI_PARENT;
  
  sdi.displayname = (LPOLESTR)MMC_CALLBACK;
  sdi.nImage      = pSubNode->sImage();
  sdi.nOpenImage  = pSubNode->sOpenImage();
  //sdi.nState 
  sdi.cChildren   = pSubNode->GetChildrenCount();
  sdi.lParam      = reinterpret_cast <LPARAM> (pSubNode);
  sdi.relativeID  = m_ID;
  
  hr = ipConsoleNameSpace2->InsertItem( &sdi );

  if (SUCCEEDED(hr))
  {
    pSubNode->SetID(sdi.ID);
    m_NodeList.push_front( pSubNode );
  }
  else
  {
    pSubNode->Release();
    //delete pSubNode;
  }

  return hr;
}

//IComponentData::Notify -- MNCN_REMOVE_CHILDREN
HRESULT CRootFolder::OnParentRemoveChildren(HSCOPEITEM hID)
{
  ASSERT(hID == m_ParentID);
  if (hID == m_ParentID)
    return OnRemoveChildren(m_ID);
  return E_UNEXPECTED;
}

HRESULT CRootFolder::OnRemoveChildren(HSCOPEITEM hID)
{
  ASSERT(m_ID == hID);

  FreeNodes();
  m_ID = 0;
  return S_OK;
}

HRESULT CRootFolder::AddMenuItems(LPCONTEXTMENUCALLBACK piCallback, long * pInsertionAllowed)
{
  HRESULT hr = S_OK;

  // 10/2/1998 PAS
  // when the snapin is first loaded and the root node has not yet been expanded or 
  // selected than we haven't yet been able to save the ID.
  // The right click context menu can be invoked when the node has no yet been expanded or
  // selected, if we haven't yet gotten the ID we have no way to change the selection 
  // node so the menu command can't be supported yet.
  if (!m_ID)
    return S_FALSE;

  ITEM_STR name;
  ITEM_STR status;
  
  if( *pInsertionAllowed & CCM_INSERTIONALLOWED_TOP )
  {
    CONTEXTMENUITEM m = { 0 };
    for (const CONTEXTMENUITEMBYID *M = TaskMenuItems; M->lCommandID; M++)
    {
      m.strName           = const_cast<TCHAR *>(LoadStringHelper(name,   M->strNameID));
      m.strStatusBarText  = const_cast<TCHAR *>(LoadStringHelper(status, M->strStatusBarTextID));
			m.lCommandID        = M->lCommandID;
			m.lInsertionPointID = CCM_INSERTIONPOINTID_PRIMARY_TOP;
			m.fFlags            = MF_ENABLED;
 		  //m.fSpecialFlags   = 0;		// currently always 0, initialized to 0

      if (m.lCommandID == ID_ROOT_CONNECT && m_ParentID)
        continue;

      hr = piCallback->AddItem(&m);

      if (FAILED(hr))
        break;
    }
  }

  if( *pInsertionAllowed & CCM_INSERTIONALLOWED_TASK )
  {
    CONTEXTMENUITEM m = { 0 };
    for (const CONTEXTMENUITEMBYID *M = TaskMenuItems; M->lCommandID; M++)
    {
      m.strName           = const_cast<TCHAR *>(LoadStringHelper(name,   M->strNameID));
      m.strStatusBarText  = const_cast<TCHAR *>(LoadStringHelper(status, M->strStatusBarTextID));
			m.lCommandID        = M->lCommandID;
      m.lInsertionPointID = CCM_INSERTIONPOINTID_PRIMARY_TASK;
			m.fFlags            = MF_ENABLED;
 		  //m.fSpecialFlags   = 0;		// currently always 0, initialized to 0

      if (m.lCommandID == ID_ROOT_CONNECT && m_ParentID)
        continue;

      hr = piCallback->AddItem(&m);

      if (FAILED(hr))
        break;
    }
  }
  return hr;
}

HRESULT CRootFolder::OnMenuCommand(IConsole2 *ipConsole2, long nCommandID )
{
  HRESULT hr = S_FALSE;
  switch(nCommandID)
  {
    case ID_ROOT_CONNECT:
      ATLTRACE(_T("CRootFolder(%s)-OnMenuCommand Connect to another computer\n"), GetNodeName());
      hr = OnChangeComputerConnection();
      break;
    default:
      ATLTRACE(_T("CRootFolder(%s)-OnMenuCommand Unrecognized command 0x%lX\n"), GetNodeName(), nCommandID);
      break;
  }
  return hr; 
}

HRESULT CRootFolder::OnMenuCommand(IConsole2 *ipConsole2, long nCommandID, LPARAM Cookie)
{
  ATLTRACE(_T("CRootFolder(%s)-OnMenuCommand Unrecognized command 0x%lX\n"), GetNodeName(), nCommandID);
  return E_UNEXPECTED;
}

HRESULT CRootFolder::OnChangeComputerConnection()
{
  CRootWizard2 *pPage2 = new CRootWizard2(CRootWizard2::LASTANDONLY_PAGE, IDS_CONNECT_TITLE, this);
  if (!pPage2)
    return S_FALSE;
    
  PROPSHEETHEADER sheet;
	memset(&sheet, 0, sizeof(PROPSHEETHEADER));
  sheet.dwSize = sizeof(PROPSHEETHEADER);
  sheet.dwFlags = PSH_WIZARD | PSH_WIZARDCONTEXTHELP;
  sheet.hwndParent = ::GetActiveWindow();
  sheet.hInstance = _Module.GetResourceInstance();
  sheet.pszIcon = 0;
  sheet.pszCaption = 0;
  sheet.nPages = 1;
  sheet.nStartPage = 0;

#if USE_WIZARD97_WATERMARKS
  sheet.dwFlags        |= PSH_WIZARD97 | PSH_WATERMARK;
  sheet.pszbmWatermark  = MAKEINTRESOURCE(IDB_WATERMARK1);
#endif
#if USE_WIZARD97_HEADERS
  sheet.dwFlags        |= PSH_WIZARD97 | PSH_HEADER;
  sheet.pszbmHeader     = MAKEINTRESOURCE(IDB_HEADER1);
#endif

  HPROPSHEETPAGE hPages[1];

  hPages[0] = pPage2->Create();

  sheet.phpage = &hPages[0];
  sheet.pfnCallback = NULL;

  PropertySheet(&sheet);
  
  return S_OK;
}

// folder selection...
HRESULT CRootFolder::OnSelect(BOOL bScope, BOOL bSelect, IConsoleVerb* ipConsoleVerb)
{
  ASSERT(bScope);

  if (bSelect) 
  {
		// allow properties verb to work when context menu is used in scope or result pane
		VERIFY( ipConsoleVerb->SetVerbState( MMC_VERB_PROPERTIES, ENABLED, TRUE ) == S_OK);
    if (!bScope) // incase the rules are changed again leave !bScope test
    {
		  VERIFY( ipConsoleVerb->SetVerbState( MMC_VERB_OPEN, ENABLED, TRUE ) == S_OK);
		  VERIFY( ipConsoleVerb->SetDefaultVerb( MMC_VERB_OPEN ) == S_OK ); 
    }
  }
  return S_OK;
}

HRESULT CRootFolder::OnSelect(BOOL bScope, BOOL bSelect, IConsoleVerb* ipConsoleVerb, LPARAM Cookie)
{
  ASSERT(!bScope);

  if (bSelect) 
  {
		// allow properties verb to work when context menu is used in scope or result pane
		VERIFY( ipConsoleVerb->SetVerbState( MMC_VERB_PROPERTIES, ENABLED, TRUE ) == S_OK);

    // why this seems to be a meaningless test
    // changes between MMC 1.1 vs MMC 1.2 (as least in the documentation)
    //   as to whether bScope mean scope pane or scope item 
    //   leave this in for the time being
    if (!bScope) // incase the rules are changed again leave !bScope test
    {
		  VERIFY( ipConsoleVerb->SetVerbState( MMC_VERB_OPEN, ENABLED, TRUE ) == S_OK);
		  VERIFY( ipConsoleVerb->SetDefaultVerb( MMC_VERB_OPEN ) == S_OK ); 
    }
  }
  return S_OK;
}

HRESULT CRootFolder::QueryPagesFor()
{ 
  return S_OK;
}

HRESULT CRootFolder::OnCreatePropertyPages( LPPROPERTYSHEETCALLBACK lpProvider, LONG_PTR handle,  DATA_OBJECT_TYPES context)
{
  if (context == CCT_SNAPIN_MANAGER)
  {
    CRootWizard1 *pPage1 = new CRootWizard1(CRootWizard1::FIRST_PAGE, IDS_ADDSNAPIN_TITLE);
    CRootWizard2 *pPage2 = new CRootWizard2(CRootWizard2::LAST_PAGE,  IDS_ADDSNAPIN_TITLE,	this);
    if (!pPage1 || !pPage2)
      return S_FALSE;

    ASSERT(handle == NULL); // $$ behavior change, better see what's going on...
														// previously context of CCT_SNAPIN_MANAGER, the handle was always null so
			                      // ...access the the folder object directly was assumed to be a valid operation
      
    VERIFY(lpProvider->AddPage(pPage1->Create()) == S_OK);

    return lpProvider->AddPage(pPage2->Create());
  }
  else
  {
		COMPUTER_CONNECTION_INFO   ConnInfo;
		GetComputerConnectionInfo(ConnInfo);

    CRootGeneralPage *pPage1 = new CRootGeneralPage(NULL);
    if (pPage1)   
      lpProvider->AddPage(pPage1->Create());

    CRootVersionPage *pPage2 = new CRootVersionPage(NULL);
    if (pPage2)   
      lpProvider->AddPage(pPage2->Create());

		PCSystemInfo sysInfo; 

    PCid hID = GetPCid();

		if (PCGetServiceInfo( hID, &sysInfo, sizeof(sysInfo) ))
		{
      CServicePageContainer *pContainer = new CServicePageContainer(sysInfo.sysParms, this, handle, hID, ConnInfo, 0, TRUE, -1);
      if (pContainer)
      {
			  CRootServicePage *pPage3 = new CRootServicePage(NULL, pContainer);
			  if (pPage3)
			  {
				  pPage3->PCInfo = sysInfo;
				  lpProvider->AddPage(pPage3->Create());
			  }
        pContainer->Release();
        pContainer = NULL;
      }
		}
    else
    {
      VERIFY(S_OK == MMCFreeNotifyHandle(handle));
    }

    return S_OK;
  }
  return S_FALSE; 
}

HRESULT CRootFolder::OnPropertyChange(PROPERTY_CHANGE_HDR *pUpdate, IConsole2 *ipConsole2)
{
  ATLTRACE(_T("Service Info updated via property page...\n"));
	return S_OK;
}