from big_ol_pile_of_manim_imports import *

from old_projects.lost_lecture import Orbiting
from old_projects.lost_lecture import ShowWord


class LogoGeneration(LogoGenerationTemplate):
    CONFIG = {
        "random_seed": 2,
    }

    def get_logo_animations(self, logo):
        layers = logo.spike_layers
        for layer in layers:
            random.shuffle(layer.submobjects)
            for spike in layer:
                spike.save_state()
                spike.scale(0.5)
                spike.apply_complex_function(np.log)
                spike.rotate(-90 * DEGREES, about_point=ORIGIN)
                spike.set_fill(opacity=0)

        return [
            FadeIn(
                logo.iris_background,
                rate_func=squish_rate_func(smooth, 0.25, 1),
                run_time=3,
            ),
            AnimationGroup(*[
                LaggedStartMap(
                    Restore, layer,
                    run_time=3,
                    path_arc=180 * DEGREES,
                    rate_func=squish_rate_func(smooth, a / 3.0, (a + 0.9) / 3.0),
                    lag_ratio=0.8,
                )
                for layer, a in zip(layers, [0, 2, 1, 0])
            ]),
            Animation(logo.pupil),
        ]


class ThinkingAboutAProof(PiCreatureScene):
    def construct(self):
        randy = self.pi_creature
        randy.scale(0.5, about_edge=DL)
        bubble = ThoughtBubble()
        bubble.pin_to(randy)
        bubble.shift(MED_SMALL_BUFF * RIGHT)
        cloud = bubble[-1]
        cloud.rotate(90 * DEGREES)
        cloud.set_height(FRAME_HEIGHT - 0.5)
        cloud.stretch(2.8, 0)
        cloud.next_to(bubble[2], RIGHT)
        cloud.to_edge(UP, buff=0.25)
        bubble[1].shift(0.25 * UL)

        you_arrow = Vector(LEFT, color=WHITE)
        you_arrow.next_to(randy, RIGHT)
        you = TextMobject("You")
        you.next_to(you_arrow, RIGHT)
        lm_arrow = Vector(DOWN, color=WHITE)
        lm_arrow.next_to(randy, UP)
        love_math = TextMobject("Love math")
        love_math.next_to(lm_arrow, UP)
        love_math.shift_onto_screen()

        self.add(bubble)
        self.play(
            FadeInFrom(you, LEFT),
            GrowArrow(you_arrow),
        )
        self.play(
            FadeInFromDown(love_math),
            GrowArrow(lm_arrow),
            randy.change, "erm"
        )
        self.wait(2)
        self.play(
            randy.change, "pondering", cloud
        )
        self.wait(10)


class SumOfIntegersProof(Scene):
    CONFIG = {
        "n": 6,
    }

    def construct(self):
        equation = TexMobject(
            "1", "+", "2", "+", "3", "+",
            "\\cdots", "+", "n",
            "=", "\\frac{n(n+1)}{2}"
        )
        equation.scale(1.5)
        equation.to_edge(UP)
        one, two, three, dots, n = numbers = VGroup(*[
            equation.get_part_by_tex(tex, substring=False).copy()
            for tex in ("1", "2", "3", "\\cdots", "n",)
        ])
        for number in numbers:
            number.generate_target()
            number.target.scale(0.75)

        rows = self.get_rows()
        rows.next_to(equation, DOWN, buff=MED_LARGE_BUFF)
        flipped_rows = self.get_flipped_rows(rows)

        for row, num in zip(rows, [one, two, three]):
            num.target.next_to(row, LEFT)
        dots.target.rotate(90 * DEGREES)
        dots.target.next_to(rows[3:-1], LEFT)
        dots.target.align_to(one.target, LEFT)
        n.target.next_to(rows[-1], LEFT)

        for row in rows:
            row.save_state()
            for square in row:
                square.stretch(0, 0)
                square.move_to(row, LEFT)
            row.fade(1)

        self.play(LaggedStartMap(FadeInFromDown, equation[:-1]))
        self.wait()
        self.play(
            LaggedStartMap(
                MoveToTarget, numbers,
                path_arc=-90 * DEGREES,
                lag_ratio=1,
                run_time=1
            )
        )
        self.play(LaggedStartMap(Restore, rows))
        self.wait()
        self.play(
            ReplacementTransform(
                rows.copy().set_fill(opacity=0), flipped_rows,
                path_arc=-PI,
                run_time=2
            )
        )
        self.wait()
        self.play(Write(equation[-1]))
        self.wait(5)

    def get_rows(self):
        rows = VGroup()
        for count in range(1, self.n + 1):
            row = VGroup(*[Square() for k in range(count)])
            row.arrange(RIGHT, buff=0)
            rows.add(row)
        rows.arrange(DOWN, buff=0, aligned_edge=LEFT)
        rows.set_height(5)
        rows.set_stroke(WHITE, 3)
        rows.set_fill(BLUE, 0.5)
        return rows

    def get_flipped_rows(self, rows):
        result = rows.copy()
        result.rotate(PI)
        result.set_fill(RED_D, 0.5)
        result.move_to(rows, LEFT)
        result.shift(rows[0][0].get_width() * RIGHT)
        return result


class FeynmansLostLectureWrapper(Scene):
    def construct(self):
        title = TextMobject("Feynman's Lost Lecture")
        title.scale(1.5)
        title.to_edge(UP)
        rect = ScreenRectangle(height=6)
        rect.next_to(title, DOWN)

        self.add(title)
        self.play(ShowCreation(rect))
        self.wait()


class HoldUpProof(TeacherStudentsScene):
    def construct(self):
        title = TextMobject("One of my all-time favorite proofs")
        title.to_edge(UP)
        self.add(title)

        self.play(
            self.teacher.change, "raise_right_hand", self.screen,
            self.get_student_changes(
                "pondering", "confused", "maybe",
                look_at_arg=title
            )
        )
        self.look_at(title)
        self.wait(5)
        self.change_student_modes(
            "happy", "thinking", "hooray",
            look_at_arg=title
        )
        self.wait(5)


class MultipleDefinitionsOfAnEllipse(Scene):
    def construct(self):
        title = Title("Multiple definitions of ``ellipse''")
        self.add(title)

        definitions = VGroup(
            TextMobject("1. Stretch a circle"),
            TextMobject("2. Thumbtack \\\\ \\quad\\, construction"),
            TextMobject("3. Slice a cone"),
        )
        definitions.arrange(
            DOWN, buff=LARGE_BUFF,
            aligned_edge=LEFT
        )
        definitions.next_to(title, DOWN, LARGE_BUFF)
        definitions.to_edge(LEFT)

        for definition in definitions:
            definition.saved_state = definition.copy()
            definition.saved_state.set_fill(LIGHT_GREY, 0.5)

        self.play(LaggedStartMap(
            FadeInFrom, definitions,
            lambda m: (m, RIGHT),
            run_time=4
        ))
        self.wait()
        for definition in definitions:
            others = [d for d in definitions if d is not definition]
            self.play(
                definition.set_fill, WHITE, 1,
                definition.scale, 1.2, {"about_edge": LEFT},
                *list(map(Restore, others))
            )
            self.wait(2)


class StretchACircle(Scene):
    def construct(self):
        plane = NumberPlane(
            x_unit_size=2,
            y_unit_size=2
        )

        circle = Circle(radius=2)
        circle.set_stroke(YELLOW, 5)
        circle_ghost = circle.copy()
        circle_ghost.set_stroke(width=1)

        plane_circle_group = VGroup(plane, circle)
        plane_circle_group.save_state()

        arrows = self.get_arrows()

        prop = 1.0 / 8
        start_point = Dot(circle.point_from_proportion(prop))
        end_point = start_point.copy().stretch(2, 0, about_point=ORIGIN)
        end_point.stretch(0.5, 0)
        end_point.set_color(RED)

        xy = TexMobject("(x, y)")
        cxy = TexMobject("(c \\cdot x, y)")
        cxy[1].set_color(RED)
        for tex in xy, cxy:
            tex.scale(1.5)
            tex.add_background_rectangle()

        xy_arrow = Vector(DOWN, color=WHITE)
        cxy_arrow = Vector(DL, color=WHITE)
        xy_arrow.next_to(start_point, UP, SMALL_BUFF)
        xy.next_to(xy_arrow, UP, SMALL_BUFF)
        cxy_arrow.next_to(end_point, UR, SMALL_BUFF)
        cxy.next_to(cxy_arrow, UR, SMALL_BUFF)

        self.add(plane_circle_group)
        self.wait()
        self.play(
            ApplyMethod(
                plane_circle_group.stretch, 2, 0,
                run_time=2,
            ),
            LaggedStartMap(
                GrowArrow, arrows,
                run_time=1,
                lag_ratio=1
            ),
        )
        self.play(FadeOut(arrows))
        self.wait()
        self.play(Restore(plane_circle_group))
        self.play(
            GrowArrow(xy_arrow),
            Write(xy),
            FadeInFrom(start_point, UP),
        )
        self.wait()
        self.add(circle_ghost)
        self.play(
            circle.stretch, 2, 0,
            ReplacementTransform(start_point.copy(), end_point),
            run_time=2
        )
        self.play(
            GrowArrow(cxy_arrow),
            Write(cxy)
        )
        self.wait(2)

    def get_arrows(self):
        result = VGroup()
        for vect in [LEFT, RIGHT]:
            for y in range(-3, 4):
                arrow = Vector(vect)
                arrow.move_to(2 * vect + y * UP)
                result.add(arrow)
        result.set_color(RED)
        return result


class ShowArrayOfEccentricities(Scene):
    def construct(self):
        eccentricities = np.linspace(0, 0.99, 6)
        eccentricity_labels = VGroup(*list(map(
            DecimalNumber, eccentricities
        )))
        ellipses = self.get_ellipse_row(eccentricities)
        ellipses.set_color_by_gradient(BLUE, YELLOW)
        ellipses.move_to(DOWN)

        for label, ellipse in zip(eccentricity_labels, ellipses):
            label.next_to(ellipse, UP)

        name = TextMobject("Eccentricity")
        name.scale(1.5)
        name.to_edge(UP)
        alt_name = TextMobject("(read ``squishification'')")
        alt_name.set_color(YELLOW)
        alt_name.next_to(name, RIGHT)
        alt_name.shift_onto_screen()
        name.generate_target()
        name.target.next_to(alt_name, LEFT)

        arrows = VGroup(*[
            Arrow(name.get_bottom(), label.get_top())
            for label in eccentricity_labels
        ])
        arrows.set_color_by_gradient(BLUE, YELLOW)

        for label, arrow in zip(eccentricity_labels, arrows):
            label.save_state()
            label.fade(1)
            label.scale(0.1)
            label.move_to(arrow.get_start())

        morty = Mortimer(height=2)
        morty.next_to(alt_name, DOWN)

        self.add(ellipses[0])
        for e1, e2 in zip(ellipses[:-1], ellipses[1:]):
            self.play(ReplacementTransform(
                e1.copy(), e2,
                path_arc=10 * DEGREES
            ))
        self.wait()

        self.play(
            Write(name),
            LaggedStartMap(GrowArrow, arrows),
            LaggedStartMap(Restore, eccentricity_labels)
        )
        self.wait()
        self.play(
            Write(alt_name),
            MoveToTarget(name),
            morty.change, "hooray", alt_name,
            VFadeIn(morty),
        )
        self.play(Blink(morty))
        self.play(morty.change, "thinking", ellipses)
        self.wait()
        self.play(Blink(morty))

        for i in 0, -1:
            e_copy = ellipses[i].copy()
            e_copy.set_color(RED)
            self.play(ShowCreation(e_copy))
            self.play(
                ShowCreationThenFadeAround(
                    eccentricity_labels[i],
                ),
                FadeOut(e_copy)
            )
        self.wait()

        circle = ellipses[0]
        group = VGroup(*it.chain(
            eccentricity_labels,
            ellipses[1:],
            arrows,
            name,
            alt_name,
            [morty]
        ))
        self.play(
            LaggedStartMap(FadeOutAndShiftDown, group),
            circle.set_height, 5,
            circle.center,
        )

    def get_ellipse(self, eccentricity, width=2):
        result = Circle(color=WHITE)
        result.set_width(width)
        a = width / 2.0
        c = eccentricity * a
        b = np.sqrt(a**2 - c**2)
        result.stretch(b / a, 1)
        result.shift(c * LEFT)

        result.eccentricity = eccentricity
        return result

    def get_ellipse_row(self, eccentricities, buff=MED_SMALL_BUFF, **kwargs):
        result = VGroup(*[
            self.get_ellipse(e, **kwargs)
            for e in eccentricities
        ])
        result.arrange(RIGHT, buff=buff)
        return result

    def get_eccentricity(self, ellipse):
        """
        Assumes that it's major/minor axes line up
        with x and y axes
        """
        a = ellipse.get_width()
        b = ellipse.get_height()
        if b > a:
            a, b = b, a
        c = np.sqrt(a**2 - b**2)
        return fdiv(c, a)


class ShowOrbits(ShowArrayOfEccentricities):
    CONFIG = {"camera_config": {"background_opacity": 1}}

    def construct(self):
        earth_eccentricity = 0.0167
        comet_eccentricity = 0.9671
        earth_orbit = self.get_ellipse(eccentricity=earth_eccentricity)
        comet_orbit = self.get_ellipse(eccentricity=comet_eccentricity)
        earth_orbit.set_height(5)
        comet_orbit.set_width(
            0.7 * FRAME_WIDTH,
            about_point=ORIGIN,
        )

        sun = ImageMobject("Sun")
        earth = ImageMobject("Earth")
        comet = ImageMobject("Comet")
        sun.set_height(1)
        earth.set_height(0.5)
        comet.set_height(0.1)

        earth_parts = VGroup(sun, earth_orbit, earth)

        eccentricity_label = DecimalNumber(
            earth_eccentricity,
            num_decimal_places=4
        )
        eccentricity_equals = TextMobject(
            "Eccentricity = "
        )
        earth_orbit_words = TextMobject("Earth's orbit")
        earth_orbit_words.set_color(BLUE)
        full_label = VGroup(
            earth_orbit_words,
            eccentricity_equals, eccentricity_label
        )
        full_label.arrange(RIGHT, SMALL_BUFF)
        earth_orbit_words.shift(0.5 * SMALL_BUFF * UL)
        full_label.to_edge(UP)

        comet_orbit_words = TextMobject("Halley's comet orbit")
        comet_orbit_words.set_color(LIGHT_GREY)
        comet_orbit_words.move_to(earth_orbit_words, RIGHT)

        orbiting_earth = Orbiting(earth, sun, earth_orbit)
        orbiting_comet = Orbiting(comet, sun, comet_orbit)

        self.add(full_label, earth_orbit_words)
        self.add(sun, earth_orbit, orbiting_earth)
        self.wait(10)
        orbiting_earth.rate = 1.5
        orbiting_comet.rate = 1.5
        self.play(
            earth_parts.set_height,
            comet_orbit.get_height() / 4.53,
            earth_parts.shift, 3 * RIGHT
        )
        comet_orbit.shift(3 * RIGHT)
        comet_orbit.save_state()
        Transform(comet_orbit, earth_orbit).update(1)
        self.play(
            Restore(comet_orbit, run_time=2),
            ChangingDecimal(
                eccentricity_label,
                lambda a: self.get_eccentricity(comet_orbit)
            ),
            FadeOutAndShift(earth_orbit_words, UP),
            FadeInFromDown(comet_orbit_words)
        )
        self.add(orbiting_comet)
        self.wait(10)


class EccentricityInThumbtackCase(ShowArrayOfEccentricities):
    def construct(self):
        ellipse = self.get_ellipse(0.2, width=5)
        ellipse_target = self.get_ellipse(0.9, width=5)
        ellipse_target.scale(fdiv(
            sum(self.get_abc(ellipse)),
            sum(self.get_abc(ellipse_target)),
        ))
        for mob in ellipse, ellipse_target:
            mob.center()
            mob.set_color(BLUE)
        thumbtack_update = self.get_thumbtack_update(ellipse)
        ellipse_point_update = self.get_ellipse_point_update(ellipse)
        focal_lines_update = self.get_focal_lines_update(
            ellipse, ellipse_point_update.mobject
        )
        focus_to_focus_line_update = self.get_focus_to_focus_line_update(ellipse)
        eccentricity_label = self.get_eccentricity_label()
        eccentricity_value_update = self.get_eccentricity_value_update(
            eccentricity_label, ellipse,
        )
        inner_brace_update = self.get_focus_line_to_focus_line_brace_update(
            focus_to_focus_line_update.mobject
        )
        outer_lines = self.get_outer_dashed_lines(ellipse)
        outer_lines_brace = Brace(outer_lines, DOWN)

        focus_distance = TextMobject("Focus distance")
        focus_distance.set_color(GREEN)
        focus_distance.next_to(inner_brace_update.mobject, DOWN, SMALL_BUFF)
        focus_distance.add_to_back(focus_distance.copy().set_stroke(BLACK, 5))
        focus_distance_update = Mobject.add_updater(
            focus_distance,
            lambda m: m.set_width(
                inner_brace_update.mobject.get_width(),
            ).next_to(inner_brace_update.mobject, DOWN, SMALL_BUFF)
        )
        diameter = TextMobject("Diameter")
        diameter.set_color(RED)
        diameter.next_to(outer_lines_brace, DOWN, SMALL_BUFF)

        fraction = TexMobject(
            "{\\text{Focus distance}", "\\over",
            "\\text{Diameter}}"
        )
        numerator = fraction.get_part_by_tex("Focus")
        numerator.set_color(GREEN)
        fraction.set_color_by_tex("Diameter", RED)
        fraction.move_to(2 * UP)
        fraction.to_edge(RIGHT, buff=MED_LARGE_BUFF)
        numerator_update = Mobject.add_updater(
            numerator,
            lambda m: m.set_width(focus_distance.get_width()).next_to(
                fraction[1], UP, MED_SMALL_BUFF
            )
        )

        fraction_arrow = Arrow(
            eccentricity_label.get_right(),
            fraction.get_top() + MED_SMALL_BUFF * UP,
            path_arc=-60 * DEGREES,
        )
        fraction_arrow.pointwise_become_partial(fraction_arrow, 0, 0.95)

        ellipse_transformation = Transform(
            ellipse, ellipse_target,
            rate_func=there_and_back,
            run_time=8,
        )

        self.add(ellipse)
        self.add(thumbtack_update)
        self.add(ellipse_point_update)
        self.add(focal_lines_update)
        self.add(focus_to_focus_line_update)
        self.add(eccentricity_label)
        self.add(eccentricity_value_update)

        self.play(ellipse_transformation)

        self.add(inner_brace_update)
        self.add(outer_lines)
        self.add(outer_lines_brace)

        self.add(fraction)
        self.add(fraction_arrow)
        self.add(focus_distance)
        self.add(diameter)

        self.add(focus_distance_update)
        self.add(numerator_update)

        self.play(
            ellipse_transformation,
            VFadeIn(inner_brace_update.mobject),
            VFadeIn(outer_lines),
            VFadeIn(outer_lines_brace),
            VFadeIn(fraction),
            VFadeIn(fraction_arrow),
            VFadeIn(focus_distance),
            VFadeIn(diameter),
        )

    def get_thumbtack_update(self, ellipse):
        thumbtacks = VGroup(*[
            self.get_thumbtack()
            for x in range(2)
        ])

        def update_thumbtacks(thumbtacks):
            foci = self.get_foci(ellipse)
            for thumbtack, focus in zip(thumbtacks, foci):
                thumbtack.move_to(focus, DR)
            return thumbtacks

        return Mobject.add_updater(thumbtacks, update_thumbtacks)

    def get_ellipse_point_update(self, ellipse):
        dot = Dot(color=RED)
        return cycle_animation(MoveAlongPath(
            dot, ellipse,
            run_time=5,
            rate_func=linear
        ))

    def get_focal_lines_update(self, ellipse, ellipse_point):
        lines = VGroup(*[Line(LEFT, RIGHT) for x in range(2)])
        lines.set_color_by_gradient(YELLOW, PINK)

        def update_lines(lines):
            foci = self.get_foci(ellipse)
            Q = ellipse_point.get_center()
            for line, focus in zip(lines, foci):
                line.put_start_and_end_on(focus, Q)
            return lines

        return Mobject.add_updater(lines, update_lines)

    def get_focus_to_focus_line_update(self, ellipse):
        return Mobject.add_updater(
            Line(LEFT, RIGHT, color=WHITE),
            lambda m: m.put_start_and_end_on(*self.get_foci(ellipse))
        )

    def get_focus_line_to_focus_line_brace_update(self, line):
        brace = Brace(Line(LEFT, RIGHT))
        brace.add_to_back(brace.copy().set_stroke(BLACK, 5))
        return Mobject.add_updater(
            brace,
            lambda b: b.match_width(line, stretch=True).next_to(
                line, DOWN, buff=SMALL_BUFF
            )
        )

    def get_eccentricity_label(self):
        words = TextMobject("Eccentricity = ")
        decimal = DecimalNumber(0, num_decimal_places=2)
        group = VGroup(words, decimal)
        group.arrange(RIGHT)
        group.to_edge(UP)
        return group

    def get_eccentricity_value_update(self, eccentricity_label, ellipse):
        decimal = eccentricity_label[1]
        decimal.add_updater(lambda d: d.set_value(
            self.get_eccentricity(ellipse)
        ))
        return decimal

    def get_outer_dashed_lines(self, ellipse):
        line = DashedLine(2.5 * UP, 2.5 * DOWN)
        return VGroup(
            line.move_to(ellipse, RIGHT),
            line.copy().move_to(ellipse, LEFT),
        )

    #
    def get_abc(self, ellipse):
        a = ellipse.get_width() / 2
        b = ellipse.get_height() / 2
        c = np.sqrt(a**2 - b**2)
        return a, b, c

    def get_foci(self, ellipse):
        a, b, c = self.get_abc(ellipse)
        return [
            ellipse.get_center() + c * RIGHT,
            ellipse.get_center() + c * LEFT,
        ]

    def get_thumbtack(self):
        angle = 10 * DEGREES
        result = SVGMobject(file_name="push_pin")
        result.set_height(0.5)
        result.set_fill(LIGHT_GREY)
        result.rotate(angle)
        return result


class EccentricityForSlicedConed(Scene):
    def construct(self):
        equation = TexMobject(
            "\\text{Eccentricity} = ",
            "{\\sin(", "\\text{angle of plane}", ")", "\\over",
            "\\sin(", "\\text{angle of cone slant}", ")}"
        )
        equation.set_color_by_tex("plane", YELLOW)
        equation.set_color_by_tex("cone", BLUE)
        equation.to_edge(LEFT)
        self.play(FadeInFromDown(equation))


class AskWhyAreTheyTheSame(TeacherStudentsScene):
    def construct(self):
        morty = self.teacher
        self.student_says(
            "Why on earth \\\\ are these the same?",
            student_index=2,
            target_mode="sassy",
            bubble_kwargs={"direction": LEFT}
        )
        bubble = self.students[2].bubble
        self.play(
            morty.change, "awe",
            self.get_student_changes("confused", "confused", "sassy")
        )
        self.look_at(self.screen)
        self.wait(3)
        self.play(morty.change, "thinking", self.screen)
        self.change_student_modes("maybe", "erm", "confused")
        self.look_at(self.screen)
        self.wait(3)

        baby_morty = BabyPiCreature()
        baby_morty.match_style(morty)
        baby_morty.to_corner(DL)

        self.play(
            FadeOutAndShift(bubble),
            FadeOutAndShift(bubble.content),
            LaggedStartMap(
                FadeOutAndShift, self.students,
                lambda m: (m, 3 * DOWN),
            ),
            ReplacementTransform(
                morty, baby_morty,
                path_arc=30 * DEGREES,
                run_time=2,
            )
        )
        self.pi_creatures = VGroup(baby_morty)
        bubble = ThoughtBubble(height=6, width=7)
        bubble.set_fill(DARK_GREY, 0.5)
        bubble.pin_to(baby_morty)

        egg = Circle(radius=0.4)
        egg.stretch(0.75, 1)
        egg.move_to(RIGHT)
        egg.apply_function(
            lambda p: np.array([
                p[0], p[0] * p[1], p[2]
            ])
        )
        egg.flip()
        egg.set_width(3)
        egg.set_stroke(RED, 5)
        egg.move_to(bubble.get_bubble_center())

        self.play(baby_morty.change, "confused", 2 * DOWN)
        self.wait(2)
        self.play(
            baby_morty.change, "thinking",
            LaggedStartMap(DrawBorderThenFill, bubble)
        )
        self.play(ShowCreation(egg))
        self.wait(3)

        bubble_group = VGroup(bubble, egg)
        self.play(
            ApplyMethod(
                bubble_group.shift, FRAME_WIDTH * LEFT,
                rate_func=running_start,
            ),
            baby_morty.change, "awe"
        )
        self.play(Blink(baby_morty))
        self.wait()


class TriangleOfEquivalences(Scene):
    def construct(self):
        title = Title("How do you prove this\\textinterrobang.")
        self.add(title)
        rects = VGroup(*[ScreenRectangle() for x in range(3)])
        rects.set_height(2)
        rects[:2].arrange(RIGHT, buff=2)
        rects[2].next_to(rects[:2], DOWN, buff=1.5)
        rects.next_to(title, DOWN)

        arrows = VGroup(*[
            TexMobject("\\Leftrightarrow")
            for x in range(3)
        ])
        arrows.scale(2)
        arrows[0].move_to(rects[:2])
        arrows[1].rotate(60 * DEGREES)
        arrows[1].move_to(rects[1:])
        arrows[2].rotate(-60 * DEGREES)
        arrows[2].move_to(rects[::2])
        arrows[1:].shift(0.5 * DOWN)

        self.play(LaggedStartMap(
            DrawBorderThenFill, arrows,
            lag_ratio=0.7,
            run_time=3,
        ))
        self.wait()
        self.play(FadeOutAndShift(arrows[1:]))
        self.wait()


class SliceCone(ExternallyAnimatedScene):
    pass


class TiltPlane(ExternallyAnimatedScene):
    pass


class IntroduceConeEllipseFocalSum(ExternallyAnimatedScene):
    pass


class ShowMeasurementBook(TeacherStudentsScene):
    CONFIG = {"camera_config": {"background_opacity": 1}}

    def construct(self):
        measurement = ImageMobject("MeasurementCover")
        measurement.set_height(3.5)
        measurement.move_to(self.hold_up_spot, DOWN)

        words = TextMobject("Highly recommended")
        arrow = Vector(RIGHT, color=WHITE)
        arrow.next_to(measurement, LEFT)
        words.next_to(arrow, LEFT)

        self.play(
            self.teacher.change, "raise_right_hand",
            FadeInFromDown(measurement)
        )
        self.change_all_student_modes("hooray")
        self.wait()
        self.play(
            GrowArrow(arrow),
            FadeInFrom(words, RIGHT),
            self.get_student_changes(
                "thinking", "happy", "pondering",
                look_at_arg=arrow
            )
        )
        self.wait(3)


class IntroduceSpheres(ExternallyAnimatedScene):
    pass


class TangencyAnimation(Scene):
    def construct(self):
        rings = VGroup(*[
            Circle(color=YELLOW, stroke_width=3, radius=0.5)
            for x in range(5)
        ])
        for ring in rings:
            ring.save_state()
            ring.scale(0)
            ring.saved_state.set_stroke(width=0)

        self.play(LaggedStartMap(
            Restore, rings,
            run_time=2,
            lag_ratio=0.7
        ))


class TwoSpheresRotating(ExternallyAnimatedScene):
    pass


class TiltSlopeWithOnlySpheres(ExternallyAnimatedScene):
    pass


class TiltSlopeWithOnlySpheresSideView(ExternallyAnimatedScene):
    pass


class AskAboutWhyYouWouldAddSpheres(PiCreatureScene):
    def construct(self):
        randy = self.pi_creature
        randy.flip()
        randy.set_height(2)
        randy.set_color(BLUE_C)
        randy.to_edge(RIGHT)
        randy.shift(2 * UP)
        randy.look(UP)

        graph_spot = VectorizedPoint()

        why = TextMobject("...why?")
        why.next_to(randy, UP)

        bubble = ThoughtBubble(height=2, width=2)
        bubble.pin_to(randy)

        self.play(FadeInFromDown(randy))
        self.play(
            Animation(graph_spot),
            randy.change, "maybe",
            Write(why),
        )
        self.wait(3)
        self.play(randy.change, "pondering")
        self.play(
            why.to_corner, DR,
            why.set_fill, LIGHT_GREY, 0.5,
        )
        self.wait()
        self.play(
            ShowCreation(bubble),
            randy.change, "thinking"
        )
        self.wait()
        self.look_at(graph_spot)
        self.wait(2)


class ShowTangencyPoints(ExternallyAnimatedScene):
    pass


class ShowFocalLinesAsTangent(ExternallyAnimatedScene):
    pass


class UseDefiningFeatures(Scene):
    def construct(self):
        title = TextMobject("Problem-solving tip:")
        title.scale(1.5)
        title.to_edge(UP)
        tip = TextMobject(
            """
            - Make sure you're using all the \\\\
            \\phantom{-} defining features of the objects \\\\
            \\phantom{-} involved.
            """,
            alignment=""
        )
        tip.next_to(title, DOWN, MED_LARGE_BUFF)
        tip.shift(MED_SMALL_BUFF * RIGHT)
        tip.set_color(YELLOW)

        self.add(title)
        self.play(Write(tip, run_time=4))
        self.wait()


class RemindAboutTangencyToCone(ExternallyAnimatedScene):
    pass


class ShowCircleToCircleLine(ExternallyAnimatedScene):
    pass


class ShowSegmentSplit(Scene):
    CONFIG = {
        "include_image": True,
    }

    def construct(self):
        if self.include_image:
            image = ImageMobject("ShowCircleToCircleLine")
            image.set_height(FRAME_HEIGHT)
            self.add(image)

        brace1 = Brace(Line(ORIGIN, 1.05 * UP), LEFT)
        brace2 = Brace(Line(1.7 * DOWN, ORIGIN), LEFT)
        braces = VGroup(brace1, brace2)
        braces.rotate(-14 * DEGREES)
        braces.move_to(0.85 * UP + 1.7 * LEFT)

        words = VGroup(
            TextMobject("Top segment"),
            TextMobject("Bottom segment")
        )
        for word, brace in zip(words, braces):
            word.next_to(
                brace.get_center(), LEFT,
                buff=0.35
            )
        words[0].set_color(PINK)
        words[1].set_color(GOLD)

        for mob in it.chain(braces, words):
            mob.add_to_back(mob.copy().set_stroke(BLACK, 5))

        for brace in braces:
            brace.save_state()
            brace.set_stroke(width=0)
            brace.scale(0)

        self.play(
            LaggedStartMap(
                Restore, braces,
                lag_ratio=0.7
            ),
        )
        for word in words:
            self.play(Write(word, run_time=1))
        self.wait()


class ShowSegmentSplitWithoutImage(ShowSegmentSplit):
    CONFIG = {
        "include_image": False,
    }


class ShowCircleToCircleLineAtMultiplePoints(ExternallyAnimatedScene):
    pass


class ConjectureLineEquivalence(ExternallyAnimatedScene):
    pass


class WriteConjecture(Scene):
    CONFIG = {
        "image_name": "ConjectureLineEquivalenceBigSphere",
        "q_coords": 1.28 * UP + 0.15 * LEFT,
        "circle_point_coords": 0.84 * RIGHT + 0.05 * DOWN,
        "tangent_point_coords": 0.85 * UP + 1.75 * LEFT,
        "plane_line_color": GOLD,
        "text_scale_factor": 0.75,
        "shift_plane_word_down": False,
        "include_image": False,
    }

    def construct(self):
        if self.include_image:
            image = ImageMobject(self.image_name)
            image.set_height(FRAME_HEIGHT)
            self.add(image)

        title = TextMobject("Conjecture:")
        title.to_corner(UR)

        cone_line = Line(self.q_coords, self.circle_point_coords)
        plane_line = Line(self.q_coords, self.tangent_point_coords)
        plane_line.set_color(self.plane_line_color)
        lines = VGroup(cone_line, plane_line)

        cone_line_words = TextMobject("Cone line")
        plane_line_words = TextMobject("Plane line")
        plane_line_words.set_color(self.plane_line_color)
        words = VGroup(cone_line_words, plane_line_words)

        for word in words:
            word.add_to_back(word.copy().set_stroke(BLACK, 5))
            word.in_equation = word.copy()

        equation = VGroup(
            TexMobject("||"),
            words[0].in_equation,
            TexMobject("||"),
            TexMobject("="),
            TexMobject("||"),
            words[1].in_equation,
            TexMobject("||"),
        )
        equation.arrange(RIGHT, buff=SMALL_BUFF)
        equation.scale(0.75)
        equation.next_to(title, DOWN, MED_LARGE_BUFF)
        equation.shift_onto_screen()
        title.next_to(equation, UP)

        for word, line in zip(words, lines):
            word.scale(self.text_scale_factor)
            word.next_to(ORIGIN, UP, SMALL_BUFF)
            if self.shift_plane_word_down and (word is plane_line_words):
                word.next_to(ORIGIN, DOWN, SMALL_BUFF)
            angle = line.get_angle()
            if abs(angle) > 90 * DEGREES:
                angle += PI
            word.rotate(angle, about_point=ORIGIN)
            word.shift(line.get_center())

        self.play(LaggedStartMap(
            FadeInFromDown,
            VGroup(title, equation),
            lag_ratio=0.7
        ))
        self.wait()
        for word, line in zip(words, lines):
            self.play(ShowCreation(line))
            self.play(WiggleOutThenIn(line))
            self.play(ReplacementTransform(
                word.in_equation.copy(), word
            ))
            self.wait()


class WriteConjectureV2(WriteConjecture):
    CONFIG = {
        "image_name": "ConjectureLineEquivalenceSmallSphere",
        "q_coords": 2.2 * LEFT + 1.3 * UP,
        "circle_point_coords": 1.4 * LEFT + 2.25 * UP,
        "tangent_point_coords": 0.95 * LEFT + 1.51 * UP,
        "plane_line_color": PINK,
        "text_scale_factor": 0.5,
        "shift_plane_word_down": True,
        "include_image": False,
    }


class ShowQ(Scene):
    def construct(self):
        mob = TexMobject("Q")
        mob.scale(2)
        mob.add_to_back(mob.copy().set_stroke(BLACK, 5))
        self.play(FadeInFromDown(mob))
        self.wait()


class ShowBigSphereTangentLines(ExternallyAnimatedScene):
    pass


class LinesTangentToSphere(ExternallyAnimatedScene):
    pass


class QuickGeometryProof(Scene):
    def construct(self):
        radius = 2
        circle = Circle(color=BLUE, radius=radius)
        circle.shift(0.5 * DOWN)
        angle = 60 * DEGREES
        O = circle.get_center()
        p1 = circle.point_from_proportion(angle / TAU)
        p2 = circle.point_from_proportion(1 - angle / TAU)
        Q = O + (radius / np.cos(angle)) * RIGHT

        O_p1 = Line(O, p1)
        O_p2 = Line(O, p2)
        p1_Q = Line(p1, Q, color=MAROON_B)
        p2_Q = Line(p2, Q, color=MAROON_B)
        O_Q = DashedLine(O, Q)

        elbow = VGroup(Line(RIGHT, UR), Line(UR, UP))
        elbow.set_stroke(WHITE, 1)
        elbow.scale(0.2, about_point=ORIGIN)

        ticks = VGroup(Line(DOWN, UP), Line(DOWN, UP))
        ticks.scale(0.1)
        ticks.arrange(RIGHT, buff=SMALL_BUFF)

        equation = TexMobject(
            "\\Delta OP_1Q \\cong \\Delta OP_2Q",
            tex_to_color_map={
                "O": BLUE,
                "P_1": MAROON_B,
                "P_2": MAROON_B,
                "Q": YELLOW,
            }
        )
        equation.to_edge(UP)
        self.add(*equation)

        self.add(circle)
        self.add(
            TexMobject("O").next_to(O, LEFT),
            TexMobject("P_1").next_to(p1, UP).set_color(MAROON_B),
            TexMobject("P_2").next_to(p2, DOWN).set_color(MAROON_B),
            TexMobject("Q").next_to(Q, RIGHT).set_color(YELLOW),
        )
        self.add(O_p1, O_p2, p1_Q, p2_Q, O_Q)
        self.add(
            Dot(O, color=BLUE),
            Dot(p1, color=MAROON_B),
            Dot(p2, color=MAROON_B),
            Dot(Q, color=YELLOW)
        )
        self.add(
            elbow.copy().rotate(angle + PI, about_point=ORIGIN).shift(p1),
            elbow.copy().rotate(-angle + PI / 2, about_point=ORIGIN).shift(p2),
        )
        self.add(
            ticks.copy().rotate(angle).move_to(O_p1),
            ticks.copy().rotate(-angle).move_to(O_p2),
        )

        everything = VGroup(*self.mobjects)

        self.play(LaggedStartMap(
            GrowFromCenter, everything,
            lag_ratio=0.25,
            run_time=4
        ))


class ShowFocalSumEqualsCircleDistance(ExternallyAnimatedScene):
    pass


class FinalMovingEllipsePoint(ExternallyAnimatedScene):
    pass


class TiltPlaneWithSpheres(ExternallyAnimatedScene):
    pass


class NameDandelin(Scene):
    CONFIG = {"camera_config": {"background_opacity": 1}}

    def construct(self):
        title = TextMobject(
            "Proof by\\\\",
            "Germinal Pierre Dandelin (1822)"
        )
        title.to_edge(UP)

        portrait = ImageMobject("GerminalDandelin")
        portrait.set_height(5)
        portrait.next_to(title, DOWN)

        google_result = ImageMobject("GoogleDandelin")
        google_result.set_height(4)
        google_result.center()
        google_result.to_corner(DR)

        cmon_google = TextMobject("C'mon Google...")
        cmon_google.set_color(RED)
        cmon_google.next_to(google_result, RIGHT)
        cmon_google.next_to(google_result, UP, aligned_edge=RIGHT)

        dandelion = ImageMobject("DandelionSphere", height=1.5)
        dandelion.to_edge(LEFT, buff=LARGE_BUFF)
        dandelion.shift(UP)
        big_dandelion = dandelion.copy().scale(2)
        big_dandelion.next_to(dandelion, DOWN, buff=0)
        dandelions = Group(dandelion, big_dandelion)

        self.add(title[0])
        self.play(FadeInFromDown(portrait))
        self.play(Write(title[1]))
        self.wait()
        self.play(FadeInFrom(google_result, LEFT))
        self.play(Write(cmon_google, run_time=1))
        self.wait()

        self.play(LaggedStartMap(
            FadeInFromDown, dandelions,
            lag_ratio=0.7,
            run_time=1
        ))
        self.wait()


class DandelinSpheresInCylinder(ExternallyAnimatedScene):
    pass


class ProjectCircleOntoTiltedPlane(ExternallyAnimatedScene):
    pass


class CylinderDandelinSpheresChangingSlope(ExternallyAnimatedScene):
    pass


class DetailsLeftAsHomework(PiCreatureScene):
    CONFIG = {
        "default_pi_creature_kwargs": {
            "color": GREY_BROWN,
            "flip_at_start": False,
        },
        "default_pi_creature_start_corner": DL,
    }

    def construct(self):
        # morty = self.pi_creature
        self.pi_creature_says(
            "Details left \\\\ as homework",
            target_mode="hooray"
        )
        self.wait(3)


class AskWhyYouWouldChooseThisProof(PiCreatureScene):
    def construct(self):
        randy, other = self.pi_creatures
        screen = ScreenRectangle(height=4).to_edge(UP)

        for pi, vect, word in (randy, UP, "You"), (other, LEFT, "Non-math \\\\ enthusiast"):
            arrow = Vector(-vect, color=WHITE)
            arrow.next_to(pi, vect)
            label = TextMobject(word)
            label.next_to(arrow, vect)
            pi.arrow = arrow
            pi.label = label

        for pi, mode in (randy, "hooray"), (other, "tired"):
            self.play(
                GrowArrow(pi.arrow),
                FadeInFrom(pi.label, RIGHT),
                pi.change, mode,
            )
        self.play(
            randy.change, "raise_right_hand", screen,
            other.look_at, screen,
        )
        self.wait()
        self.play(other.change, "thinking", screen)
        self.wait(5)

    def create_pi_creatures(self):
        randy = Randolph(color=BLUE_C)
        other = PiCreature(color=RED_D)
        other.flip()
        group = VGroup(randy, other)
        group.arrange(RIGHT, buff=5)
        group.to_edge(DOWN)
        return group


class CreativeConstruction(PiCreatureScene):
    def construct(self):
        randy = self.pi_creature
        self.remove(randy)

        dandelin = ImageMobject("GerminalDandelin")
        dandelin.set_height(4)
        dandelin.move_to(FRAME_WIDTH * RIGHT / 4)

        lightbulb = Lightbulb()
        lightbulb.next_to(dandelin, UP)

        kant = ImageMobject("Kant")
        kant.set_height(3)
        bubble = ThoughtBubble(height=3, width=4)
        bubble.pin_to(kant)
        kant_words = TextMobject(
            "How is synthetic a priori\\\\" +
            "reasoning possible?"
        )
        kant_words.scale(0.75)
        bubble.position_mobject_inside(kant_words)
        kant_group = VGroup(bubble, kant_words, kant)
        kant_group.to_corner(UR)

        self.add(dandelin)
        self.add(lightbulb)
        self.play(
            Write(lightbulb, run_time=1),
            self.get_light_shine(lightbulb)
        )
        self.wait()
        self.play(
            lightbulb.next_to, randy, RIGHT,
            {"buff": LARGE_BUFF, "aligned_edge": UP},
            randy.change, "pondering",
            VFadeIn(randy),
            FadeOutAndShift(dandelin, DOWN),
        )

        self.play(
            self.get_light_shine(lightbulb),
            Blink(randy),
        )
        self.wait()
        self.play(
            FadeInFromDown(kant),
            Write(bubble),
            Write(kant_words),
        )

        lightbulb.generate_target()
        q_marks = VGroup()
        for submob in lightbulb.target.family_members_with_points():
            if True or get_norm(submob.get_center() - lightbulb.get_center()) > 0.25:
                q_mark = TexMobject("?")
                q_mark.set_height(0.25)
                q_mark.move_to(submob)
                Transform(submob, q_mark).update(1)
                q_marks.add(submob)
        q_marks.space_out_submobjects(2)

        self.wait()
        self.play(randy.change, 'confused', lightbulb)
        self.play(MoveToTarget(
            lightbulb,
            run_time=3,
            rate_func=there_and_back,
            lag_ratio=0.5
        ))
        self.play(Blink(randy))
        self.wait()

    def get_rings(self, center, max_radius, delta_r):
        radii = np.arange(0, max_radius, delta_r)
        rings = VGroup(*[
            Annulus(
                inner_radius=r1,
                outer_radius=r2,
                fill_opacity=0.75 * (1 - fdiv(r1, max_radius)),
                fill_color=YELLOW
            )
            for r1, r2 in zip(radii, radii[1:])
        ])
        rings.move_to(center)
        return rings

    def get_light_shine(self, lightbulb, max_radius=15.0, delta_r=0.025):
        rings = self.get_rings(
            lightbulb.get_center(),
            max_radius=15.0,
            delta_r=0.025,
        )
        return LaggedStartMap(
            FadeIn, rings,
            rate_func=there_and_back,
            run_time=2,
            lag_ratio=0.5
        )


class LockhartQuote(Scene):
    CONFIG = {
        "camera_config": {"background_opacity": 1}
    }

    def construct(self):
        mb_string = "Madame\\,\\,Bovary"
        ml_string = "Mona\\,\\,Lisa."
        strings = (mb_string, ml_string)
        quote_text = """
            \\large
            How do people come up with such ingenious arguments?
            It's the same way people come up with %s or %s
            I have no idea how it happens.  I only know that
            when it happens to me, I feel very fortunate.
        """ % strings
        quote_parts = [s for s in quote_text.split(" ") if s]
        quote = TextMobject(
            *quote_parts,
            tex_to_color_map={
                mb_string: BLUE,
                ml_string: YELLOW,
            },
            alignment=""
        )
        quote.set_width(FRAME_WIDTH - 2)
        quote.to_edge(UP)

        measurement = ImageMobject("MeasurementCover")
        madame_bovary = ImageMobject("MadameBovary")
        mona_lisa = ImageMobject("MonaLisa")
        pictures = Group(measurement, madame_bovary, mona_lisa)
        for picture in pictures:
            picture.set_height(4)
        pictures.arrange(RIGHT, buff=LARGE_BUFF)
        pictures.to_edge(DOWN)

        measurement.save_state()
        measurement.set_width(FRAME_WIDTH)
        measurement.center()
        measurement.fade(1)
        self.play(Restore(measurement))
        self.wait()
        for word in quote:
            anims = [ShowWord(word)]
            for text, picture in zip(strings, pictures[1:]):
                if word is quote.get_part_by_tex(text):
                    anims.append(FadeInFromDown(
                        picture, run_time=1
                    ))
            self.play(*anims)
            self.wait(0.005 * len(word)**1.5)
        self.wait(2)
        self.play(
            LaggedStartMap(
                FadeOutAndShiftDown, quote,
                lag_ratio=0.2,
                run_time=5,
            ),
            LaggedStartMap(
                FadeOutAndShiftDown, pictures,
                run_time=3,
            ),
        )


class ImmersedInGeometryProblems(PiCreatureScene):
    def construct(self):
        randy = self.pi_creature
        randy.center().to_edge(DOWN)

        for vect in compass_directions(start_vect=UL):
            self.play(randy.change, "pondering", 4 * vect)
            self.wait(2)


class ShowApollonianCircles(Scene):
    def construct(self):
        radii = [1.0, 2.0, 3.0]
        curvatures = [1.0 / r for r in radii]
        k4 = sum(curvatures) - 2 * np.sqrt(
            sum([
                k1 * k2
                for k1, k2 in it.combinations(curvatures, 2)
            ])
        )
        radii.append(1.0 / k4)
        centers = [
            ORIGIN, 3 * RIGHT, 4 * UP,
            4 * UP + 3 * RIGHT,
        ]
        circles = VGroup(*[
            Circle(radius=r).shift(c)
            for r, c in zip(radii, centers)
        ])

        circles.set_height(FRAME_HEIGHT - 3)
        circles.center().to_edge(DOWN)
        # circles.set_fill(opacity=1)
        circles.submobjects.reverse()
        circles.set_stroke(width=5)
        circles.set_color_by_gradient(BLUE, YELLOW)

        equation = TexMobject("""
            \\left(
            {1 \\over r_1} + {1 \\over r_2} +
            {1 \\over r_3} + {1 \\over r_4}
            \\right)^2 = 
            2\\left(
            {1 \\over r_1^2} + {1 \\over r_2^2} +
            {1 \\over r_3^2} + {1 \\over r_4^2}
            \\right)
        """)
        # equation.scale(1.5)
        equation.next_to(circles, UP)

        self.add(equation)
        self.play(LaggedStartMap(
            DrawBorderThenFill, circles
        ))
        self.wait()


class EllipseLengthsLinedUp(EccentricityInThumbtackCase):
    def construct(self):
        ellipse = self.get_ellipse(eccentricity=0.6)
        ellipse.scale(2)
        foci = self.get_foci(ellipse)

        point = VectorizedPoint()
        point_movement = cycle_animation(
            MoveAlongPath(
                point, ellipse,
                run_time=5,
                rate_func=linear,
            )
        )

        arrow = Vector(RIGHT, color=WHITE)
        arrow.to_edge(LEFT)
        q_mark = TexMobject("?")
        q_mark.next_to(arrow, UP)

        lines = VGroup(*[Line(UP, DOWN) for x in range(2)])
        lines.set_color_by_gradient(PINK, GOLD)
        lines.set_stroke(width=5)

        h_line = Line(LEFT, RIGHT, color=WHITE)
        h_line.set_width(0.25)

        def update_lines(lines):
            for line, focus in zip(lines, foci):
                d = get_norm(point.get_center() - focus)
                line.put_start_and_end_on(
                    ORIGIN, d * UP
                )
            lines.arrange(DOWN, buff=0)
            lines.next_to(arrow, RIGHT)
            h_line.move_to(lines[0].get_bottom())
        lines_animation = Mobject.add_updater(
            lines, update_lines
        )

        self.add(point_movement)
        self.add(arrow)
        self.add(q_mark)
        self.add(h_line)
        self.add(lines_animation)

        self.wait(20)


class ReactionToGlimpseOfGenius(TeacherStudentsScene, CreativeConstruction):
    def construct(self):
        morty = self.teacher

        lightbulb = Lightbulb()
        lightbulb.set_stroke(width=4)
        lightbulb.scale(1.5)
        lightbulb.move_to(self.hold_up_spot, DOWN)

        rings = self.get_rings(
            lightbulb.get_center(),
            max_radius=15,
            delta_r=0.1,
        )

        arrow = Vector(RIGHT, color=WHITE)
        arrow.next_to(lightbulb, LEFT)

        clock = Clock()
        clock.next_to(arrow, LEFT)

        pi_lights = VGroup()
        for pi in self.students:
            light = Lightbulb()
            light.scale(0.75)
            light.next_to(pi, UP)
            pi.light = light
            pi_lights.add(light)

        q_marks = VGroup()
        for submob in lightbulb:
            q_mark = TexMobject("?")
            q_mark.move_to(submob)
            q_marks.add(q_mark)
        q_marks.space_out_submobjects(2)

        self.student_says(
            "I think Lockhart was \\\\"
            "speaking more generally.",
            target_mode="sassy",
            added_anims=[morty.change, "guilty"]
        )
        self.wait(2)
        self.play(
            morty.change, "raise_right_hand",
            FadeInFromDown(lightbulb),
            RemovePiCreatureBubble(self.students[1]),
            self.get_student_changes(*3 * ["confused"]),
            run_time=1
        )
        self.play(Transform(
            lightbulb, q_marks,
            run_time=3,
            rate_func=there_and_back_with_pause,
            lag_ratio=0.5
        ))
        self.play(
            ClockPassesTime(clock, hours_passed=4, run_tim=4),
            VFadeIn(clock),
            GrowArrow(arrow),
            self.get_student_changes(
                *3 * ["pondering"],
                look_at_arg=clock
            )
        )
        self.play(
            ClockPassesTime(clock, run_time=1, hours_passed=1),
            VFadeOut(clock),
            FadeOut(arrow),
            lightbulb.scale, 1.5,
            lightbulb.move_to, 2 * UP,
            self.get_student_changes(
                *3 * ["awe"],
                look_at_arg=2 * UP
            ),
            run_time=1
        )
        self.play(self.get_light_shine(lightbulb))
        self.play(
            ReplacementTransform(
                VGroup(lightbulb),
                pi_lights
            ),
            morty.change, "happy",
            *[
                ApplyMethod(pi.change, mode, pi.get_top())
                for pi, mode in zip(self.students, [
                    "hooray", "tease", "surprised"
                ])
            ]
        )
        self.wait(3)


class DandelinEndScreen(PatreonEndScreen):
    CONFIG = {
        "specific_patrons": [
            "Juan Benet",
            "Matt Russell",
            "Soekul",
            "Keith Smith",
            "Burt Humburg",
            "CrypticSwarm",
            "Brian Tiger Chow",
            "Joseph Kelly",
            "Roy Larson",
            "Andrew Sachs",
            "Devin Scott",
            "Akash Kumar",
            "Arthur Zey",
            "Ali Yahya",
            "Mayank M. Mehrotra",
            "Lukas Biewald",
            "Yana Chernobilsky",
            "Kaustuv DeBiswas",
            "Yu Jun",
            "Dave Nicponski",
            "Damion Kistler",
            "Jordan Scales",
            "Markus Persson",
            "Fela",
            "Fred Ehrsam",
            "Gary Kong",
            "Randy C. Will",
            "Britt Selvitelle",
            "Jonathan Wilson",
            "Ryan Atallah",
            "Joseph John Cox",
            "Luc Ritchie",
            "Valeriy Skobelev",
            "Adrian Robinson",
            "Michael Faust",
            "Solara570",
            "George M. Botros",
            "Peter Ehrnstrom",
            "Kai-Siang Ang",
            "Alexis Olson",
            "Ludwig",
            "Omar Zrien",
            "Sindre Reino Trosterud",
            "Jeff Straathof",
            "Matt Langford",
            "Matt Roveto",
            "Marek Cirkos",
            "Magister Mugit",
            "Stevie Metke",
            "Cooper Jones",
            "James Hughes",
            "John V Wertheim",
            "Chris Giddings",
            "Song Gao",
            "Richard Burgmann",
            "John Griffith",
            "Chris Connett",
            "Steven Tomlinson",
            "Jameel Syed",
            "Bong Choung",
            "Ignacio Freiberg",
            "Zhilong Yang",
            "Giovanni Filippi",
            "Eric Younge",
            "Prasant Jagannath",
            "James H. Park",
            "Norton Wang",
            "Kevin Le",
            "Oliver Steele",
            "Yaw Etse",
            "Dave B",
            "supershabam",
            "Delton Ding",
            "Thomas Tarler",
            "1stViewMaths",
            "Jacob Magnuson",
            "Mark Govea",
            "Clark Gaebel",
            "Mathias Jansson",
            "David Clark",
            "Michael Gardner",
            "Mads Elvheim",
            "Awoo",
            "Dr . David G. Stork",
            "Ted Suzman",
            "Linh Tran",
            "Andrew Busey",
            "John Haley",
            "Ankalagon",
            "Eric Lavault",
            "Boris Veselinovich",
            "Julian Pulgarin",
            "Jeff Linse",
            "Robert Teed",
            "Jason Hise",
            "Bernd Sing",
            "James Thornton",
            "Mustafa Mahdi",
            "Mathew Bramson",
            "Jerry Ling",
            "Rish Kundalia",
            "Achille Brighton",
            "Ripta Pasay",
        ],
    }
