use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::fmt;
use hwloc2;
use crate::util;
use sysinfo;

/// Uniquely identifies a topology object.
#[derive(Clone)]
pub struct TopologyObjectID {
    /// Type of the object (cache, package, PU, etc.)
    pub type_id: hwloc2::ObjectType,
    /// hwloc logical index of the object among objects of same type.
    index:   u32
}
impl TopologyObjectID {
    /// Construct ID for given obejct.
    pub fn from_obj(obj: &hwloc2::TopologyObject) -> TopologyObjectID {
        TopologyObjectID{ type_id: obj.object_type(), index: obj.logical_index() }
    }
    /// Construct ID from cached topology object info.
    pub fn from_cached(obj: &TopologyObjectInfo) -> TopologyObjectID {
        TopologyObjectID{ type_id: obj.object_type.clone(), index: obj.logical_index }
    }
    /// True if this ID is the ID of the root object of the hierarchy.
    pub fn is_root(&self) -> bool {
        self.type_id == hwloc2::ObjectType::Machine
    }
}

/// Traits needed for hash table support.
impl PartialEq for TopologyObjectID {
    fn eq(&self, other: &Self) -> bool {
        return self.type_id == other.type_id && self.index == other.index;
    }
}
impl Eq for TopologyObjectID{}
impl Hash for TopologyObjectID {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (self.type_id.clone() as u32).hash(state);
        self.index.hash(state);
    }
}

/// Specifies style to draw an object label, if a non-default style should be used.
#[derive(Debug)]
pub enum TopologyObjectLabelStyle {
    /// Label for a low-level cache that should be smaller to save space.
    SmallCache
}
/// Specifies how to draw a label for a topology object.
#[derive(Debug)]
pub struct TopologyObjectLabel {
    /// Sub-labels to be drawn as lines when we need to save horizontal space.
    pub lines:          Vec<String>,
    /// Any non-default style to use.
    pub style_override: Option<TopologyObjectLabelStyle>
}

/// Precomputed info about a topology object, to avoid repeated recursive computation.
#[derive(Debug)]
pub struct TopologyObjectInfo {
    /// Type of the object (cache, package, PU, etc.)
    pub object_type:    hwloc2::ObjectType,
    /// OS index, used e.g. for PUs to max exactly to OS PU indices.
    pub os_index:       u32,
    /// Depth from root.
    pub depth:          u32,
    /// Arity (how many children the object has).
    pub arity:          u32,
    /// Logical index - unique among objects of the same type.
    pub logical_index:  u32,
    /// If this is a cache, size of the cache in bytes.
    pub cache_size:     Option<u64>,
    /// If this object contains PUs, set of all PUs contained.
    pub cpuset:         Option<hwloc2::CpuSet>,
    /// Whether to draw this object in a horizontal layout (children side-by-side horizontally).
    pub use_horizontal: bool,
    /// Whether this object has at least one sibling in the same parent.
    pub has_siblings:   bool,
    /// Whether to draw this object with a frame (to easier differentiate it from other objects).
    pub use_frame:      bool,
    /// Number of nest levels within this object where all children have a frame.
    pub frame_level:    u16,
    /// Whether this object recursively contains only a single core (may have multiple PUs).
    pub is_single_core: bool,
    /// Name of the CPU this object is in, if any.
    pub cpu_name:       Option<String>,
    /// Specifies how to draw the label for this object.
    pub label:          Option<TopologyObjectLabel>,
    /// Indices of this objects' children, used for children iterators.
    child_indices:      Vec<usize>
}
impl TopologyObjectInfo {
    /// Get ID of this topology object.
    pub fn id(&self) -> TopologyObjectID {
        TopologyObjectID { type_id: self.object_type.clone(), index: self.logical_index }
    }

    /// Get the number of children of this topology object.
    pub fn child_count(&self) -> usize {
        self.child_indices.len()
    }

    /// True if this is a PU object.
    pub fn is_pu(&self) -> bool {
        self.object_type == hwloc2::ObjectType::PU
    }

    /// True if this is a package object.
    pub fn is_package(&self) -> bool {
        self.object_type == hwloc2::ObjectType::Package
    }
}

/// Iterator over TopologyObjectInfo items representing children of a single topology object.
#[derive(Clone)]
pub struct TopologyObjectChildren<'a> {
    /// Reference to `TopologyCache.storage`.
    storage:       &'a Vec<TopologyObjectInfo>,
    /// Reference to `TopologyObjectInfo.child_indices` of the parent.
    child_indices: &'a Vec<usize>,
    /// Index (into `child_indices`) of the next child to iterate.
    next_index:    usize,
    /// Index (into `child_indices`) of the first child in the iterator (needed for subslicing).
    start:         usize,
    /// Index (into `child_indices`) of the last child in the iterator (needed for subslicing).
    end:           usize
}

/// Shortened Debug implementation, for less spammy output.
impl<'a> fmt::Debug for TopologyObjectChildren<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "storage.len(): {},", self.storage.len())?;
        write!(f, "child_idices: {:?},", self.child_indices)?;
        write!(f, "next_index: {:?}, start: {:?}, end: {:?}\n",
               self.next_index, self.start, self.end)
    }
}
impl<'a> Iterator for TopologyObjectChildren<'a> {
    type Item = &'a TopologyObjectInfo;
    fn next(&mut self) -> Option<&'a TopologyObjectInfo> {
        let result = self.peek();
        if result.is_some() {
            self.next_index += 1;
        }
        result
    }
}
impl<'a> ExactSizeIterator for TopologyObjectChildren<'a> {
    fn len(&self) -> usize {
        assert!(self.next_index <= self.end, "next_index overflow");
        assert!(self.end <= self.child_indices.len(), "end overflow");
        self.end - self.next_index
    }
}
impl<'a> TopologyObjectChildren<'a> {
    /// Get the first item without advancing the iterator. None if the iterator is empty.
    pub fn peek(&self) -> Option<&'a TopologyObjectInfo> {
        assert!(self.next_index <= self.end, "invalid next item index in TopologyObjectChildren");
        self.child_indices.get(self.next_index).and_then(|idx| {
            if self.next_index < self.end {
                Some(&self.storage[*idx])
            } else {
                None
            }
        })
    }

    /// Get an iterator of a 'subslice' of this iterator.
    ///
    /// start: First index (inclusive) of the subiterator relative to current iterator.
    /// end:   Last index (exclusive) of the subiterator relative to current iterator.
    pub fn subiter(&self, start: usize, end: usize) -> TopologyObjectChildren<'a> {
        assert!(start <= end, "start and end of subiter in wrong order");
        let new_start = usize::min(self.next_index + start, self.child_indices.len());
        let new_end   = usize::min(self.next_index + end,   self.child_indices.len());
        TopologyObjectChildren {
            storage:       self.storage,
            child_indices: self.child_indices,
            next_index:    new_start,
            start:         new_start,
            end:           new_end
        }
    }

    /// Get item at specified index in the iterator (basically implementing D's RandomAcccessRange).
    pub fn get(&self, index: usize) -> &'a TopologyObjectInfo {
        let idx = self.child_indices.get(self.next_index + index)
                      .expect(format!("index out of range: {} (context: {:?})", index, self).as_str());
        &self.storage[*idx]
    }

    /// Call `func` for each 'group' of children of same type (e.g. caches containing same subcaches/cores)
    ///
    /// The second parameter to the lambda is `true` only for the last group.
    pub fn by_group<F>(self, mut func: F ) where F: FnMut(TopologyObjectChildren<'a>, bool) {
        let mut group_start = 0;
        for (i, child) in self.clone().enumerate() {
            if TopologyCache::objects_differ(self.get(group_start), child) {
                let group = self.subiter(group_start, i);
                group_start = i;
                func(group, false);
            }
        }
        let group = self.subiter(group_start, self.len());
        func(group, true);
    }
}

/// Stores precomputed data about all topology objects, with direct (table) as well as recursive access.
pub struct TopologyCache {
    /// Maps topology object ID to object indices in `storage`.
    cache:   HashMap<TopologyObjectID, usize>,
    /// Stores actual topology object ID.
    storage: Vec<TopologyObjectInfo>,
    /// CPU sets of each logical core.
    ///
    /// This has one set per logical core, and each set only contains a single logical core/PU.
    cpusets_pu:   Vec<hwloc2::CpuSet>,
    /// CPU sets of each physical core.
    ///
    /// Each set contains all logical cores of corresponding physical core.
    cpusets_core: Vec<hwloc2::CpuSet>,
    /// CPU sets of each L3 cache.
    ///
    /// Each set contains all logical cores of cores under the L3 caches.
    cpusets_l3:   Vec<hwloc2::CpuSet>,
}

impl TopologyCache {
    /// Construct a TopologyCache for all topology objects within given `root`.
    pub fn new(root: &hwloc2::TopologyObject, cpus: &[sysinfo::Cpu]) -> Option<TopologyCache> {
        let mut cache   = HashMap::new();
        let mut storage = Vec::new();

        /// Recursively process children of `object` and extract/cache topology object info.
        fn recursive_process_children(
            object:  &hwloc2::TopologyObject,
            cpus:    &[sysinfo::Cpu],
            cache:   &mut HashMap<TopologyObjectID, usize>,
            storage: &mut Vec<TopologyObjectInfo>)
        {
            let mut child_indices = Vec::new();
            for child in object.children() {
                recursive_process_children( child, cpus, cache, storage );
                // The child was just pushed at the end of the 'recursive_process_children()' call
                child_indices.push(storage.len() - 1);
            }

            let key = TopologyObjectID{ type_id: object.object_type(), index: object.logical_index() };
            let cpu_name = if key.type_id == hwloc2::ObjectType::Package {
                Some(TopologyCache::cpu_name(cpus, object))
            } else {
                None
            };
            let value = TopologyObjectInfo {
                object_type:    key.type_id.clone(),
                os_index:       object.os_index(),
                depth:          object.depth(),
                arity:          object.arity(),
                logical_index:  object.logical_index(),
                cache_size:     object.cache_attributes().and_then(|attr| Some(attr.size)),
                cpuset:         object.cpuset(),
                use_horizontal: TopologyCache::topology_object_use_horizontal(object),
                has_siblings:   TopologyCache::has_siblings(object),
                use_frame:      TopologyCache::topology_object_use_frame(object),
                frame_level:    TopologyCache::topology_object_frame_level(object),
                is_single_core: TopologyCache::is_single_core(object),
                cpu_name:       cpu_name.clone(),
                label:          TopologyCache::topology_object_label(object, cpu_name),
                child_indices
            };
            let index: usize = storage.len();
            cache.insert( key, index );
            // Pushing to storage here at the end means root is at the end of storage.
            storage.push( value );
        }

        recursive_process_children(root, cpus, &mut cache, &mut storage);

        /// Initialize `cpusets` to contain all CPU sets of topology objects of type `obj_type` in the hierarchy under `obj`.
        fn init_cpusets_by_type(obj: &hwloc2::TopologyObject, obj_type: hwloc2::ObjectType, cpusets: &mut Vec<hwloc2::CpuSet>) {
            if obj.object_type() == obj_type {
                cpusets.push(obj.cpuset().expect("We should be able to get the list of processing units of a core/logical core/L3"));
            } else {
                for child in obj.children() {
                    init_cpusets_by_type(child, obj_type.clone(), cpusets);
                }
            }
        }
        let mut cpusets_pu   = Vec::new();
        let mut cpusets_core = Vec::new();
        let mut cpusets_l3   = Vec::new();
        init_cpusets_by_type(root, hwloc2::ObjectType::PU,      &mut cpusets_pu);
        init_cpusets_by_type(root, hwloc2::ObjectType::Core,    &mut cpusets_core);
        init_cpusets_by_type(root, hwloc2::ObjectType::L3Cache, &mut cpusets_l3);

        Some(TopologyCache{
            cache,
            storage,
            cpusets_pu,
            cpusets_core,
            cpusets_l3
        })
    }

    /// Get info about the root object of the topology. Should recursively own all cores.
    pub fn root(&self) -> &TopologyObjectInfo {
        self.storage.last().expect("There should be at least one topology object - the root")
    }

    /// Get CPU sets of each logical core.
    pub fn cpusets_pu(&self) -> &[hwloc2::CpuSet] {
        self.cpusets_pu.as_slice()
    }

    /// Get CPU sets of each physical core.
    pub fn cpusets_core(&self) -> &[hwloc2::CpuSet] {
        self.cpusets_core.as_slice()
    }

    /// Get CPU sets of each L3 cache.
    pub fn cpusets_l3(&self) -> &[hwloc2::CpuSet] {
        self.cpusets_l3.as_slice()
    }

    /// Get info for topology object with given ID.
    pub fn get_by_id(&self, id: TopologyObjectID) -> &TopologyObjectInfo {
        &self.storage[*self.cache.get(&id).expect("ID of a nonexistent topology object")]
    }

    /// Get info for the next topology object (if any) with same object type as `id`.
    pub fn next_by_type(&self, id: TopologyObjectID) -> Option<TopologyObjectID> {
        let new_id = TopologyObjectID{ type_id: id.type_id, index: id.index + 1};
        self.cache.get(&new_id).and_then(|_item| Some(new_id))
    }

    /// Get info for the previous topology object (if any) with same object type as `id`.
    pub fn previous_by_type(&self, id: TopologyObjectID) -> Option<TopologyObjectID> {
        if id.index == 0 {
            return None
        }
        let new_id = TopologyObjectID{ type_id: id.type_id, index: id.index - 1};
        self.cache.get(&new_id).and_then(|_item| Some(new_id))
    }

    /// Get info for given topology object.
    pub fn get_by_obj(&self, obj: &hwloc2::TopologyObject) -> &TopologyObjectInfo {
        self.get_by_id(TopologyObjectID::from_obj(obj))
    }

    /// Get an iterator of all children of topology object corresponding to `info`.
    pub fn get_children<'a>(&'a self, info: &'a TopologyObjectInfo) -> TopologyObjectChildren<'a> {
        TopologyObjectChildren{
            storage: &self.storage,
            child_indices: &info.child_indices,
            next_index: 0,
            start: 0, 
            end: info.child_indices.len()
        }
    }

    /// Get parent, if any, topology object corresponding to `info`.
    pub fn get_parent(&self, info: &TopologyObjectInfo) -> Option<&TopologyObjectInfo> {
        let id = TopologyObjectID::from_cached(info);
        let own_index = *self.cache.get(&id).expect("topology object not in cache");
        // Inefficient, but not called from any performance-critical code.
        self.storage.iter().find(|&obj| obj.child_indices.iter().any(|&c| c == own_index))
    }

    /// Determine if `info` is a child or a child of a recursive child of `possible_parent`.
    pub fn is_recursive_child_of(&self, info: &TopologyObjectInfo, possible_parent: &TopologyObjectID) -> bool {
        let id = TopologyObjectID::from_cached(info);
        if id == *possible_parent {
            true
        } else if let Some(parent) = self.get_parent(self.get_by_id(id)) {
            self.is_recursive_child_of(parent, possible_parent)
        } else {
            false
        }
    }

    /// Return info about the sibling object displayed above `info` in a topology view.
    ///
    /// Since topologi view depends on `by_group()` and `use_horizontal`, topology cache can 
    /// compute their relative layout, if not precise positions.
    ///
    /// May return `info`, if there is nothing above.
    pub fn sibling_up(&self, info: &TopologyObjectID) -> TopologyObjectID {
        self.sibling_relative(info, (0, -1))
    }

    /// Return info about the sibling object displayed below `info` in a topology view.
    ///
    /// Since topologi view depends on `by_group()` and `use_horizontal`, topology cache can 
    /// compute their relative layout, if not precise positions.
    ///
    /// May return `info`, if there is nothing below.
    pub fn sibling_down(&self, info: &TopologyObjectID) -> TopologyObjectID {
        self.sibling_relative(info, (0, 1))
    }

    /// Get sibling at specified relative position to `info` in a topology view.
    ///
    /// * `delta` is the relative position of the sibling, represented in number of objects X/Y
    ///   on a grid of siblings on a topology view.
    ///
    /// Will only get siblings in the same group (see `by_group()`). If `delta` would go out of
    /// bounds, returns the furthest sibling in that direction.
    fn sibling_relative(&self, info: &TopologyObjectID, delta: (isize, isize)) -> TopologyObjectID {
        let parent = if let Some(parent) = self.get_parent(self.get_by_id(info.clone())) {
            parent
        } else {
            return info.clone();
        };

        let mut sibling_id:TopologyObjectID = info.clone();
        self.get_children(parent).by_group(|children, _is_last| {
            let group_sibling_count = children.clone().count();
            let (rows, cols) = util::squarish_grid_dimensions(group_sibling_count, parent.use_horizontal);
            let index = children.clone().position(|c: &TopologyObjectInfo| TopologyObjectID::from_cached(c) == *info);
            if let Some(index_in_group) = index {
                let (x, y) = (index_in_group % cols, index_in_group / cols);
                let (delta_x, delta_y) = delta;

                let result_x = (cols-1).min(x.saturating_add_signed(delta_x));
                let result_y = (rows-1).min(y.saturating_add_signed(delta_y));
                let sibling_index_in_group = result_y * cols + result_x;
                let sibling = children.clone().skip(sibling_index_in_group).next().unwrap();
                sibling_id = TopologyObjectID::from_cached(sibling);
            } else {
                return;
            }
        });

        sibling_id    
    }

    /// True if `a` and `b` are different 'kinds' of objects for purpose of grouping (e.g. big vs LITTLE cores).
    pub fn objects_differ(a: &TopologyObjectInfo, b: &TopologyObjectInfo) -> bool {
        if a.object_type.clone() != b.object_type.clone() {
            return true
        } 
        use hwloc2::ObjectType;
        match(a.object_type.clone(), b.object_type.clone()) {
            (ObjectType::L1Cache | ObjectType::L2Cache | ObjectType::L3Cache | ObjectType::L4Cache | ObjectType::L5Cache, 
             ObjectType::L1Cache | ObjectType::L2Cache | ObjectType::L3Cache | ObjectType::L4Cache | ObjectType::L5Cache) => {
                if a.cache_size.unwrap() != b.cache_size.unwrap()  {
                    return true
                }
             },
            (ObjectType::Package, ObjectType::Package) => {
                if a.cpu_name != b.cpu_name {
                    return true
                }
            },
            _ => {}
        }
            
        a.arity != b.arity || a.depth != b.depth
    }

    /// Determine whether `obj` should be rendered in a horizontal manner, taking more X than Y space.
    /// 
    /// May be done in some cases by a horizontal layout, in others by a grid where the 'longer'
    /// side is horizontal, etc.
    /// 
    /// If false, using vertical space is prioritized.
    fn topology_object_use_horizontal(obj: &hwloc2::TopologyObject) -> bool {
        if obj.object_type() == hwloc2::ObjectType::PU {
            // PUs themselves are 'vertical', and cores above them 'horizontal' as a result - i.e. 
            // PUs are shown side by side horizontally.
            false
        } else if obj.object_type() == hwloc2::ObjectType::Package {
            // Packages (CPUs) are vertical, i.e. any core groups with shared last-level-caches in
            // those packages are stacked vertically. This doesn't matter for single-core-group
            // CPUs, but uses space optimally for e.g. Zen1-2 CPUs with multiple core groups.
            false
        } else {
            let children = obj.children();
            if children.len() == 0 {
                // Leaf object that is not a PU - for completeness, we don't currently draw such objects.
                true
            } else if children.len() == 1 {
                // Caches on top of a single core and similar cases where an object is 'layered' on
                // top of a child are stacked vertically.
                false
            } else {
                let child_orientation = Self::topology_object_use_horizontal(children[0]);
                let all_children_same_orientation =
                    children[1..].iter().any(|&child|{Self::topology_object_use_horizontal(child) != child_orientation});
                if !all_children_same_orientation {
                    // Inconsistent child directions, use horizontal as default
                    true
                } else {
                    // Default case for multiple children - opposite orientation vs the child nodes.
                    !child_orientation
                }
            }
        }
    }

    /// Determine if we should draw a frame around a topology object.
    /// 
    /// Usually frames are used to differentiate an object from its siblings on the same level.
    fn topology_object_use_frame(obj: &hwloc2::TopologyObject) -> bool {
        match obj.object_type() {
            hwloc2::ObjectType::Core =>    false,
            hwloc2::ObjectType::PU =>      false,
            hwloc2::ObjectType::Package => true,
            _ =>                           TopologyCache::has_siblings(obj)
        }
    }

    /// Number of nest levels within this object (including itself) where all children have `use_frame` as true.
    ///
    /// 0 if `use_frame` is false.
    fn topology_object_frame_level(obj: &hwloc2::TopologyObject) -> u16 {
        if Self::topology_object_use_frame(obj) {
            if obj.children().iter().all(|c| Self::topology_object_use_frame(c)) {
                obj.children().iter().map(|c| Self::topology_object_frame_level(c)).max().unwrap_or(0) + 1
            } else {
                1
            }
        } else {
            0
        }
    }

    /// Determine if `obj` has any siblings.
    pub fn has_siblings(obj: &hwloc2::TopologyObject) -> bool {
        if let Some(parent) = obj.parent() {
            parent.children().len() > 1
        } else {
            false
        }
    }

    /// Determine if the hierarchy under `obj` consists of a single core and related objects (e.g. cache)
    /// 
    /// Such 'single cores' are then drawn in a specific way, from the topmost object that is still 
    /// related to (contains in children recursively) the one core.
    pub fn is_single_core(obj: &hwloc2::TopologyObject) -> bool {
        if obj.object_type() == hwloc2::ObjectType::Core {
            return true;
        }
        let children = obj.children();
        // Either a node owning multiple cores, or a leaf node that is not a core.
        if children.len() != 1 {
            return false;
        }
        return Self::is_single_core(children[0]);
    }

    /// Get a human-readable name of given physical CPU (hwloc Package).
    pub fn cpu_name(cpus: &[sysinfo::Cpu], obj: &hwloc2::TopologyObject) -> String {
        assert!(obj.object_type() == hwloc2::ObjectType::Package, "`cpu_name` should only be called for 'Package' objects");
        TopologyCache::cpu_name_recursive(cpus, obj).unwrap_or(String::from("CPU"))
    }

    /// Implementation of `cpu_name`, recursively searches for the first `PU` to get corresponding name.
    fn cpu_name_recursive(cpus: &[sysinfo::Cpu], obj: &hwloc2::TopologyObject) -> Option<String> {
        if obj.object_type() == hwloc2::ObjectType::PU {
            // Use `sysinfo` to get name corresponding to the PU's OS index.
            let index = obj.os_index() as usize; 
            if index >= cpus.len() {
                // Default name if hwloc2 os_index somehow does not match sysinfo cpus.
                return Some(String::from("CPU"));
            }
            return Some(util::cpu_brand_transform(cpus[index].brand()));
        } else {
            // Get to the first child that is a PU, and so we can get its name.
            for child in obj.children() {
                let name = TopologyCache::cpu_name_recursive(cpus, child);
                if name.is_some() {
                    return name;
                }
            }
        }
        // Reached a leaf node that is not a PU.
        return None;
    }

    /// Prepare text/style to draw for a given object's label.
    fn topology_object_label(obj: &hwloc2::TopologyObject, cpu_name: Option<String>) -> Option<TopologyObjectLabel> {
        use hwloc2::ObjectType;

        match obj.object_type() {
            ObjectType::L1Cache | ObjectType::L2Cache | ObjectType::L3Cache 
                                | ObjectType::L4Cache | ObjectType::L5Cache => {
                let size = obj.cache_attributes().and_then(|attr| Some(attr.size)).unwrap();
                let name = match obj.object_type() {
                    ObjectType::L1Cache => "L1d",
                    ObjectType::L2Cache => "L2",
                    ObjectType::L3Cache => "L3",
                    ObjectType::L4Cache => "L4",
                    ObjectType::L5Cache => "L5",
                    _ => {unreachable!("guaranteed by above match");}
                };
                // L1/L2 caches use a smaller font as they tend to be per-core.
                let style_override = if [ObjectType::L1Cache, ObjectType::L2Cache].contains( &obj.object_type()) {
                    Some(TopologyObjectLabelStyle::SmallCache)
                    // ui.style_mut().override_text_style = Some(egui::TextStyle::Name("SmallCache".into()));
                } else {
                    None
                };
                let lines = vec![
                    String::from(name), 
                    util::format_cache_size(size, 2)
                ];
                Some(TopologyObjectLabel{ lines, style_override })
            },
            ObjectType::Package => {
                // ui.label(obj.cpu_name.as_ref().unwrap());
                Some(TopologyObjectLabel{
                    lines: vec![cpu_name.as_ref().unwrap().clone()],
                    style_override: None
                })
            },
            ObjectType::Machine | ObjectType::Core => {
                // Don't render these to reduce clutter.
                None
            },
            _ => {
                Some(TopologyObjectLabel{
                    lines: vec![format!("{:?}", obj.object_type())],
                    style_override: None
                })
            }
        }
    }
}
