class Renderer():
    __roots: List[NodePath]
    __draw_nps: List[NodePath]
    __circles: List[Tuple[int, float, Geom]]
    __thicknesses: List[float]
    __line_segs: LineSegs

    def __init__(self, root: NodePath) -> None:
        self.__roots = [root]
        self.__draw_nps = []
        self.__circles = []
        self.__thicknesses = [2.5]
        self.__line_segs = LineSegs()
        self.__line_segs.set_thickness(2.5)

    def push_root(self, np: NodePath) -> None:
        assert isinstance(np, NodePath)
        self.render()
        self.__roots.append(np)

    def pop_root(self) -> NodePath:
        self.render()
        return self.__roots.pop()

    def push_line_thickness(self, thickness: float) -> None:
        self.__render_lines()
        self.__thicknesses.append(thickness)
        self.__line_segs.set_thickness(thickness)

    def pop_line_thickness(self) -> float:
        self.__render_lines()
        self.__line_segs.set_thickness(self.__thicknesses[-2])
        return self.__thicknesses.pop()

    @property
    def line_segs(self) -> LineSegs:
        return self.__line_segs

    @property
    def root(self) -> NodePath:
        return self.__roots[-1]

    def render(self) -> None:
        self.__render_lines()

    def __render_lines(self) -> None:
        if not self.__line_segs.is_empty():
            n = self.__line_segs.create()
            self.root.attach_new_node(n)

    def draw_line(self, colour: Colour, p1: Point, p2: Point) -> None:
        ls = self.__line_segs
        ls.set_color(*colour)
        ls.move_to(*p1)
        ls.draw_to(*p2)

    def draw_sphere(self, p: Point, colour: Colour = WHITE, scale: float = 0.2) -> None:
        np = loader.load_model("models/misc/sphere.egg")
        np.set_color(*colour)
        np.reparent_to(self.root)
        np.set_pos(*p)
        np.set_scale(scale)

    def draw_arc(self, p: Point, angle_rads: float = 2 * math.pi, nsteps: int = 16, radius: float = 0.06, colour: Colour = WHITE) -> None:
        ls = self.__line_segs
        ls.set_color(*colour)

        x, y, z = p

        ls.move_to(x + radius, y, z)
        for i in range(nsteps + 1):
            a = angle_rads * i / nsteps
            ty = math.sin(a) * radius + y
            tx = math.cos(a) * radius + x
            ls.draw_to(tx, ty, z)

    def draw_circle(self, p: Point, *args, **kwargs) -> None:
        self.draw_arc(p, 2 * math.pi, *args, **kwargs)

    def draw_circle_filled(self, p: Point, nsteps=16, radius: float = 0.06, colour: Colour = WHITE) -> None:
        gn = GeomNode("circle")

        exists: bool = False
        for cn, cr, cg in self.__circles:
            if cn == nsteps and cr == radius:
                exists = True
                gn.add_geom(cg)
                break
        if not exists:
            self.__circles.append((nsteps, radius, BuildGeometry.addCircle(gn, nsteps, radius, colour)))

        np = self.root.attach_new_node(gn)

        np.set_pos(*p)
        np.set_color(*colour)