/// <summary>
    /// Finds an appropriate visualizer for the given descriptor and attaches it to the given object.
    /// </summary>
    public ZSUVisualizerBase ResolveVisualizerAndAttach(VisualizationDescriptor descriptor, GameObject targetObject)
    {
        if (targetObject.gameObject.GetComponent<ZSUVisualizerBase>() != null)
        {
            Debug.LogError("Target object already has a visualizer.");
            return null;
        }

        GameObject visualizerNode = null;
        ZSUVisualizerBase visualizer = null;

        //
        // Phase 1: Attempt to locate a compatible descendent prefab or game object.
        //
        if (visualizer == null)
        {
            var matchingChild = SelectObject(descriptor);
            if (matchingChild != null)
            {
                var matchingChildVisualizer = matchingChild.GetComponent<ZSUVisualizerBase>();
                if (matchingChildVisualizer != null)
                {
                    // todo: check subclass to ensure its generic argument matches descriptor.type or is simply ZSUVisualizer.
                    visualizerNode = (GameObject)GameObject.Instantiate(matchingChild);
                    visualizer = visualizerNode.GetComponent<ZSUVisualizerBase>();
                }
            }
        }

        //
        // Phase 2: Fallback to type-based lookup.
        //
        if (visualizer == null)
        {
            Type visualizerType = Utility.FindType("ZSU" + descriptor.TargetType.Name + "Visualizer");
            if (visualizerType == null)
            {
                IsMissing("Visualizer", descriptor);
                visualizerType = typeof(ZSUVisualizerBase);
            }

            // Created dedicated child.
            {
                visualizerNode = new GameObject();
            }

            // Create visualizer.
            visualizer = (ZSUVisualizerBase)visualizerNode.AddComponent(visualizerType);
        }

        //
        // Phase 3: common enforcements.
        //

        //visualizerNode.hideFlags = HideFlags.HideAndDontSave;
        visualizerNode.transform.parent = targetObject.transform;
        visualizerNode.layer = targetObject.layer;
        visualizerNode.transform.localPosition = Vector3.zero;
        visualizerNode.transform.localRotation = Quaternion.identity;
        visualizerNode.transform.localScale = Vector3.one;

        visualizerNode.name = string.Format("<{0}>", visualizer.GetType().Name);
        visualizer.AppearanceSet = this;

        return visualizer;
    }
    /// <summary>
    /// Deprecated: Selects an object based on a descriptor with support for incomplete matches.
    /// </summary>
    protected GameObject SelectObject2(VisualizationDescriptor descriptor, bool allowPartialMatch)
    {
        // todo: use CSS-style precedence/search rules?

        var descriptorChain = descriptor.AsChain();
        GameObject candidateObject = null;

        for (int i = 1; i <= descriptorChain.Length; ++i)
        {
            GameObject candidateObjectForChainLengthI = this.gameObject;

            foreach (VisualizationDescriptor descriptorTemp in descriptorChain.Skip(descriptorChain.Length - i))
            {
                string targetType = descriptorTemp.TargetType.Name;
                string targetClass = descriptorTemp.Class;
                string[] searchStrings = new string[]
                {
                    targetType + "." + targetClass, // type.class
                    "." + targetClass, // .class
                    targetType // type
                };

                bool childFound = false;
                foreach (string searchString in searchStrings)
                {
                    var child = candidateObjectForChainLengthI.transform.FindChild(searchString);
                    if (child == null)
                    {
                        continue;
                    }

                    candidateObjectForChainLengthI = child.gameObject;
                    childFound = true;
                }

                if (childFound == false)
                {
                    if (!allowPartialMatch)
                    {
                        candidateObjectForChainLengthI = null;
                    }
                    break;
                }
            }

            if (candidateObjectForChainLengthI != null)
            {
                candidateObject = candidateObjectForChainLengthI;
            }
        }

        return candidateObject;
    }
    protected void IsMissing(string contentType, VisualizationDescriptor descriptor)
    {
        if (!this.WarnIfMissingContent)
        {
            return;
        }

        if (_warnings == null)
        {
            _warnings = new HashSet<string>();
        }

        string warningMessage = string.Format("Missing '{0}' for descriptor '{1}'.", contentType, descriptor.ToString());
        if (_warnings.Contains(warningMessage))
        {
            return;
        }

        Debug.LogWarning(warningMessage);
        _warnings.Add(warningMessage);
    }
    /// <summary>
    /// Returns the first object (if any) that matches the given descriptor.
    /// </summary>
    protected GameObject SelectObject(VisualizationDescriptor descriptor)
    {
        var descriptorChain = descriptor.AsChain();

        // Select all immediate children that have visualization components.
        foreach (ZSUVisualizerBase visualizer
            in this.transform.Cast<Transform>().Select(t => t.GetComponent<ZSUVisualizerBase>()).Where(v => v != null))
        {
            SelectorNode[] selectorChain = SelectorNode.Parse(visualizer.gameObject.name);
            if (selectorChain == null)
            {
                continue;
            }

            bool isVisualizerMatched = DoesSelectorMatchDescriptorChain(selectorChain, descriptorChain);
            if (isVisualizerMatched)
            {
                return visualizer.gameObject;
            }
        }

        // Fall back to old method.
        // TODO: Phase this out and return null.
        return SelectObject2(descriptor, false);
    }
    /// <summary>
    /// CSS-inspired, simplified selector matching.
    /// </summary>
    protected bool DoesSelectorMatchDescriptorChain(SelectorNode[] selectorChain, VisualizationDescriptor[] descriptorChain)
    {
        //
        // Algorithm: Starting at the leaf selector node and leaf descriptor node,
        // we move up both chains, acknowledging compatible node pairs on the way up.
        // The ParentRelationship value is important in permitting descriptor nodes
        // to be skipped or not. The algorithm terminates with a match if we have fully traversed
        // up the selector chain without finding any incompatibility between the chains,
        // and terminates without a match if otherwise.
        //

        VisualizationDescriptor descriptorNode = descriptorChain.LastOrDefault();
        bool isLeafMatched = false;
        for (int i = selectorChain.Length - 1; i >= 0; --i)
        {
            if (descriptorNode == null)
            {
                // Ran out of descriptor nodes, thus the selector was not fully matched.
                return false;
            }
            SelectorNode selectorNode = selectorChain[i];
            if ((selectorNode.Class != null && selectorNode.Class != descriptorNode.Class)
                || (selectorNode.Type != null && selectorNode.Type != descriptorNode.TargetType.Name))
            {
                if (isLeafMatched)
                {
                    // Not a matching node.
                    if (selectorNode.ParentRelationship == SelectorNode.SelectorNodeRelationship.Direct)
                    {
                        // We needed to have directly matched the parent here or bust.
                        return false;
                    }
                    else
                    {
                        // Let's keep our selector position, but move up the descriptor chain.
                        i++;
                        descriptorNode = descriptorNode.ParentDescriptor;
                        continue;
                    }
                }
                else
                {
                    // We needed to at least match the leaf node.
                    return false;
                }
            }
            else
            {
                // Found a match.
                if (!isLeafMatched)
                {
                    isLeafMatched = true;
                }
                descriptorNode = descriptorNode.ParentDescriptor;
            }
        }

        // Completed traversal of the selector chain without returning early.
        // Complete match.
        return true;
    }