/// <summary>
        /// Retrieves chain of command for entity represented by UPN.
        /// If bTrio is not set, this returns list of single item lists terminating with single item list containing CEO.
        /// If bTrio is set, this returns list of trio leader lists terminating with single item list containing CEO.
        /// </summary>
        /// <param name="strUPN">Main person we are displaying in the org chart.</param>
        /// <param name="bTrio">Whether we are displaying in trio mode.</param>
        /// <param name="strErrors">Error return value.</param>
        /// <returns>list of list of users</returns>
        public List <List <JObject> > GetAncestorsAndMain(string strUPN, bool bTrio, ref string strErrors)
        {
            List <List <JObject> > returnedListOfLists = new List <List <JObject> >();

            // preserve original error
            string strOriginalError = strErrors;

            // split comma delimited UPN list into UPNs
            string[] arrayUPN = strUPN.Split(',');
            for (int idxTrio = 0; idxTrio < arrayUPN.Length; idxTrio++)
            {
                // retrieve graph node for this person (or for each trio member) from graph
                string  strMainUPN = arrayUPN[idxTrio];
                JObject graphUser  = this.extensions.GetUser(strMainUPN, ref strErrors);

                // TODO: this logic is dependent on trios being properly filled in at each level of hierarchy

                // enumerate graph users from the main person (or from each trio member) to the root
                int idxAncestorOrMain = 0;
                while (graphUser != null)
                {
                    // create a new AncestorOrMain trio list if processing non-trio member or first member of trio
                    if (idxTrio == 0)
                    {
                        returnedListOfLists.Insert(0, new List <JObject>());
                    }

                    // get next graph user
                    JObject graphUserParent = this.extensions.GetUsersManager((string)graphUser["userPrincipalName"], ref strErrors);

                    // tag user with manager attribute
                    graphUser["managerUserPrincipalName"] = (graphUserParent != null) ? graphUserParent["userPrincipalName"] : "NO MANAGER";

                    // insert user at end of the correct AncestorOrMain trio list
                    JToken tokenTrio = null;
                    if (bTrio && graphUser.TryGetValue(DirectoryExtensions.GetExtensionName("trio"), out tokenTrio))
                    {
                        // trio mode and there is a trio set on this object, add to list each time through
                        returnedListOfLists.ElementAt(returnedListOfLists.Count - idxAncestorOrMain - 1).Add(graphUser);
                    }
                    else if (idxTrio == 0)
                    {
                        // otherwise, this user not part of a trio, just add the first time through
                        returnedListOfLists.ElementAt(returnedListOfLists.Count - idxAncestorOrMain - 1).Add(graphUser);
                    }

                    // detect infinite loop: user is own manager, skip, notify and allow user to fix
                    if ((graphUserParent != null) && ((string)graphUserParent["userPrincipalName"] == (string)graphUser["userPrincipalName"]))
                    {
                        strErrors += (string)graphUser["userPrincipalName"] + " has itself as manager. Please resolve.\n";
                        break;
                    }

                    // set next graph user
                    graphUser = graphUserParent;

                    // increment the ancestor level
                    idxAncestorOrMain++;
                }

                // we know that root doesn't have a manager, restore original error
                if (graphUser == null)
                {
                    strErrors = strOriginalError;
                }
            }

            return(returnedListOfLists);
        }
        /// <summary>
        /// Retrieves subordinates for entity represented by UPN.
        /// Each direct subordinate is head of a list, with subordinates of that direct as elements of that list.
        /// </summary>
        /// <param name="strUPN">Main person we are displaying in the org chart.</param>
        /// <param name="bTrio">Whether we are displaying in trio mode.</param>
        /// <param name="strErrors">Error return value.</param>
        /// <returns>list of list of users</returns>
        public List <List <JObject> > GetDirectsOfDirects(string strUPN, bool bTrio, ref string strErrors)
        {
            List <List <JObject> > returnedListOfLists = new List <List <JObject> >();

            // split comma delimited UPN list into UPNs
            string[] arrayUPN = strUPN.Split(',');

            // TODO: if in trio mode and only one passed member, do filtered query for rest of trio
            // TODO: more efficient to do this filtered query for rest of trio in GetAncestorsAndMain

            // now retrieve direct reports for single person or trio
            for (int i = 0; i < arrayUPN.Length; i++)
            {
                string strMainUPN = arrayUPN[i];
                JUsers directs    = this.extensions.GetUsersDirectReports(strMainUPN, ref strErrors);
                if (directs != null && directs.users != null)
                {
                    foreach (JObject directReport in directs.users)
                    {
                        // add a new list at front of list of lists
                        returnedListOfLists.Insert(0, new List <JObject>());

                        // tag the direct report with manager attribute
                        directReport["managerUserPrincipalName"] = strMainUPN;

                        // insert the direct report at front of newly inserted list
                        returnedListOfLists.ElementAt(0).Insert(0, directReport);

                        // get direct reports of the direct report (and tag manager state to color code managers among directs)
                        JUsers directsOfDirect = this.extensions.GetUsersDirectReports((string)directReport["userPrincipalName"], ref strErrors);
                        directReport["isManager"] = directsOfDirect.users.Count > 0;
                        foreach (JObject directOfDirect in directsOfDirect.users)
                        {
                            // tag each direct of direct with manager attribute and assume they are not managers (for purposes of coloring)
                            directOfDirect["managerUserPrincipalName"] = (string)directReport["userPrincipalName"];
                            directOfDirect["isManager"] = false;
                            returnedListOfLists.ElementAt(0).Add(directOfDirect);
                        }
                    }
                }
            }

            // sort the list of lists by trio
            // http://stackoverflow.com/questions/3309188/c-net-how-to-sort-a-list-t-by-a-property-in-the-object
            returnedListOfLists.Sort(
                delegate(List <JObject> x, List <JObject> y)
            {
                JToken tokenTrioX = null;
                bool bxTrio       = x.ElementAt(0).TryGetValue(DirectoryExtensions.GetExtensionName("trio"), out tokenTrioX);
                JToken tokenTrioY = null;
                bool byTrio       = y.ElementAt(0).TryGetValue(DirectoryExtensions.GetExtensionName("trio"), out tokenTrioY);

                if (!bxTrio && !byTrio)
                {
                    // if neither has a trio, they are equal
                    return(0);
                }
                else if (bxTrio && !byTrio)
                {
                    // if only one has a trio, that one comes first
                    return(-1);
                }
                else if (!bxTrio && byTrio)
                {
                    // if only one has a trio, that one comes first
                    return(1);
                }
                else if (bxTrio && byTrio)
                {
                    // if both have trios, perform the comparison to determine which one comes first
                    return(((string)tokenTrioX).CompareTo((string)tokenTrioY));
                }
                else
                {
                    return(0);
                }
            });
            return(returnedListOfLists);
        }