// SPDX-FileCopyrightText: 2026 dcel contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use maplike::Get;

use crate::{
    Dcel, Face, FaceId, HalfEdge, HalfEdgeId, Vertex, VertexId,
    walkers::{
        CirculateEdgesWithExcludesIter, CirculateEdgesWithExcludesReverseIter,
        CirculateEdgesWithExcludesReverseWalker, CirculateEdgesWithExcludesWalker,
        CirculateHalfEdgesWithExcludesIter, CirculateHalfEdgesWithExcludesReverseIter,
        CirculateHalfEdgesWithExcludesReverseWalker, CirculateHalfEdgesWithExcludesWalker,
        CirculateHalfSpokesReverseIter, CirculateVertexesWithExcludesIter,
        CirculateVertexesWithExcludesReverseIter, CirculateVertexesWithExcludesReverseWalker,
        CirculateVertexesWithExcludesWalker, CirculationHalfSpokesIter,
        CirculationHalfSpokesReverseWalker, CirculationHalfSpokesWalker,
        CirculationInterspokesIter, CirculationInterspokesReverseIter,
        CirculationInterspokesReverseWalker, CirculationInterspokesWalker, CirculationSpokesIter,
        CirculationSpokesReverseIter, CirculationSpokesReverseWalker, CirculationSpokesWalker,
        HalfSpokesIter, HalfSpokesReverseIter, HalfSpokesReverseWalker, HalfSpokesWalker,
        InterspokesIter, InterspokesReverseIter, InterspokesReverseWalker, InterspokesWalker,
        SpokesIter, SpokesReverseIter, SpokesReverseWalker, SpokesWalker,
    },
};

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC, FC> Dcel<VW, HEW, FW, VC, HEC, FC> {
    /// Iterate over half-spokes sticking out from a vertex in forward
    /// direction.
    #[inline]
    pub fn vertex_half_spokes(
        &self,
        vertex: VertexId,
    ) -> HalfSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.half_spokes(self.vertex_representative(vertex))
    }

    /// Iterate over half-spokes sticking out from a vertex in backward
    /// direction.
    #[inline]
    pub fn vertex_half_spokes_reverse(
        &self,
        vertex: VertexId,
    ) -> HalfSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.half_spokes_reverse(self.vertex_representative(vertex))
    }
}

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC: Get<usize, Value = HalfEdge<HEW>>, FC>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over vertex rim vertices in forward direction.
    #[inline]
    pub fn vertex_rim_vertices(
        &self,
        vertex: VertexId,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesWalker {
            circulator: self.vertex_rim_half_edges(vertex).walker(),
        }
        .iter(self)
    }

    /// Iterate over vertex rim vertices in backward direction.
    #[inline]
    pub fn vertex_rim_vertices_reverse(
        &self,
        vertex: VertexId,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesReverseWalker {
            circulator: self.vertex_rim_half_edges_reverse(vertex).walker(),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC: Get<usize, Value = HalfEdge<HEW>>, FC>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over vertex rim half-edges in forward direction.
    #[inline]
    pub fn vertex_rim_half_edges(
        &self,
        vertex: VertexId,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        let initial_half_edge = self.next(self.vertex_representative(vertex));
        let excluded_half_edges = self
            .vertex_spokes(vertex)
            .flat_map(|edge| [edge.lesser(), edge.greater()])
            .collect::<Vec<HalfEdgeId>>();

        // Boundary vertices do not have cycles circulating them.
        // Return an empty walker-iterator instead.
        if self.check_if_boundary_vertex(vertex) {
            return CirculateHalfEdgesWithExcludesWalker {
                initial_half_edge,
                curr_half_edge: None,
                excluded_half_edges,
            }
            .iter(self);
        }

        self.circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
    }

    /// Iterate over vertex rim half-edges in backward direction.
    #[inline]
    pub fn vertex_rim_half_edges_reverse(
        &self,
        vertex: VertexId,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        let initial_half_edge = self.prev(self.twin(self.vertex_representative(vertex)));
        let excluded_half_edges = self
            .vertex_spokes(vertex)
            .flat_map(|edge| [edge.lesser(), edge.greater()])
            .collect::<Vec<HalfEdgeId>>();

        // Boundary vertices do not have cycles circulating them.
        // Return an empty walker-iterator instead.
        if self.check_if_boundary_vertex(vertex) {
            return CirculateHalfEdgesWithExcludesReverseWalker {
                initial_half_edge,
                curr_half_edge: None,
                excluded_half_edges,
            }
            .iter(self);
        }

        self.circulate_half_edges_with_excludes_reverse(initial_half_edge, excluded_half_edges)
    }
}

impl<VW, HEW, FW, VC, HEC, FC> Dcel<VW, HEW, FW, VC, HEC, FC> {
    /// Iterate over half-spokes sticking out from a vertex in forward
    /// direction.
    #[inline]
    pub fn half_spokes(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> HalfSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        HalfSpokesWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
        }
        .iter(self)
    }

    /// Iterate over half-spokes sticking out from a vertex in backward
    /// direction.
    #[inline]
    pub fn half_spokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> HalfSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        HalfSpokesReverseWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC: Get<usize, Value = HalfEdge<HEW>>, FC>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over vertex spokes in forward direction.
    #[inline]
    pub fn vertex_spokes(&self, vertex: VertexId) -> SpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.spokes(self.vertex_representative(vertex))
    }

    /// Iterate over vertex spokes in backward direction.
    #[inline]
    pub fn vertex_spokes_reverse(
        &self,
        vertex: VertexId,
    ) -> SpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.spokes_reverse(self.vertex_representative(vertex))
    }
}

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC: Get<usize, Value = HalfEdge<HEW>>, FC>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over vertex rim edges in forward direction.
    #[inline]
    pub fn vertex_rim_edges(
        &self,
        vertex: VertexId,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesWalker {
            circulator: self.vertex_rim_half_edges(vertex).walker(),
        }
        .iter(self)
    }

    /// Iterate over vertex rim edges in backward direction.
    #[inline]
    pub fn vertex_rim_edges_reverse(
        &self,
        vertex: VertexId,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesReverseWalker {
            circulator: self.vertex_rim_half_edges_reverse(vertex).walker(),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC: Get<usize, Value = Vertex<VW>>, HEC: Get<usize, Value = HalfEdge<HEW>>, FC>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over interspokes sharing a vertex in forward direction.
    #[inline]
    pub fn vertex_interspokes(
        &self,
        vertex: VertexId,
    ) -> InterspokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.interspokes(self.vertex_representative(vertex))
    }

    /// Iterate over interspokes sharing a vertex in backward direction.
    #[inline]
    pub fn vertex_interspokes_reverse(
        &self,
        vertex: VertexId,
    ) -> InterspokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.interspokes_reverse(self.vertex_representative(vertex))
    }
}

impl<VW, HEW, FW, VC, HEC: Get<usize, Value = HalfEdge<HEW>>, FC> Dcel<VW, HEW, FW, VC, HEC, FC> {
    /// Iterate over vertex spokes in forward direction.
    #[inline]
    pub fn spokes(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> SpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        SpokesWalker {
            circulator: HalfSpokesWalker {
                initial_half_edge,
                curr_half_edge: Some(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over vertex spokes in backward direction.
    #[inline]
    pub fn spokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> SpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        SpokesReverseWalker {
            circulator: HalfSpokesReverseWalker {
                initial_half_edge,
                curr_half_edge: Some(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over interspokes sharing a vertex in forward direction.
    #[inline]
    pub fn interspokes(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> InterspokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        InterspokesWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
        }
        .iter(self)
    }

    /// Iterate over interspokes sharing a vertex in backward direction.
    #[inline]
    pub fn interspokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> InterspokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        InterspokesReverseWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC, HEC: Get<usize, Value = HalfEdge<HEW>>, FC> Dcel<VW, HEW, FW, VC, HEC, FC> {
    /// Iterate over vertices in the circulation around the same incident face
    /// in forward direction.
    #[inline]
    pub fn circulate_vertices(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_vertices_with_excludes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over vertices in the circulation around the same incident face
    /// in backward direction.
    #[inline]
    pub fn circulate_vertices_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_vertices_with_excludes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over vertices in the circulation around the same incident face
    /// in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_vertices_with_excludes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesWalker {
            circulator: self
                .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over vertices in the circulation around the same incident face
    /// in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_vertices_with_excludes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesReverseWalker {
            circulator: self
                .circulate_half_edges_with_excludes_reverse(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over half-edges in the circulation around the same incident face
    /// in forward direction.
    #[inline]
    pub fn circulate_half_edges(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_half_edges_with_excludes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over half-edges in the circulation around the same incident face
    /// in backward direction.
    #[inline]
    pub fn circulate_half_edges_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_half_edges_with_excludes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over half-edges in the circulation around the same incident face
    /// in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_half_edges_with_excludes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateHalfEdgesWithExcludesWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
            excluded_half_edges: excluded_half_edges.into_iter().collect(),
        }
        .iter(self)
    }

    /// Iterate over half-edges in the circulation around the same incident face
    /// in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_half_edges_with_excludes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateHalfEdgesWithExcludesReverseWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
            excluded_half_edges: excluded_half_edges.into_iter().collect(),
        }
        .iter(self)
    }

    /// Iterate over edges in the circulation around the same incident face in
    /// forward direction.
    #[inline]
    pub fn circulate_edges(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_edges_with_excludes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over edges in the circulation around the same incident face in
    /// backward direction.
    #[inline]
    pub fn circulate_edges_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        self.circulate_edges_with_excludes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over edges in the circulation around the same incident face in
    /// forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_edges_with_excludes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesWalker {
            circulator: self
                .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over edges in the circulation around the same incident face in
    /// backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulate_edges_with_excludes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesReverseWalker {
            circulator: self
                .circulate_half_edges_with_excludes_reverse(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over half-spokes of a circulation in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_half_spokes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculationHalfSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationHalfSpokesWalker {
            circulator: self
                .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
                .walker(),
            half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
            prev_half_edge: self.prev(initial_half_edge),
        }
        .iter(self)
    }

    /// Iterate over half-spokes of a circulation in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_half_spokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateHalfSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationHalfSpokesReverseWalker {
            circulator: self
                .circulate_half_edges_with_excludes_reverse(initial_half_edge, excluded_half_edges)
                .walker(),
            half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
            prev_half_edge: self.next(initial_half_edge),
        }
        .iter(self)
    }

    /// Iterate over spokes of a circulation in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_spokes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculationSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationSpokesWalker {
            circulator: CirculationHalfSpokesWalker {
                circulator: self
                    .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
                    .walker(),
                half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
                prev_half_edge: self.prev(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over spokes of a circulation in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_spokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculationSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationSpokesReverseWalker {
            circulator: CirculationHalfSpokesReverseWalker {
                circulator: self
                    .circulate_half_edges_with_excludes_reverse(
                        initial_half_edge,
                        excluded_half_edges,
                    )
                    .walker(),
                half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
                prev_half_edge: self.next(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over interspokes of a circulation in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_interspokes(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculationInterspokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationInterspokesWalker {
            circulator: CirculationHalfSpokesWalker {
                circulator: self
                    .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges)
                    .walker(),
                half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
                prev_half_edge: self.prev(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over interspokes of a circulation in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_interspokes_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculationInterspokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculationInterspokesReverseWalker {
            circulator: CirculationHalfSpokesReverseWalker {
                circulator: self
                    .circulate_half_edges_with_excludes_reverse(
                        initial_half_edge,
                        excluded_half_edges,
                    )
                    .walker(),
                half_spokes_walker: self.half_spokes(initial_half_edge).walker(),
                prev_half_edge: self.prev(initial_half_edge),
            },
        }
        .iter(self)
    }

    /// Iterate over half-edges of the rim of a circulation in forward
    /// direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_half_edges(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        let excluded_half_edges = excluded_half_edges.into_iter().collect::<Vec<HalfEdgeId>>();

        let mut rim_excluded_half_edges: Vec<HalfEdgeId> = self
            .circulate_edges(initial_half_edge)
            .flat_map(|edge| [edge.lesser(), edge.greater()])
            .collect();
        rim_excluded_half_edges.extend(
            self.circulation_spokes(initial_half_edge, excluded_half_edges.iter().copied())
                .flat_map(|edge| [edge.lesser(), edge.greater()]),
        );
        rim_excluded_half_edges.extend(excluded_half_edges.iter().copied());

        let Some(initial_half_spoke) = self
            .circulation_half_spokes(initial_half_edge, excluded_half_edges.iter().copied())
            .next()
        else {
            return CirculateHalfEdgesWithExcludesWalker {
                initial_half_edge,
                curr_half_edge: None,
                excluded_half_edges: rim_excluded_half_edges,
            }
            .iter(self);
        };

        let initial_half_edge = self
            .half_spokes(self.twin(initial_half_spoke))
            .find(|half_spoke| !rim_excluded_half_edges.contains(half_spoke))
            .unwrap();

        self.circulate_half_edges_with_excludes(initial_half_edge, rim_excluded_half_edges)
    }

    /// Iterate over half-edges of the rim of a circulation in backward
    /// direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_half_edges_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        let excluded_half_edges = excluded_half_edges.into_iter().collect::<Vec<HalfEdgeId>>();

        let mut rim_excluded_half_edges: Vec<HalfEdgeId> = self
            .circulate_edges_reverse(initial_half_edge)
            .flat_map(|edge| [edge.lesser(), edge.greater()])
            .collect();
        rim_excluded_half_edges.extend(
            self.circulation_spokes_reverse(initial_half_edge, excluded_half_edges.iter().copied())
                .flat_map(|edge| [edge.lesser(), edge.greater()]),
        );
        rim_excluded_half_edges.extend(excluded_half_edges.iter().copied());

        let Some(initial_half_spoke) = self
            .circulation_half_spokes_reverse(initial_half_edge, excluded_half_edges.iter().copied())
            .next()
        else {
            return CirculateHalfEdgesWithExcludesReverseWalker {
                initial_half_edge,
                curr_half_edge: None,
                excluded_half_edges: rim_excluded_half_edges,
            }
            .iter(self);
        };

        let initial_half_edge = self
            // XXX: Not sure why, but `.half_spokes_reverse` here makes the
            // tests fail.
            .half_spokes(self.twin(initial_half_spoke))
            .find(|half_spoke| !rim_excluded_half_edges.contains(half_spoke))
            .unwrap();

        self.circulate_half_edges_with_excludes_reverse(initial_half_edge, rim_excluded_half_edges)
    }

    /// Iterate over edges of the rim of a circulation in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_edges(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesWalker {
            circulator: self
                .circulation_rim_half_edges(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over edges of the rim of a circulation in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_edges_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesReverseWalker {
            circulator: self
                .circulation_rim_half_edges_reverse(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over vertices of the rim of a circulation in forward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_vertices(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesWalker {
            circulator: self
                .circulation_rim_half_edges(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }

    /// Iterate over vertices of the rim of a circulation in backward direction.
    ///
    /// Provided half-edges are excluded from traversal.
    #[inline]
    pub fn circulation_rim_vertices_reverse(
        &self,
        initial_half_edge: HalfEdgeId,
        excluded_half_edges: impl IntoIterator<Item = HalfEdgeId>,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesReverseWalker {
            circulator: self
                .circulation_rim_half_edges_reverse(initial_half_edge, excluded_half_edges)
                .walker(),
        }
        .iter(self)
    }
}

impl<
    VW,
    HEW,
    FW,
    VC: Get<usize, Value = Vertex<VW>>,
    HEC: Get<usize, Value = HalfEdge<HEW>>,
    FC: Get<usize, Value = Face<FW>>,
> Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over vertices on the boundary of a face in forward direction.
    #[inline]
    pub fn face_vertices(
        &self,
        face: FaceId,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesWalker {
            circulator: self.face_half_edges(face).walker(),
        }
        .iter(self)
    }

    /// Iterate over vertices on the boundary of a face in backward direction.
    #[inline]
    pub fn face_vertices_reverse(
        &self,
        face: FaceId,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateVertexesWithExcludesReverseWalker {
            circulator: self.face_half_edges_reverse(face).walker(),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC, HEC, FC: Get<usize, Value = Face<FW>>> Dcel<VW, HEW, FW, VC, HEC, FC> {
    /// Iterate over half-edges on the boundary of a face in forward direction.
    #[inline]
    pub fn face_half_edges(
        &self,
        face: FaceId,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateHalfEdgesWithExcludesWalker {
                // Uninitialized half-edge.
                initial_half_edge: HalfEdgeId::new(0),
                // Setting `curr_edge` to None makes the iterator produce no
                // elements.
                curr_half_edge: None,
                excluded_half_edges: vec![],
            }
            .iter(self);
        };

        CirculateHalfEdgesWithExcludesWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
            excluded_half_edges: vec![],
        }
        .iter(self)
    }

    /// Iterate over half-edges on the boundary of a face in backward direction.
    #[inline]
    pub fn face_half_edges_reverse(
        &self,
        face: FaceId,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateHalfEdgesWithExcludesReverseWalker {
                // Uninitialized half-edge.
                initial_half_edge: HalfEdgeId::new(0),
                // Setting `curr_edge` to None makes the iterator produce no
                // elements.
                curr_half_edge: None,
                excluded_half_edges: vec![],
            }
            .iter(self);
        };

        CirculateHalfEdgesWithExcludesReverseWalker {
            initial_half_edge,
            curr_half_edge: Some(initial_half_edge),
            excluded_half_edges: vec![],
        }
        .iter(self)
    }

    /// Iterate over edges on the boundary of a face in forward direction.
    #[inline]
    pub fn face_edges(
        &self,
        face: FaceId,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesWalker {
            circulator: self.face_half_edges(face).walker(),
        }
        .iter(self)
    }

    /// Iterate over edges on the boundary of a face in backward direction.
    #[inline]
    pub fn face_edges_reverse(
        &self,
        face: FaceId,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        CirculateEdgesWithExcludesReverseWalker {
            circulator: self.face_half_edges_reverse(face).walker(),
        }
        .iter(self)
    }
}

impl<VW, HEW, FW, VC, HEC: Get<usize, Value = HalfEdge<HEW>>, FC: Get<usize, Value = Face<FW>>>
    Dcel<VW, HEW, FW, VC, HEC, FC>
{
    /// Iterate over half-spokes sticking out from the boundary of a face in
    /// forward direction.
    #[inline]
    pub fn face_half_spokes(
        &self,
        face: FaceId,
    ) -> CirculationHalfSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationHalfSpokesWalker {
                circulator: CirculateHalfEdgesWithExcludesWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
                half_spokes_walker: HalfSpokesWalker {
                    initial_half_edge: HalfEdgeId::new(0),
                    curr_half_edge: None,
                },
                prev_half_edge: HalfEdgeId::new(0),
            }
            .iter(self);
        };

        self.circulation_half_spokes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over half-spokes sticking out from the boundary of a face in
    /// backward direction.
    #[inline]
    pub fn face_half_spokes_reverse(
        &self,
        face: FaceId,
    ) -> CirculateHalfSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationHalfSpokesReverseWalker {
                circulator: CirculateHalfEdgesWithExcludesReverseWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
                half_spokes_walker: HalfSpokesWalker {
                    initial_half_edge: HalfEdgeId::new(0),
                    curr_half_edge: None,
                },
                prev_half_edge: HalfEdgeId::new(0),
            }
            .iter(self);
        };

        self.circulation_half_spokes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over spokes sticking out from the boundary of a face in forward
    /// direction.
    #[inline]
    pub fn face_spokes(&self, face: FaceId) -> CirculationSpokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationSpokesWalker {
                circulator: CirculationHalfSpokesWalker {
                    circulator: CirculateHalfEdgesWithExcludesWalker {
                        // Uninitialized half-edge.
                        initial_half_edge: HalfEdgeId::new(0),
                        // Setting `curr_edge` to None makes the iterator produce no
                        // elements.
                        curr_half_edge: None,
                        excluded_half_edges: vec![],
                    },
                    half_spokes_walker: HalfSpokesWalker {
                        initial_half_edge: HalfEdgeId::new(0),
                        curr_half_edge: None,
                    },
                    prev_half_edge: HalfEdgeId::new(0),
                },
            }
            .iter(self);
        };

        self.circulation_spokes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over spokes sticking out from the boundary of a face in backward
    /// direction.
    #[inline]
    pub fn face_spokes_reverse(
        &self,
        face: FaceId,
    ) -> CirculationSpokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationSpokesReverseWalker {
                circulator: CirculationHalfSpokesReverseWalker {
                    circulator: CirculateHalfEdgesWithExcludesReverseWalker {
                        // Uninitialized half-edge.
                        initial_half_edge: HalfEdgeId::new(0),
                        // Setting `curr_edge` to None makes the iterator produce no
                        // elements.
                        curr_half_edge: None,
                        excluded_half_edges: vec![],
                    },
                    half_spokes_walker: HalfSpokesWalker {
                        initial_half_edge: HalfEdgeId::new(0),
                        curr_half_edge: None,
                    },
                    prev_half_edge: HalfEdgeId::new(0),
                },
            }
            .iter(self);
        };

        self.circulation_spokes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over interspokes touching the boundary of a face in forward
    /// direction.
    #[inline]
    pub fn face_interspokes(
        &self,
        face: FaceId,
    ) -> CirculationInterspokesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationInterspokesWalker {
                circulator: CirculationHalfSpokesWalker {
                    circulator: CirculateHalfEdgesWithExcludesWalker {
                        // Uninitialized half-edge.
                        initial_half_edge: HalfEdgeId::new(0),
                        // Setting `curr_edge` to None makes the iterator produce no
                        // elements.
                        curr_half_edge: None,
                        excluded_half_edges: vec![],
                    },
                    half_spokes_walker: HalfSpokesWalker {
                        initial_half_edge: HalfEdgeId::new(0),
                        curr_half_edge: None,
                    },
                    prev_half_edge: HalfEdgeId::new(0),
                },
            }
            .iter(self);
        };

        self.circulation_interspokes(initial_half_edge, std::iter::empty())
    }

    /// Iterate over interspokes touching the boundary of a face in backward
    /// direction.
    #[inline]
    pub fn face_interspokes_reverse(
        &self,
        face: FaceId,
    ) -> CirculationInterspokesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculationInterspokesReverseWalker {
                circulator: CirculationHalfSpokesReverseWalker {
                    circulator: CirculateHalfEdgesWithExcludesReverseWalker {
                        // Uninitialized half-edge.
                        initial_half_edge: HalfEdgeId::new(0),
                        // Setting `curr_edge` to None makes the iterator produce no
                        // elements.
                        curr_half_edge: None,
                        excluded_half_edges: vec![],
                    },
                    half_spokes_walker: HalfSpokesWalker {
                        initial_half_edge: HalfEdgeId::new(0),
                        curr_half_edge: None,
                    },
                    prev_half_edge: HalfEdgeId::new(0),
                },
            }
            .iter(self);
        };

        self.circulation_interspokes_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over half-edges of the rim of a face in forward direction.
    #[inline]
    pub fn face_rim_half_edges(
        &self,
        face: FaceId,
    ) -> CirculateHalfEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateHalfEdgesWithExcludesWalker {
                // Uninitialized half-edge.
                initial_half_edge: HalfEdgeId::new(0),
                // Setting `curr_edge` to None makes the iterator produce no
                // elements.
                curr_half_edge: None,
                excluded_half_edges: vec![],
            }
            .iter(self);
        };

        self.circulation_rim_half_edges(initial_half_edge, std::iter::empty())
    }

    /// Iterate over half-edges of the rim of a face in backward direction.
    #[inline]
    pub fn face_rim_half_edges_reverse(
        &self,
        face: FaceId,
    ) -> CirculateHalfEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateHalfEdgesWithExcludesReverseWalker {
                // Uninitialized half-edge.
                initial_half_edge: HalfEdgeId::new(0),
                // Setting `curr_edge` to None makes the iterator produce no
                // elements.
                curr_half_edge: None,
                excluded_half_edges: vec![],
            }
            .iter(self);
        };

        self.circulation_rim_half_edges_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over vertices of the rim of a face in forward direction.
    #[inline]
    pub fn face_rim_vertices(
        &self,
        face: FaceId,
    ) -> CirculateVertexesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateVertexesWithExcludesWalker {
                circulator: CirculateHalfEdgesWithExcludesWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
            }
            .iter(self);
        };

        self.circulation_rim_vertices(initial_half_edge, std::iter::empty())
    }

    /// Iterate over vertices of the rim of a face in backward direction.
    #[inline]
    pub fn face_rim_vertices_reverse(
        &self,
        face: FaceId,
    ) -> CirculateVertexesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateVertexesWithExcludesReverseWalker {
                circulator: CirculateHalfEdgesWithExcludesReverseWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
            }
            .iter(self);
        };

        self.circulation_rim_vertices_reverse(initial_half_edge, std::iter::empty())
    }

    /// Iterate over edges of the rim of a face in forward direction.
    #[inline]
    pub fn face_rim_edges(
        &self,
        face: FaceId,
    ) -> CirculateEdgesWithExcludesIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateEdgesWithExcludesWalker {
                circulator: CirculateHalfEdgesWithExcludesWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
            }
            .iter(self);
        };

        self.circulation_rim_edges(initial_half_edge, std::iter::empty())
    }

    /// Iterate over edges of the rim of a face in backward direction.
    #[inline]
    pub fn face_rim_edges_reverse(
        &self,
        face: FaceId,
    ) -> CirculateEdgesWithExcludesReverseIter<'_, VW, HEW, FW, VC, HEC, FC> {
        // Unbounded face has no half-edges. Since the unbounded face is
        // supposed to behave similarly to other faces, it is better to branch
        // out here than to have the code below panic.
        let Some(initial_half_edge) = self.face_representative(face) else {
            return CirculateEdgesWithExcludesReverseWalker {
                circulator: CirculateHalfEdgesWithExcludesReverseWalker {
                    // Uninitialized half-edge.
                    initial_half_edge: HalfEdgeId::new(0),
                    // Setting `curr_edge` to None makes the iterator produce no
                    // elements.
                    curr_half_edge: None,
                    excluded_half_edges: vec![],
                },
            }
            .iter(self);
        };

        self.circulation_rim_edges_reverse(initial_half_edge, std::iter::empty())
    }
}

#[cfg(test)]
mod test {
    use crate::{
        Dcel, assert_face_boundary, assert_face_rim, assert_face_spokes_interspokes,
        assert_vertex_rim, assert_vertex_spokes_interspokes, init_dcel_with_3x3_hex_mesh,
    };

    #[test]
    fn test_vertex_rims() {
        let dcel = init_dcel_with_3x3_hex_mesh!(Dcel<(i32, i32)>);

        assert_vertex_rim!(&dcel, 0, 0);
        assert_vertex_rim!(&dcel, 1, 0);
        assert_vertex_rim!(&dcel, 2, 0);
        assert_vertex_rim!(&dcel, 3, 0);
        assert_vertex_rim!(&dcel, 4, 0);
        assert_vertex_rim!(&dcel, 5, 12);
        assert_vertex_rim!(&dcel, 6, 0);
        assert_vertex_rim!(&dcel, 7, 0);
        assert_vertex_rim!(&dcel, 8, 12);
        assert_vertex_rim!(&dcel, 9, 12);
        assert_vertex_rim!(&dcel, 10, 0);
        assert_vertex_rim!(&dcel, 11, 0);
        assert_vertex_rim!(&dcel, 12, 12);
        assert_vertex_rim!(&dcel, 13, 0);
        assert_vertex_rim!(&dcel, 14, 0);
        assert_vertex_rim!(&dcel, 15, 12);
        assert_vertex_rim!(&dcel, 16, 12);
        assert_vertex_rim!(&dcel, 17, 12);
        assert_vertex_rim!(&dcel, 18, 12);
        assert_vertex_rim!(&dcel, 19, 0);
        assert_vertex_rim!(&dcel, 20, 0);
        assert_vertex_rim!(&dcel, 21, 0);
        assert_vertex_rim!(&dcel, 22, 0);
        assert_vertex_rim!(&dcel, 23, 0);
        assert_vertex_rim!(&dcel, 24, 0);
        assert_vertex_rim!(&dcel, 25, 0);
        assert_vertex_rim!(&dcel, 26, 0);
        assert_vertex_rim!(&dcel, 27, 0);
        assert_vertex_rim!(&dcel, 28, 0);
        assert_vertex_rim!(&dcel, 29, 0);
    }

    #[test]
    fn test_vertex_spokes_interspokes() {
        let dcel = init_dcel_with_3x3_hex_mesh!(Dcel<(i32, i32)>);

        assert_vertex_spokes_interspokes!(&dcel, 0, 3);
        assert_vertex_spokes_interspokes!(&dcel, 1, 2);
        assert_vertex_spokes_interspokes!(&dcel, 2, 2);
        assert_vertex_spokes_interspokes!(&dcel, 3, 2);
        assert_vertex_spokes_interspokes!(&dcel, 4, 3);
        assert_vertex_spokes_interspokes!(&dcel, 5, 3);
        assert_vertex_spokes_interspokes!(&dcel, 6, 3);
        assert_vertex_spokes_interspokes!(&dcel, 7, 2);
        assert_vertex_spokes_interspokes!(&dcel, 8, 3);
        assert_vertex_spokes_interspokes!(&dcel, 9, 3);
        assert_vertex_spokes_interspokes!(&dcel, 10, 2);
        assert_vertex_spokes_interspokes!(&dcel, 11, 2);
        assert_vertex_spokes_interspokes!(&dcel, 12, 3);
        assert_vertex_spokes_interspokes!(&dcel, 13, 3);
        assert_vertex_spokes_interspokes!(&dcel, 14, 3);
        assert_vertex_spokes_interspokes!(&dcel, 15, 3);
        assert_vertex_spokes_interspokes!(&dcel, 16, 3);
        assert_vertex_spokes_interspokes!(&dcel, 17, 3);
        assert_vertex_spokes_interspokes!(&dcel, 18, 3);
        assert_vertex_spokes_interspokes!(&dcel, 19, 2);
        assert_vertex_spokes_interspokes!(&dcel, 20, 3);
        assert_vertex_spokes_interspokes!(&dcel, 21, 2);
        assert_vertex_spokes_interspokes!(&dcel, 22, 2);
        assert_vertex_spokes_interspokes!(&dcel, 23, 2);
        assert_vertex_spokes_interspokes!(&dcel, 24, 2);
        assert_vertex_spokes_interspokes!(&dcel, 25, 3);
        assert_vertex_spokes_interspokes!(&dcel, 26, 2);
        assert_vertex_spokes_interspokes!(&dcel, 27, 3);
        assert_vertex_spokes_interspokes!(&dcel, 28, 2);
        assert_vertex_spokes_interspokes!(&dcel, 29, 2);
    }

    #[test]
    fn test_face_boundaries() {
        let dcel = init_dcel_with_3x3_hex_mesh!(Dcel<(i32, i32)>);

        assert_face_boundary!(&dcel, 0, 0);
        assert_face_boundary!(&dcel, 1, 6);
        assert_face_boundary!(&dcel, 2, 6);
        assert_face_boundary!(&dcel, 3, 6);
        assert_face_boundary!(&dcel, 4, 6);
        assert_face_boundary!(&dcel, 5, 6);
        assert_face_boundary!(&dcel, 6, 6);
        assert_face_boundary!(&dcel, 7, 6);
        assert_face_boundary!(&dcel, 8, 6);
        assert_face_boundary!(&dcel, 9, 6);
    }

    #[test]
    fn test_face_spokes_interspokes() {
        let dcel = init_dcel_with_3x3_hex_mesh!(Dcel<(i32, i32)>);

        assert_face_spokes_interspokes!(&dcel, 0, 0);
        assert_face_spokes_interspokes!(&dcel, 1, 3);
        assert_face_spokes_interspokes!(&dcel, 2, 5);
        assert_face_spokes_interspokes!(&dcel, 3, 4);
        assert_face_spokes_interspokes!(&dcel, 4, 6);
        assert_face_spokes_interspokes!(&dcel, 5, 6);
        assert_face_spokes_interspokes!(&dcel, 6, 4);
        assert_face_spokes_interspokes!(&dcel, 7, 3);
        assert_face_spokes_interspokes!(&dcel, 8, 5);
        assert_face_spokes_interspokes!(&dcel, 9, 4);
    }

    #[test]
    fn test_face_rims() {
        let dcel = init_dcel_with_3x3_hex_mesh!(Dcel<(i32, i32)>);

        assert_face_rim!(&dcel, 0, 0);

        // Boundary faces have no rims, so the number of elements is undefined,
        // at least for now.
        /*assert_face_rim!(&dcel, 1, 22);
        assert_face_rim!(&dcel, 2, 30);
        assert_face_rim!(&dcel, 3, 26);
        assert_face_rim!(&dcel, 4, 34);*/

        // Only the central face has a rim.
        assert_face_rim!(&dcel, 5, 18);

        // Boundary faces have no rims, so the number of elements is undefined,
        // at least for now.
        /*assert_face_rim!(&dcel, 6, 26);
        assert_face_rim!(&dcel, 7, 22);
        assert_face_rim!(&dcel, 8, 30);
        assert_face_rim!(&dcel, 9, 26);*/
    }
}
