// DSColumn.cpp : Implementation of ds column routines and classes

//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1999
//
//  File:      DSColumn.cpp
//
//  Contents:  DS Column routines, classes, and static data
//
//  History:   12-Mar-99 JeffJon    Created
//
//
//--------------------------------------------------------------------------

#include "stdafx.h"
#include "resource.h"

#include "util.h"

#include "DSColumn.h"
#include "dscookie.h"
#include "dscmn.h"      // CrackName()
#include "dsutil.h"

BOOL ColumnExtractStringValue(
    OUT CString& strref,
    IN CDSCookie*,
    IN PADS_SEARCH_COLUMN pColumn,
	IN DWORD iValue)
{
  if (pColumn == NULL || pColumn->dwNumValues <= iValue)
  {
    return FALSE;
  }

  switch (pColumn->dwADsType)
	{
	case ADSTYPE_CASE_IGNORE_STRING:
		strref = (LPCWSTR)pColumn->pADsValues[iValue].CaseIgnoreString;
		break;
	case ADSTYPE_DN_STRING:
		strref = (LPCWSTR)pColumn->pADsValues[iValue].DNString;
		break;
	default:
		return FALSE;
	}
    return TRUE;
}

BOOL ColumnExtractString(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
    return ColumnExtractStringValue( strref, pCookie, pColumn, 0 );
}

BOOL ColumnExtractElementFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn,
    long lElement,
	IN DWORD iValue = 0);

BOOL ColumnExtractElementFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn,
    long lElement,
	IN DWORD iValue)
{
    if (pColumn == NULL)
    {
        return FALSE;
    }

    BOOL fRetval = FALSE;
    CString str;
    CComBSTR bstr;
    HRESULT hr = S_OK;
    do { // false loop
        if ( !ColumnExtractStringValue( str, pCookie, pColumn, iValue ) || str.IsEmpty() )
        {
            strref.Empty();
            fRetval = TRUE;
            break;
        }
        CPathCracker pathCracker;
        hr = pathCracker.Set(const_cast<BSTR>((LPCTSTR)str), ADS_SETTYPE_DN);
        if ( FAILED(hr) )
            break;
        // no need to reset this
        hr = pathCracker.SetDisplayType(ADS_DISPLAY_VALUE_ONLY);
        if ( FAILED(hr) )
            break;
        hr = pathCracker.put_EscapedMode(ADS_ESCAPEDMODE_OFF);
        if ( FAILED(hr) )
            break;
        hr = pathCracker.GetElement(lElement, &bstr);
        strref = bstr;
        fRetval = TRUE;
    } while (FALSE); // false loop

    return fRetval;
}

BOOL ColumnExtractLeafFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
  return ColumnExtractElementFromDN( strref, pCookie, pColumn, 0 );
}

BOOL ColumnExtractParentFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
  return ColumnExtractElementFromDN( strref, pCookie, pColumn, 1 );
}

BOOL ColumnExtractGreatGrandparentFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
  return ColumnExtractElementFromDN( strref, pCookie, pColumn, 3 );
}

BOOL ColumnExtractConnectionDisplayName(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
  if (pColumn == NULL)
  {
    return FALSE;
  }

    ADS_INTEGER adsint = 0;
    switch (pColumn->dwADsType)
    {
    case ADSTYPE_INTEGER:
      adsint = pColumn->pADsValues->Integer;
      break;
    default:
    // no value, let it stay 0
      break;
    }
    if (NTDSCONN_OPT_IS_GENERATED & adsint) {
      strref.LoadString (IDS_CONNECTION_KCC_GENERATED);
    } else {
      strref = pCookie->GetName();
    }
    return TRUE;
}

BOOL ColumnExtractAttribute(
    OUT CString& strref,
    IN CDSCookie*,
    IN PADS_SEARCH_COLUMN pColumn)
{
  if (pColumn == NULL)
  {
    return FALSE;
  }

  switch (pColumn->dwADsType)
  {
	case ADSTYPE_DN_STRING         :
		strref = pColumn->pADsValues->DNString;
		break;

	case ADSTYPE_CASE_EXACT_STRING :
		strref = pColumn->pADsValues->CaseExactString;
		break;

	case ADSTYPE_CASE_IGNORE_STRING:
		strref = pColumn->pADsValues->CaseIgnoreString;
		break;

	case ADSTYPE_PRINTABLE_STRING  :
		strref = pColumn->pADsValues->PrintableString;
		break;

	case ADSTYPE_NUMERIC_STRING    :
		strref = pColumn->pADsValues->NumericString;
		break;

	case ADSTYPE_OBJECT_CLASS    :
		strref = pColumn->pADsValues->ClassName;
		break;

	case ADSTYPE_BOOLEAN :
		strref = ((DWORD)pColumn->pADsValues->Boolean) ? L"TRUE" : L"FALSE";
		break;

	case ADSTYPE_INTEGER           :
		strref.Format(L"%d", (DWORD) pColumn->pADsValues->Integer);
		break;

	case ADSTYPE_OCTET_STRING      :
		{
			CString sOctet = L"";
	
			BYTE  b;
			for ( DWORD idx=0; idx<pColumn->pADsValues->OctetString.dwLength; idx++) 
			{
				b = ((BYTE *)pColumn->pADsValues->OctetString.lpValue)[idx];
				sOctet.Format(L"0x%02x ", b);
				strref += sOctet;
			}
		}
		break;

	case ADSTYPE_UTC_TIME:
    {
      PTSTR ptszDate = NULL;
      int cchDate = 0;
      SYSTEMTIME st = {0};

      if (!SystemTimeToTzSpecificLocalTime(NULL, &pColumn->pADsValues->UTCTime, &st))
      {
        strref = L"";
        return TRUE;
      }
      cchDate = GetDateFormat(LOCALE_USER_DEFAULT, 0 , 
                              &st, NULL, 
                              ptszDate, 0);
      ptszDate = (PTSTR)malloc(sizeof(TCHAR) * cchDate);
      if (GetDateFormat(LOCALE_USER_DEFAULT, 0, 
                      &st, NULL, 
                      ptszDate, cchDate))
      {
  		  strref = ptszDate;
      }
      else
      {
        strref = L"";
      }
      free(ptszDate);

      PTSTR ptszTime = NULL;

      cchDate = GetTimeFormat(LOCALE_USER_DEFAULT, 0 , 
                              &st, NULL, 
                              ptszTime, 0);
      ptszTime = (PTSTR)malloc(sizeof(TCHAR) * cchDate);
      if (ptszTime != NULL)
      {
        if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, 
                        &st, NULL, 
                        ptszTime, cchDate))
        {
          strref += _T(" ") + CString(ptszTime);
        }
        else
        {
          strref += _T("");
        }
        free(ptszTime);
      }
      else
      {
        strref = _T("");
      }
    }
		break;

	default :
    ASSERT(FALSE);
		break;
  }

  return TRUE;
}


BOOL _ColumnCrackDN(
    OUT CString& strref,
    IN LPCTSTR lpcszDN,
    IN CRACK_NAME_OPR RequestedOpr)
{
    PWSTR pwzName = NULL;
    HRESULT hr = CrackName(const_cast<PTSTR>(lpcszDN),
                           &pwzName,
                           RequestedOpr,
                           NULL);
    if (SUCCEEDED(hr))
    {
        if (GET_OBJ_CAN_NAME_EX == RequestedOpr)
        {
            LPTSTR ptzCanName = wcschr( pwzName, _T('\n') );
            if (NULL != ptzCanName)
                strref = ptzCanName+1;
            else
                strref.Empty();
        }
        else
        {
            strref = pwzName;
        }
        LocalFreeStringW(&pwzName);
    }
    else
    {
        strref.Empty();
    }
    return TRUE;
}

BOOL ColumnExtractNameFromSID(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN)
{
  return _ColumnCrackDN( strref, pCookie->GetPath(), GET_OBJ_CAN_NAME );
}

BOOL ColumnExtractCanonicalNameFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
    if (pColumn == NULL)
    {
        return FALSE;
    }

    CString str;
    if ( !ColumnExtractString( str, pCookie, pColumn ) || str.IsEmpty() )
    {
        strref.Empty();
        return TRUE;
    }

    return _ColumnCrackDN( strref, str, GET_OBJ_CAN_NAME_EX );
}

BOOL ColumnExtractDomainFromDN(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
    if (pColumn == NULL)
    {
        return FALSE;
    }

    CString str;
    if ( !ColumnExtractString( str, pCookie, pColumn ) || str.IsEmpty() )
    {
        strref.Empty();
        return TRUE;
    }

    return _ColumnCrackDN( strref, str, GET_DNS_DOMAIN_NAME );
}

int _cdecl _qsort_CompareColumns(const void * elem1, const void * elem2)
{
    PADSVALUE p1 = (PADSVALUE)elem1;
    PADSVALUE p2 = (PADSVALUE)elem2;
    if (!p1 || !p2 || !p1->DNString || !p2->DNString)
        return 0;
    return wcscmp( p1->DNString, p2->DNString );
}

BOOL ColumnExtractLeafList(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN pColumn)
{
    if (pColumn == NULL)
    {
        return FALSE;
    }

    // alphabetical order
    qsort( pColumn->pADsValues,
           pColumn->dwNumValues,
           sizeof(ADSVALUE),
           _qsort_CompareColumns );

    CString strSeparator;
    strSeparator.LoadString(IDS_SEPARATOR);
	for (DWORD iValue = 0; iValue < pColumn->dwNumValues; iValue++)
	{
		CString strTransport;
		if ( !ColumnExtractElementFromDN(
                    strTransport, pCookie, pColumn, 0, iValue) )
			return FALSE;
		if (0 < iValue)
			strref += strSeparator;
		strref += strTransport;
	}

    return TRUE;
}

BOOL _ColumnCrackFRS(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN CRACK_NAME_OPR RequestedOpr)
{
    CDSCookieInfoConnection* pExtraInfo = NULL;
    if (   NULL == pCookie
        || _wcsicmp( pCookie->GetClass(), L"nTDSConnection" )
        || NULL == (pExtraInfo = (CDSCookieInfoConnection*)pCookie->GetExtraInfo())
        || pExtraInfo->GetClass() != CDSCookieInfoBase::connection
        || pExtraInfo->m_strFRSComputerReference.IsEmpty()
       )
    {
        strref.Empty();
        return TRUE;
    }

    return _ColumnCrackDN( strref, pExtraInfo->m_strFRSComputerReference, RequestedOpr );
}

BOOL ColumnExtractFRSComputer(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN)
{
    return _ColumnCrackFRS( strref, pCookie, GET_OBJ_CAN_NAME_EX );
}

BOOL ColumnExtractFRSDomain(
    OUT CString& strref,
    IN CDSCookie* pCookie,
    IN PADS_SEARCH_COLUMN)
{
    return _ColumnCrackFRS( strref, pCookie, GET_DNS_DOMAIN_NAME );
}

ATTRIBUTE_COLUMN colName = { ATTR_COLTYPE_NAME,
                             IDS_COLUMN_NAME, //column header
                             100,             //col. width
                             NULL,            //ldap attr. name
                             NULL };          //extract fn()

ATTRIBUTE_COLUMN colClass= { ATTR_COLTYPE_CLASS,
                             IDS_COLUMN_TYPE, //column header
                             100,               //col. width
                             NULL,            //ldap attr. name
                             NULL };          //extract fn()

ATTRIBUTE_COLUMN colDesc = { ATTR_COLTYPE_DESC,
                             IDS_COLUMN_DESCRIPTION, //column header
                             150,               //col. width
                             NULL,            //ldap attr. name
                             NULL };          //extract fn()

ATTRIBUTE_COLUMN colSite = { ATTR_COLTYPE_SPECIAL,
                             IDS_COLUMN_SITE, //column header
                             100,             //col. width
                             L"siteObject",   //ldap attr. name
                             ColumnExtractLeafFromDN }; //extract fn()

ATTRIBUTE_COLUMN colLocation = { ATTR_COLTYPE_SPECIAL,
                                 IDS_COLUMN_LOCATION, //column header
                                 150,               //col. width
                                 L"location", //ldap attr. name
                                 NULL }; //extract fn()

ATTRIBUTE_COLUMN colDomain = { ATTR_COLTYPE_SPECIAL,
                               IDS_COLUMN_DOMAIN, //column header
                               150,               //col. width
                               L"serverReference", //ldap attr. name
                               ColumnExtractDomainFromDN }; //extract fn()

ATTRIBUTE_COLUMN colBridgehead = { ATTR_COLTYPE_SPECIAL,
                                   IDS_COLUMN_BRIDGEHEAD, //column header
                                   150,               //col. width
                                   L"bridgeheadTransportList", //ldap attr. name
                                   ColumnExtractLeafList }; //extract fn()

ATTRIBUTE_COLUMN colReplicaComputer = { ATTR_COLTYPE_SPECIAL,
                                        IDS_COLUMN_COMPUTER, //column header
                                        100,               //col. width
                                        L"fRSComputerReference", //ldap attr. name
                                        ColumnExtractCanonicalNameFromDN }; //extract fn()

ATTRIBUTE_COLUMN colReplicaDomain = { ATTR_COLTYPE_SPECIAL,
                                      IDS_COLUMN_DOMAIN, //column header
                                      150,               //col. width
                                      L"fRSComputerReference", //ldap attr. name
                                      ColumnExtractDomainFromDN }; //extract fn()

ATTRIBUTE_COLUMN colFromFRSComputer = { ATTR_COLTYPE_SPECIAL,
                                        IDS_COLUMN_FROM_COMPUTER, //column header
                                        100,             //col. width
                                        L"fromServer",   //ldap attr. name
                                        ColumnExtractFRSComputer }; //extract fn()

ATTRIBUTE_COLUMN colFromFRSDomain = { ATTR_COLTYPE_SPECIAL,
                                      IDS_COLUMN_FROM_DOMAIN, //column header
                                      150,               //col. width
                                      L"fromServer", //ldap attr. name
                                      ColumnExtractFRSDomain }; //extract fn()

ATTRIBUTE_COLUMN colConnectionName={ ATTR_COLTYPE_SPECIAL,
                             IDS_COLUMN_NAME, //column header
                             150,             //col. width
                             L"options",   //ldap attr. name
                             ColumnExtractConnectionDisplayName}; //extract fn()

ATTRIBUTE_COLUMN colFromServer={ ATTR_COLTYPE_SPECIAL,
                             IDS_COLUMN_FROM_SERVER, //column header
                             100,             //col. width
                             L"fromServer",   //ldap attr. name
                             ColumnExtractParentFromDN }; //extract fn()

ATTRIBUTE_COLUMN colFromSite={ ATTR_COLTYPE_SPECIAL,
                             IDS_COLUMN_FROM_SITE, //column header
                             100,             //col. width
                             L"fromServer",   //ldap attr. name
                             ColumnExtractGreatGrandparentFromDN }; //extract fn()

ATTRIBUTE_COLUMN colCost = { ATTR_COLTYPE_SPECIAL,
                                 IDS_COLUMN_COST, //column header
                                 75,               //col. width
                                 L"cost", //ldap attr. name
                                 ColumnExtractAttribute }; //extract fn()

ATTRIBUTE_COLUMN colReplInterval = { ATTR_COLTYPE_SPECIAL,
                                 IDS_COLUMN_REPLINTERVAL, //column header
                                 150,               //col. width
                                 L"replInterval", //ldap attr. name
                                 ColumnExtractAttribute }; //extract fn()

ATTRIBUTE_COLUMN colNameFromSID={ ATTR_COLTYPE_SPECIAL,
                                  IDS_COLUMN_READABLE_NAME,    // column header
                                  100,                // column width
                                  L"container",
                                  ColumnExtractNameFromSID }; // extract fn()

ATTRIBUTE_COLUMN colGenericSpecial={ ATTR_COLTYPE_SPECIAL,
                                     0,                   // resource id 0 means load it from the special column array
                                     50,
                                     NULL,
                                     ColumnExtractAttribute };

ATTRIBUTE_COLUMN colModifiedTime={ ATTR_COLTYPE_MODIFIED_TIME,
                                   0,
                                   50,
                                   NULL,
                                   ColumnExtractAttribute };

SPECIAL_COLUMN g_specialCols[] =
{ 
  { IDS_COLUMN_BUSINESS_PHONE,      L"telephoneNumber",             100 },
  { IDS_COLUMN_CITY,                L"l",                           150 },
  { IDS_COLUMN_COMPANY,             L"company",                     150 },
  { IDS_COLUMN_COUNTRY,             L"c",                           AUTO_WIDTH }, // Default to the width of the header string
  { IDS_COLUMN_DEPARTMENT,          L"department",                  150 },
  { IDS_COLUMN_DISPLAY_NAME,        L"displayName",                 100 },
  { IDS_COLUMN_SAM_ACCOUNT_NAME,    L"sAMAccountName",              120 },
  { IDS_COLUMN_MAIL,                L"mail",                        100 },
  { IDS_COLUMN_ALIAS_NAME,          L"mailNickname",                175 },
  { IDS_COLUMN_HOME_MDB,            L"homeMDB",                     100 },
  { IDS_COLUMN_FIRST_NAME,          L"givenName",                   100 },
  { IDS_COLUMN_IMHOMEURL,           L"msExchIMPhysicalURL",         170 }, 
  { IDS_COLUMN_IMURL,               L"msExchIMMetaPhysicalURL",     140 },
  { IDS_COLUMN_LAST_NAME,           L"sn",                          100 },
  { IDS_COLUMN_MODIFIED,            L"whenChanged",                 130 },
  { IDS_COLUMN_OFFICE,              L"physicalDeliveryOfficeName",  100 },
  { IDS_COLUMN_STATE,               L"st",                          100 },
  { IDS_COLUMN_TARGET_ADDRESS ,     L"targetAddress",               100 },
  { IDS_COLUMN_TITLE,               L"title",                       100 },
  { IDS_COLUMN_UPN,                 L"userPrincipalName",           200 },
  { IDS_COLUMN_TEXTENCODEORADDRESS, L"textEncodedORAddress",        130 },
  { IDS_COLUMN_ZIP_CODE,            L"postalCode",                  100 }
};

PATTRIBUTE_COLUMN colsSubnetContainer[6] = { &colName, &colSite, &colLocation, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsSitesContainer[5] = { &colName, &colLocation, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsServersContainer[6] = { &colName, &colDomain, &colBridgehead, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsNTDSDSA[6] = { &colConnectionName, &colFromServer, &colFromSite, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsInterSiteTransport[6] = { &colName, &colClass, &colDesc, &colCost, &colReplInterval, NULL };
PATTRIBUTE_COLUMN colsFRSReplicaSet[6] = { &colName, &colReplicaComputer, &colReplicaDomain, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsFRSMember[6] = { &colConnectionName, &colFromFRSComputer, &colFromFRSDomain, &colClass, &colDesc, NULL };
PATTRIBUTE_COLUMN colsFSP[5] = { &colName, &colClass, &colDesc, &colNameFromSID,  NULL };

PATTRIBUTE_COLUMN colsDefault[4] = { &colName, &colClass, &colDesc, NULL };

// currently, any additional columns must be of string type
COLUMNS_FOR_CLASS g_colarray[] = {
    { _T("subnetContainer"),  _T("subnetContainer"),  5, colsSubnetContainer },
    { _T("sitesContainer"),   _T("sitesContainer"),   4, colsSitesContainer},
    { _T("serversContainer"), _T("serversContainer"), 5, colsServersContainer},
    { _T("nTDSDSA"),          _T("nTDSDSA"),          5, colsNTDSDSA },
    { _T("interSiteTransport"), _T("interSiteTransport"), 5, colsInterSiteTransport},
    { _T("nTFRSReplicaSet"),  _T("nTFRSReplicaSet"),  5, colsFRSReplicaSet },
    { _T("nTFRSMember"),      _T("nTFRSMember"),      5, colsFRSMember },
    { _T("ForeignSecurityPrincipals"), _T("ForeignSecurityPrincipals"), 4, colsFSP },
    { NULL,                  DEFAULT_COLUMN_SET,     3, colsDefault } // empty one at the end; must be here
    };

/*
COLUMNS_FOR_CLASS* GetColumnsForClass( LPCTSTR i_pcszLdapClassName )
{
  if (NULL == i_pcszLdapClassName)
    i_pcszLdapClassName = L"";
  COLUMNS_FOR_CLASS* pColsForClass;
  for (pColsForClass = g_colarray; NULL != pColsForClass->pcszLdapClassName; pColsForClass++) {
    if ( 0 == _wcsicmp(i_pcszLdapClassName, pColsForClass->pcszLdapClassName) ) {
      break;
    }
  }
  ASSERT( NULL != pColsForClass );
  return pColsForClass;
}
*/


////////////////////////////////////////////////////////////////////////////////////
// CDSColumnSet

CDSColumnSet* CDSColumnSet::CreateColumnSet(PCOLUMNS_FOR_CLASS pColsForClass, SnapinType snapinType)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  // Using the class name as both the column id and the class name for the column set
  CDSColumnSet* pNewColumnSet = new CDSColumnSet(pColsForClass->pcszColumnID, pColsForClass->pcszLdapClassName);
  if (!pNewColumnSet)
  {
    TRACE(L"Unable to allocate memory for new column set\n");
    ASSERT(FALSE);
    return 0;
  }

  UINT nUserColCount = 0;
  for (int idx = 0; idx < pColsForClass->nColumns; idx ++)
  {
    CString cstrHeader;
    CDSColumn* pNewColumn;

    if (pColsForClass->apColumns[idx]->resid == 0)
    {
      // Don't add the exchange special columns in DSSite
      if (snapinType == SNAPINTYPE_SITE)
        continue;

      cstrHeader.LoadString(g_specialCols[nUserColCount].resid);
      pNewColumn = new CDSColumn((LPCWSTR)cstrHeader,
                                          LVCFMT_LEFT,
                                          g_specialCols[nUserColCount].iColumnWidth,
                                          idx,
                                          FALSE,
                                          g_specialCols[nUserColCount].ptszAttribute,
                                          pColsForClass->apColumns[idx]->coltype,
                                          pColsForClass->apColumns[idx]->pfnExtract);
      nUserColCount++;
    }
    else
    {
      cstrHeader.LoadString(pColsForClass->apColumns[idx]->resid);
      pNewColumn = new CDSColumn((LPCWSTR)cstrHeader,
                                          LVCFMT_LEFT,
                                          pColsForClass->apColumns[idx]->iColumnWidth,
                                          idx,
                                          TRUE,
                                          pColsForClass->apColumns[idx]->pcszAttribute,
                                          pColsForClass->apColumns[idx]->coltype,
                                          pColsForClass->apColumns[idx]->pfnExtract);
    }
    ASSERT(pNewColumn);
    if (pNewColumn)
    {
      pNewColumnSet->AddColumn(pNewColumn);
    }
  }
  return pNewColumnSet;
}


CDSColumnSet* CDSColumnSet::CreateColumnSetFromString(LPCWSTR lpszClassName, SnapinType snapinType)
{
  COLUMNS_FOR_CLASS* pColsForClass;
  for (pColsForClass = g_colarray; pColsForClass->pcszLdapClassName != NULL; pColsForClass++)
  {
    if (lpszClassName != NULL && pColsForClass->pcszLdapClassName != NULL)
    {
      if (wcscmp(pColsForClass->pcszLdapClassName, lpszClassName) == 0)
      {
        break;
      }
    }
  }
  return CDSColumnSet::CreateColumnSet(pColsForClass, snapinType);
}

CDSColumnSet* CDSColumnSet::CreateDescriptionColumnSet()
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  CString szNameHeader;
  VERIFY(szNameHeader.LoadString(IDS_COLUMN_NAME));
  CColumn* pNameColumn = new CColumn(szNameHeader,
                                     LVCFMT_LEFT,
                                     100,
                                     0,
                                     TRUE);
  
  if (pNameColumn == NULL)
  {
    return NULL;
  }

  CString szDescriptionHeader;
  VERIFY(szDescriptionHeader.LoadString(IDS_COLUMN_DESCRIPTION));
  CColumn* pDescColumn = new CColumn(szDescriptionHeader,
                                     LVCFMT_LEFT,
                                     150,
                                     1,
                                     TRUE);
  if (pDescColumn == NULL)
  {
    return NULL;
  }

  CDSColumnSet* pDSColumnSet = new CDSColumnSet(L"***---Description Set---***", NULL);
  if (pDSColumnSet == NULL)
  {
    return NULL;
  }

  pDSColumnSet->AddColumn(pNameColumn);
  pDSColumnSet->AddColumn(pDescColumn);
  return pDSColumnSet;
}

CDSColumnSet* CDSColumnSet::CreateColumnSetFromDisplaySpecifiers(PCWSTR pszClassName, SnapinType snapinType, MyBasePathsInfo* pBasePathsInfo)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  HRESULT hr = S_OK;
  CDSColumnSet* pNewColumnSet = NULL;
  BOOL bDefaultHardcodedSet = FALSE;

  //
  // Using the class name as both the column id and the class name for the column set
  //
  pNewColumnSet = new CDSColumnSet(pszClassName, pszClassName);
  if (pNewColumnSet != NULL)
  {
    //
    // Start with the hardcoded columns
    //
    COLUMNS_FOR_CLASS* pColsForClass;
    for (pColsForClass = g_colarray; pColsForClass->pcszLdapClassName != NULL; pColsForClass++)
    {
      if (pszClassName != NULL && pColsForClass->pcszLdapClassName != NULL)
      {
        if (wcscmp(pColsForClass->pcszLdapClassName, pszClassName) == 0)
        {
          break;
        }
      }
    }

    if (pColsForClass != NULL)
    {
      if (wcscmp(pColsForClass->pcszColumnID, DEFAULT_COLUMN_SET) == 0)
      {
        bDefaultHardcodedSet = TRUE;
      }

      UINT nUserColCount = 0;
      for (int idx = 0; idx < pColsForClass->nColumns; idx ++)
      {
        CString cstrHeader;
        CDSColumn* pNewColumn;

        if (pColsForClass->apColumns[idx]->resid == 0)
        {
          // Don't add the exchange special columns in DSSite
          if (snapinType == SNAPINTYPE_SITE)
            continue;

          cstrHeader.LoadString(g_specialCols[nUserColCount].resid);
          pNewColumn = new CDSColumn((LPCWSTR)cstrHeader,
                                              LVCFMT_LEFT,
                                              g_specialCols[nUserColCount].iColumnWidth,
                                              idx,
                                              FALSE,
                                              g_specialCols[nUserColCount].ptszAttribute,
                                              pColsForClass->apColumns[idx]->coltype,
                                              pColsForClass->apColumns[idx]->pfnExtract);
          nUserColCount++;
        }
        else
        {
          cstrHeader.LoadString(pColsForClass->apColumns[idx]->resid);
          pNewColumn = new CDSColumn((LPCWSTR)cstrHeader,
                                              LVCFMT_LEFT,
                                              pColsForClass->apColumns[idx]->iColumnWidth,
                                              idx,
                                              TRUE,
                                              pColsForClass->apColumns[idx]->pcszAttribute,
                                              pColsForClass->apColumns[idx]->coltype,
                                              pColsForClass->apColumns[idx]->pfnExtract);
        }
        pNewColumnSet->AddColumn(pNewColumn);
      }
    }

    //
    // Now add the DS extraColumns
    //
    CStringList strListColumns;
    hr = GetDisplaySpecifierProperty(pszClassName, L"extraColumns", pBasePathsInfo, strListColumns);
    if (FAILED(hr))
    {
      hr = GetDisplaySpecifierProperty(pszClassName, L"extraColumns", pBasePathsInfo, strListColumns, true);
    }

    if (SUCCEEDED(hr))
    {
      UINT nColCount = static_cast<UINT>(pNewColumnSet->GetCount());
      POSITION pos = strListColumns.GetHeadPosition();
      while (pos != NULL)
      {
        CDSColumn* pNewColumn = NULL;

        CString szExtraColumn = strListColumns.GetNext(pos);
        if (!szExtraColumn.IsEmpty())
        {
          //
          // Parse the 5-tuple to get the elements to make the column
          //
          CString szAttributeName;
          CString szColHeader;
          BOOL    bVisible = TRUE;
          int     iColumnWidth = 0;
          GUID    guidCallbackInterface;  // unused.  Reserved for future callback interface

          PWSTR pszTemp = new WCHAR[szExtraColumn.GetLength() + 1];
          if (pszTemp != NULL)
          {
            wcscpy(pszTemp, (LPCWSTR)szExtraColumn);

            PWSTR pszVisible = NULL;
            PWSTR pszColumnWidth = NULL;
            PWSTR pszGuidCallback = NULL;

            PWSTR pszNextTuple = pszTemp;


            pszNextTuple = wcstok(pszTemp, L",");
            if (pszNextTuple != NULL)
            {
              szAttributeName = pszNextTuple;
            }

            pszNextTuple = wcstok(NULL, L",");
            if (pszNextTuple != NULL)
            {
              szColHeader = pszNextTuple;
            }

            pszNextTuple = wcstok(NULL, L",");
            if (pszNextTuple != NULL)
            {
              pszVisible = pszNextTuple;
              int i = _wtoi(pszVisible);
              if (i == 0)
              {
                bVisible = FALSE;
              }
              else
              {
                bVisible = TRUE;
              }
            }

            pszNextTuple = wcstok(NULL, L",");
            if (pszNextTuple != NULL)
            {
              pszColumnWidth = pszNextTuple;
              iColumnWidth = _wtoi(pszColumnWidth);
            }

            pszNextTuple = wcstok(NULL, L",");
            if (pszNextTuple != NULL)
            {
              pszGuidCallback = pszNextTuple;
              HRESULT hr2 = ::CLSIDFromString(pszGuidCallback, &guidCallbackInterface);
              if (FAILED(hr2))
              {
                memset(&guidCallbackInterface, 0, sizeof(GUID));
              }
            }

            //
            // Create the column with the retrieved data
            //
            pNewColumn = new CDSColumn((PCWSTR)szColHeader,
                                        LVCFMT_LEFT,
                                        iColumnWidth,
                                        nColCount++,
                                        bVisible,
                                        (PCWSTR)szAttributeName,
                                        ATTR_COLTYPE_SPECIAL,
                                        ColumnExtractAttribute); // this will be changed to the interface when that is implemented
          }

          delete[] pszTemp;
          
        }
        if (pNewColumn != NULL)
        {
          pNewColumnSet->AddColumn(pNewColumn);
        }
      }
    }
  }

  //
  // If we failed to retrieve columns from the display specifier
  // and we hit the default hardcoded set but we were not asking
  // for the default hardcoded set and the snapin isn't the sites snapin
  // Then we delete the column set and we will pickup the actual default
  // column set since we are returning NULL
  //
  if (FAILED(hr) && 
      bDefaultHardcodedSet && 
      _wcsicmp(pszClassName, DEFAULT_COLUMN_SET) != 0 &&
      snapinType != SNAPINTYPE_SITE)
  {
    delete pNewColumnSet;
    pNewColumnSet = NULL;
  }
  return pNewColumnSet;
}

HRESULT CColumnSet::Save(IStream* pStm)
{
  // save the column set ID
  HRESULT hr = SaveStringHelper(GetColumnID(), pStm);
  if (FAILED(hr))
    return hr;
  
  // save the # of visible columns and
  // the indexes of the visible columns
  // NOTICE: we use MMC's MMC_VISIBLE_COLUMNS format to be consitent
  // and to be able to read back easily

  INT nTotalCols = GetNumCols();

  // allocate a bit more than needed (i.e. total # of columns)
  MMC_VISIBLE_COLUMNS* pVisibleColumns = 
    (MMC_VISIBLE_COLUMNS*)new BYTE[sizeof(MMC_VISIBLE_COLUMNS) + (sizeof(INT)*(nTotalCols-1))];

  if (!pVisibleColumns)
  {
    return E_OUTOFMEMORY;
  }
  pVisibleColumns->nVisibleColumns = 0;
  int iIndex = 0;
  for (POSITION pos = GetHeadPosition(); (pos != NULL); )
  {
    CColumn* pCol = GetNext(pos);
    if (pCol->IsVisible())
    {
      pVisibleColumns->rgVisibleCols[pVisibleColumns->nVisibleColumns] = iIndex;
      (pVisibleColumns->nVisibleColumns)++;
    }
    iIndex++;
  }

  // save the right length of the struct
  ULONG nByteCount = sizeof(MMC_VISIBLE_COLUMNS) + (sizeof(INT)*(pVisibleColumns->nVisibleColumns-1));
  ULONG nBytesWritten;
  hr = pStm->Write((void*)pVisibleColumns, nByteCount, &nBytesWritten);
	if (SUCCEEDED(hr))
  {
    if (nBytesWritten < nByteCount)
    {
      hr = STG_E_CANTSAVE;
    }
  }

  delete[] pVisibleColumns;
  pVisibleColumns = 0;
  return hr;
}

HRESULT CColumnSet::Load(IStream* pStm)
{
  // NOTICE: we already loaded the column set ID and
  // got a columns set that matches this

  // read the # of visible columns
	DWORD dwColCount = 0;
  INT nCountMax = GetNumCols();
	HRESULT hr = LoadDWordHelper(pStm, &dwColCount);
	if (FAILED(hr) || ((INT)dwColCount > nCountMax))
		return E_FAIL;

  // allocate some space for the array past the struct
  MMC_VISIBLE_COLUMNS* pVisibleColumns = 
    (MMC_VISIBLE_COLUMNS*)new BYTE[sizeof(MMC_VISIBLE_COLUMNS) + (sizeof(INT)*(dwColCount-1))];
  if (!pVisibleColumns)
  {
    return E_OUTOFMEMORY;
  }
  pVisibleColumns->nVisibleColumns = (INT)dwColCount;

  // load the array of indexes of visible columns
  ULONG nBytesRead;

	ULONG nByteCount = sizeof(DWORD)*dwColCount;
  INT* pArr = pVisibleColumns->rgVisibleCols;
	hr = pStm->Read(pArr, nByteCount, &nBytesRead);
	if (SUCCEEDED(hr))
  {
    if (nBytesRead < nByteCount)
    {
      hr = E_FAIL;
    }
    else
    {
      // update columns
      AddVisibleColumns(pVisibleColumns);
    }
  }
  delete[] pVisibleColumns;
  pVisibleColumns = 0;
  return S_OK;
}




/////////////////////////////////////////////////////////////////////////////////////////
// CColumnSetList

void CColumnSetList::Initialize(SnapinType snapinType, 
                                MyBasePathsInfo* pBasePathsInfo)
{
  m_pBasePathsInfo = pBasePathsInfo;
  m_snapinType = snapinType;
}

//
// Find the column set given a column set ID
//
CColumnSet* CColumnSetList::FindColumnSet(LPCWSTR lpszColumnID)
{
	POSITION pos = GetHeadPosition();
	while (pos != NULL)
	{
		CColumnSet* pTempSet = GetNext(pos);
		ASSERT(pTempSet != NULL);

		LPCWSTR lpszTempNodeID = pTempSet->GetColumnID();

		if (wcscmp(lpszTempNodeID, lpszColumnID) == 0)
		{
			return pTempSet;
		}
	}
  CColumnSet* pNewColSet = CDSColumnSet::CreateColumnSetFromDisplaySpecifiers(lpszColumnID,
                                                                              m_snapinType,
                                                                              m_pBasePathsInfo);
  if (pNewColSet != NULL)
  {
    AddTail(pNewColSet);
    return pNewColSet;
  }
	return GetDefaultColumnSet();
}

CColumnSet* CColumnSetList::GetDefaultColumnSet()
{
  if (m_pDefaultColumnSet == NULL)
  {
    m_pDefaultColumnSet = CDSColumnSet::CreateColumnSetFromDisplaySpecifiers(DEFAULT_COLUMN_SET,
                                                                             m_snapinType,
                                                                             m_pBasePathsInfo);
  }
  return m_pDefaultColumnSet;
}

HRESULT CColumnSetList::Save(IStream* pStm)
{
  // save # of items in the list
	DWORD dwCount = (DWORD)GetCount(); // list count plus default column set
   if (m_pDefaultColumnSet)
   {
      dwCount++;
   }

	HRESULT hr = SaveDWordHelper(pStm, dwCount);
	if (FAILED(hr))
		return hr;


  // save columnset list
  for (POSITION pos = GetHeadPosition(); pos != NULL; )
	{
		CColumnSet* pTempSet = GetNext(pos);

    if (pTempSet == NULL)
    {
		  ASSERT(pTempSet != NULL);
      continue;
    }

    hr = pTempSet->Save(pStm);
  	if (FAILED(hr))
	  	return hr;
	}

  // save default column set
  if (m_pDefaultColumnSet != NULL)
  {
    hr = m_pDefaultColumnSet->Save(pStm);
  }
	return hr;
}

HRESULT CColumnSetList::Load(IStream* pStm)
{
  // load # of items in the list
  DWORD dwLoadCount;
  HRESULT hr = LoadDWordHelper(pStm, &dwLoadCount);
	if (FAILED(hr))
    return hr;

  // load column list
  CString szColumnID;
  for (DWORD iColSet = 0; iColSet< dwLoadCount; iColSet++)
  {
    // load the string with the name of the column set
    hr = LoadStringHelper(szColumnID, pStm);
    if (FAILED(hr))
      return hr;
    ASSERT(!szColumnID.IsEmpty());
    CColumnSet* pColumnSet = FindColumnSet(szColumnID);
    if (pColumnSet != NULL)
    {
      hr = pColumnSet->Load(pStm);
      if (FAILED(hr))
        return hr;
    }
  }

  return S_OK;
}