import scipy
import fractions

from big_ol_pile_of_manim_imports import *

class Chapter9OpeningQuote(OpeningQuote):
    CONFIG = {
        "quote" : [
            "We often hear that mathematics consists mainly of", 
            "proving theorems.",
            "Is a writer's job mainly that of\\\\",
            "writing sentences?"
        ],
        "highlighted_quote_terms" : {
            "proving theorems." : MAROON_B,
            "writing sentences?" : MAROON_B,
        },
        "author" : "Gian-Carlo Rota",
    }

class AverageOfContinuousVariable(GraphScene):
    CONFIG = {
        "bounds" : [1, 7],
        "bound_colors" : [RED, GREEN],
    }
    def construct(self):
        self.setup_axes()
        graph = self.get_graph(
            lambda x : 0.1*x*(x-3)*(x-6) + 4
        )
        graph_label = self.get_graph_label(graph, "f(x)")
        boundary_lines = self.get_vertical_lines_to_graph(
            graph, *self.bounds, num_lines = 2,
            line_class = DashedLine
        )
        for line, color in zip(boundary_lines, self.bound_colors):
            line.set_color(color)
        v_line = self.get_vertical_line_to_graph(
            self.bounds[0], graph, color = YELLOW,
        )

        question = TextMobject(
            "What is the average \\\\ value of $f(x)$?"
        )
        question.next_to(boundary_lines, UP)

        self.play(ShowCreation(graph), Write(graph_label))
        self.play(ShowCreation(boundary_lines))
        self.play(FadeIn(
            question,
            run_time = 2,
            lag_ratio = 0.5,
        ))
        self.play(ShowCreation(v_line))
        for bound in reversed(self.bounds):
            self.play(self.get_v_line_change_anim(
                v_line, graph, bound, 
                run_time = 3,
            ))
            self.wait()
        self.wait()

    def get_v_line_change_anim(self, v_line, graph, target_x, **kwargs):
        start_x = self.x_axis.point_to_number(v_line.get_bottom())
        def update(v_line, alpha):
            new_x = interpolate(start_x, target_x, alpha)
            v_line.put_start_and_end_on(
                self.coords_to_point(new_x, 0),
                self.input_to_graph_point(new_x, graph)
            )
            return v_line
        return UpdateFromAlphaFunc(v_line, update, **kwargs)

class ThisVideo(TeacherStudentsScene):
    def construct(self):
        series = VideoSeries()
        series.to_edge(UP)
        this_video = series[8]

        self.play(FadeIn(series, lag_ratio = 0.5))
        self.teacher_says(
            "A new view of \\\\ the fundamental theorem",
            bubble_kwargs = {"height" : 3},
            added_anims = [
                this_video.shift, this_video.get_height()*DOWN/2,
                this_video.set_color, YELLOW,
            ]
        )
        self.change_student_modes(*["pondering"]*3)
        self.wait(3)

class AverageOfSineStart(AverageOfContinuousVariable):
    CONFIG = {
        "y_min" : -2, 
        "y_max" : 2,
        "x_min" : -1,
        "x_max" : 2.5*np.pi,
        "x_leftmost_tick" : 0,
        "x_tick_frequency" : np.pi/4,
        "x_axis_width" : 12,
        "graph_origin" : 5*LEFT,
        "x_label_scale_val" : 0.75,
        "func" : np.sin,
        "graph_color" : BLUE,
        "bounds" : [0, np.pi],
    }
    def construct(self):
        self.setup_axes()
        self.add_graph()
        self.ask_about_average()

    def add_graph(self, run_time = 1):
        graph = self.get_graph(self.func, color = self.graph_color)
        graph_label = self.get_graph_label(
            graph, "\\sin(x)",
            direction = UP
        )
        self.play(
            ShowCreation(graph),
            Write(graph_label),
            run_time = run_time
        )

        self.graph = graph
        self.graph_label = graph_label

    def ask_about_average(self):
        half_period_graph = self.get_graph_portion_between_bounds()
        question = TextMobject("Average height?")
        question.to_edge(UP)
        arrow = Arrow(question.get_bottom(), half_period_graph.get_top())
        midpoint = np.mean(self.bounds)
        v_line = self.get_vertical_line_to_graph(
            midpoint, self.graph,
            line_class = DashedLine,
            color = WHITE
        )

        self.play(FadeIn(half_period_graph))
        self.play(
            Write(question, run_time = 2),
            ShowCreation(arrow)
        )
        self.play(ShowCreation(v_line))
        for bound in self.bounds + [midpoint]:
            self.play(self.get_v_line_change_anim(
                v_line, self.graph, bound,
                run_time = 3
            ))

    #########

    def get_graph_portion_between_bounds(self):
        self.graph_portion_between_bounds = self.get_graph(
            self.func,
            x_min = self.bounds[0],
            x_max = self.bounds[1],
            color = YELLOW
        )
        return self.graph_portion_between_bounds

    def setup_axes(self):
        GraphScene.setup_axes(self)
        self.add_x_axis_labels()

    def add_x_axis_labels(self):
        labels_and_x_values = [
            ("\\pi/2", np.pi/2),
            ("\\pi", np.pi),
            ("3\\pi/2", 3*np.pi/2),
            ("2\\pi", 2*np.pi),
        ]
        self.x_axis_labels = VGroup()
        for label, x in labels_and_x_values:
            tex_mob = TexMobject(label)
            tex_mob.scale(self.x_label_scale_val)
            tex_mob.move_to(
                self.coords_to_point(x, -3*self.x_axis.tick_size), 
            )
            self.add(tex_mob)
            self.x_axis_labels.add(tex_mob)

class LengthOfDayGraph(GraphScene):
    CONFIG = {
        "x_min" : 0,
        "x_max" : 365,
        "x_axis_width" : 12,
        "x_tick_frequency" : 25,
        "x_labeled_nums" : list(range(50, 365, 50)),
        "x_axis_label" : "Days since March 21",
        "y_min" : 0,
        "y_max" : 16,
        "y_axis_height" : 6,
        "y_tick_frequency" : 1,
        "y_labeled_nums" : list(range(2, 15, 2)),
        "y_axis_label" : "Hours of daylight",
        "graph_origin" : 6*LEFT + 3*DOWN,
        "camera_class" : ThreeDCamera,
        "camera_config" : {
            "shading_factor" : 1,
        }
    }
    def construct(self):
        self.setup_axes()
        self.add_graph()
        self.show_solar_pannel()
        self.set_color_summer_months()
        self.mention_constants()

    def add_graph(self):
        x_label = self.x_axis_label_mob
        y_label = self.y_axis_label_mob

        graph = self.get_graph(
            lambda x : 2.7*np.sin((2*np.pi)*x/365 ) + 12.4,
            color = GREEN,
        )
        graph_label = TexMobject("2.7\\sin(2\\pi x/365) + 12.4")
        graph_label.to_corner(UP+RIGHT).shift(LEFT)
        VGroup(*graph_label[3:6]).set_color(graph.get_color())
        graph_label[9].set_color(YELLOW)

        self.remove(x_label, y_label)
        for label in y_label, x_label:
            self.play(FadeIn(
                label, 
                run_time = 2,
                lag_ratio = 0.5
            ))
        self.play(
            ShowCreation(graph, rate_func=linear),
            FadeIn(
                graph_label,
                rate_func = squish_rate_func(smooth, 0.5, 1),
                lag_ratio = 0.5
            ),
            run_time = 3,
        )
        self.wait()

        self.graph = graph 
        self.graph_label = graph_label

    def show_solar_pannel(self):
        randy = Randolph()
        randy.to_edge(DOWN)
        panel = ThreeDMobject(*[
            Rectangle(
                height = 0.7, width = 0.25,
                fill_color = DARK_GREY,
                fill_opacity = 1,
                stroke_width = 1,
                stroke_color = GREY,
            )
            for x in range(6)
        ])
        panel.arrange(RIGHT, buff = SMALL_BUFF)
        panel.center()
        panels = ThreeDMobject(panel, panel.copy(), panel.copy())
        panels.arrange(DOWN)
        panels.rotate(4*np.pi/12, DOWN)
        panels.rotate(-np.pi/6, OUT)
        side_vect = RIGHT
        side_vect = rotate_vector(side_vect, 4*np.pi/12, DOWN)
        side_vect = rotate_vector(side_vect, -np.pi/3, OUT)
        panels.next_to(randy.get_corner(UP+RIGHT), RIGHT)

        self.play(FadeIn(randy))
        self.play(
            randy.change, "thinking", panels.get_right(),
            FadeIn(
                panels, 
                run_time = 2,
                lag_ratio = 0.5
            )
        )
        for angle in -np.pi/4, np.pi/4:
            self.play(*[
                Rotate(
                    panel, angle,
                    axis = side_vect,
                    in_place = True,
                    run_time = 2,
                    rate_func = squish_rate_func(smooth, a, a+0.8)              
                )
                for panel, a in zip(panels, np.linspace(0, 0.2, len(panels)))
            ])
        self.play(Blink(randy))
        self.play(*list(map(FadeOut, [randy, panels])))

    def set_color_summer_months(self):
        summer_rect = Rectangle()
        summer_rect.set_stroke(width = 0)
        summer_rect.set_fill(YELLOW, opacity = 0.25)
        summer_rect.replace(Line(
            self.graph_origin,
            self.coords_to_point(365/2, 15.5)
        ), stretch = True)

        winter_rect = Rectangle()
        winter_rect.set_stroke(width = 0)
        winter_rect.set_fill(BLUE, opacity = 0.25)
        winter_rect.replace(Line(
            self.coords_to_point(365/2, 15.5),
            self.coords_to_point(365, 0),
        ), stretch = True)


        summer_words, winter_words = [
            TextMobject("%s \\\\ months"%s).move_to(rect)
            for s, rect in [
                ("Summer", summer_rect),
                ("Winter", winter_rect),
            ]
        ]

        for rect, words in (summer_rect, summer_words), (winter_rect, winter_words):
            self.play(
                FadeIn(rect),
                Write(words, run_time = 2)
            )
        self.wait()

    def mention_constants(self):
        #2.7\\sin(2\\pi t/365) + 12.4
        constants = VGroup(*[
            VGroup(*self.graph_label[i:j])
            for i, j in [(0, 3), (7, 9), (11, 14), (16, 20)]
        ])

        self.play(*[
            ApplyFunction(
                lambda c : c.scale_in_place(0.9).shift(SMALL_BUFF*DOWN).set_color(RED),
                constant,
                run_time = 3,
                rate_func = squish_rate_func(there_and_back, a, a+0.7)
            )
            for constant, a in zip(
                constants, 
                np.linspace(0, 0.3, len(constants))
            )
        ])
        self.wait()


    #####

class AskAboutAverageOfContinuousVariables(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "The average \\dots of a \\\\ continuous thing?",
            target_mode = "sassy",
        )
        self.change_student_modes("confused", "sassy", "confused")
        self.play(self.teacher.change_mode, "happy")
        self.wait(2)

class AverageOfFiniteSet(Scene):
    CONFIG = {
        "lengths" : [1, 4, 2, 5]
    }
    def construct(self):
        lengths = self.lengths
        lines = VGroup(*[
            Line(ORIGIN, length*RIGHT)
            for length in lengths
        ])
        colors = Color(BLUE).range_to(RED, len(lengths))
        lines.set_color_by_gradient(*colors)
        lines.arrange(RIGHT)
        lines.generate_target()
        lines.target.arrange(RIGHT, buff = 0)
        for mob in lines, lines.target:
            mob.shift(UP)
        brace = Brace(lines.target, UP)

        labels = VGroup(*[
            TexMobject(str(d)).next_to(line, UP).set_color(line.get_color())
            for d, line in zip(lengths, lines)
        ])
        plusses = [TexMobject("+") for x in range(len(lengths)-1)]
        symbols = VGroup(*
            plusses + [TexMobject("=")]
        )
        symbols.set_fill(opacity = 0)

        labels.generate_target()
        symbols.generate_target()
        symbols.target.set_fill(opacity = 1)
        sum_eq = VGroup(*it.chain(*list(zip(labels.target, symbols.target))))
        sum_eq.arrange(RIGHT)
        sum_eq.next_to(brace, UP)

        sum_mob = TexMobject(str(sum(lengths)))
        sum_mob.next_to(sum_eq, RIGHT)

        dividing_lines = VGroup(*[
            DashedLine(p + MED_SMALL_BUFF*UP, p + MED_LARGE_BUFF*DOWN)
            for alpha in np.linspace(0, 1, len(lengths)+1)
            for p in [interpolate(
                lines.target.get_left(),
                lines.target.get_right(),
                alpha
            )]
        ])

        lower_braces = VGroup(*[
            Brace(VGroup(*dividing_lines[i:i+2]), DOWN)
            for i in range(len(lengths))
        ])
        averages = VGroup(*[
            lb.get_text("$%d/%d$"%(sum(lengths), len(lengths)))
            for lb in lower_braces
        ])
        circle = Circle(color = YELLOW)
        circle.replace(averages[1], stretch = True)
        circle.scale_in_place(1.5)

        self.add(lines)
        self.play(FadeIn(
            labels,
            lag_ratio = 0.5,
            run_time = 3
        ))
        self.wait()
        self.play(
            GrowFromCenter(brace),
            *list(map(MoveToTarget, [lines, labels, symbols])),
            run_time = 2
        )
        self.play(Write(sum_mob))
        self.wait()
        self.play(ShowCreation(dividing_lines, run_time = 2))
        self.play(*it.chain(
            list(map(Write, averages)),
            list(map(GrowFromCenter, lower_braces))
        ))
        self.play(ShowCreation(circle))
        self.wait(2)

class TryToAddInfinitelyManyPoints(AverageOfSineStart):
    CONFIG = {
        "max_denominator" : 40,
    }
    def construct(self):
        self.add_graph()
        self.try_to_add_infinitely_many_values()
        self.show_continuum()
        self.mention_integral()

    def add_graph(self):
        self.setup_axes()
        AverageOfSineStart.add_graph(self, run_time = 0)
        self.add(self.get_graph_portion_between_bounds())
        self.graph_label.to_edge(RIGHT)
        self.graph_label.shift(DOWN)

    def try_to_add_infinitely_many_values(self):
        v_lines = VGroup(*[
            self.get_vertical_line_to_graph(
                numerator*np.pi/denominator, self.graph,
                color = YELLOW,
                stroke_width = 6./denominator
            )
            for denominator in range(self.max_denominator)
            for numerator in range(1, denominator)
            if fractions.gcd(numerator, denominator) == 1
        ])
        ghost_lines = v_lines.copy().set_stroke(GREY)

        v_lines.generate_target()
        start_lines = VGroup(*v_lines.target[:15])
        end_lines = VGroup(*v_lines.target[15:])

        plusses = VGroup(*[TexMobject("+") for x in start_lines])
        sum_eq = VGroup(*it.chain(*list(zip(start_lines, plusses))))
        sum_eq.add(*end_lines)
        sum_eq.arrange(RIGHT)
        sum_eq.next_to(v_lines[0], UP, aligned_edge = LEFT)
        sum_eq.to_edge(UP, buff = MED_SMALL_BUFF)

        h_line = Line(LEFT, RIGHT)
        h_line.set_width(start_lines.get_width())
        h_line.set_color(WHITE)
        h_line.next_to(sum_eq, DOWN, aligned_edge = LEFT)

        infinity = TexMobject("\\infty")
        infinity.next_to(h_line, DOWN)

        self.play(ShowCreation(
            v_lines, 
            run_time = 3,
        ))
        self.add(ghost_lines, v_lines)
        self.wait(2)
        self.play(
            MoveToTarget(
                v_lines,
                run_time = 3,
                lag_ratio = 0.5
            ),
            Write(plusses)
        )
        self.play(ShowCreation(h_line))
        self.play(Write(infinity))
        self.wait()

    def show_continuum(self):
        arrow = Arrow(ORIGIN, UP+LEFT)
        input_range = Line(*[
            self.coords_to_point(bound, 0)
            for bound in self.bounds
        ])
        VGroup(arrow, input_range).set_color(RED)

        self.play(FadeIn(arrow))
        self.play(
            arrow.next_to, input_range.get_start(), 
            DOWN+RIGHT, SMALL_BUFF
        )
        self.play(
            arrow.next_to, input_range.copy().get_end(), 
            DOWN+RIGHT, SMALL_BUFF,
            ShowCreation(input_range),
            run_time = 3,
        )
        self.play(
            arrow.next_to, input_range.get_start(), 
            DOWN+RIGHT, SMALL_BUFF,
            run_time = 3
        )
        self.play(FadeOut(arrow))
        self.wait()

    def mention_integral(self):
        randy = Randolph()
        randy.to_edge(DOWN)
        randy.shift(3*LEFT)

        self.play(FadeIn(randy))
        self.play(PiCreatureBubbleIntroduction(
            randy, "Use an integral!",
            bubble_class = ThoughtBubble,
            target_mode = "hooray"
        ))
        self.play(Blink(randy))
        curr_bubble = randy.bubble
        new_bubble = randy.get_bubble("Somehow...")
        self.play(
            Transform(curr_bubble, new_bubble),
            Transform(curr_bubble.content, new_bubble.content),
            randy.change_mode, "shruggie",
        )
        self.play(Blink(randy))
        self.wait()

class FiniteSample(TryToAddInfinitelyManyPoints):
    CONFIG = {
        "dx" : 0.1,
        "graph_origin" : 6*LEFT + 0.5*DOWN,
    }
    def construct(self):
        self.add_graph()
        self.show_finite_sample()

    def show_finite_sample(self):
        v_lines = self.get_sample_lines(dx = self.dx)
        summed_v_lines = v_lines.copy()
        plusses = VGroup(*[
            TexMobject("+").scale(0.75)
            for l in v_lines
        ])
        numerator = VGroup(*it.chain(*list(zip(summed_v_lines, plusses))))
        for group in numerator, plusses:
            group.remove(plusses[-1])
        numerator.arrange(
            RIGHT, 
            buff = SMALL_BUFF,
            aligned_edge = DOWN
        )
        # numerator.set_width(FRAME_X_RADIUS)
        numerator.scale(0.5)
        numerator.move_to(self.coords_to_point(3*np.pi/2, 0))
        numerator.to_edge(UP)
        frac_line = TexMobject("\\over \\,")
        frac_line.stretch_to_fit_width(numerator.get_width())
        frac_line.next_to(numerator, DOWN)
        denominator = TextMobject("(Num. samples)")
        denominator.next_to(frac_line, DOWN)

        self.play(ShowCreation(v_lines, run_time = 3))
        self.wait()
        self.play(
            ReplacementTransform(
                v_lines.copy(),
                summed_v_lines,
                run_time = 3,
                lag_ratio = 0.5
            ),
            Write(
                plusses,
                rate_func = squish_rate_func(smooth, 0.3, 1)
            )
        )
        self.play(Write(frac_line, run_time = 1))
        self.play(Write(denominator))
        self.wait()

        self.plusses = plusses
        self.average = VGroup(numerator, frac_line, denominator)
        self.v_lines = v_lines

    ###

    def get_sample_lines(self, dx, color = YELLOW, stroke_width = 2):
        return VGroup(*[
            self.get_vertical_line_to_graph(
                x, self.graph,
                color = color,
                stroke_width = stroke_width,
            )
            for x in np.arange(
                self.bounds[0]+dx, 
                self.bounds[1], 
                dx
            )
        ])

class FiniteSampleWithMoreSamplePoints(FiniteSample):
    CONFIG = {
        "dx" : 0.05
    }

class FeelsRelatedToAnIntegral(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Seems integral-ish...",
            target_mode = "maybe"
        )
        self.play(self.teacher.change_mode, "happy")
        self.wait(2)

class IntegralOfSine(FiniteSample):
    CONFIG = {
        "thin_dx" : 0.01,
        "rect_opacity" : 0.75,
    }
    def construct(self):
        self.force_skipping()
        FiniteSample.construct(self)
        self.remove(self.y_axis_label_mob)
        self.remove(*self.x_axis_labels[::2])
        self.revert_to_original_skipping_status()

        self.put_average_in_corner()
        self.write_integral()
        self.show_riemann_rectangles()
        self.let_dx_approach_zero()
        self.bring_back_average()
        self.distribute_dx()
        self.let_dx_approach_zero(restore = False)
        self.write_area_over_width()
        self.show_moving_v_line()

    def put_average_in_corner(self):
        self.average.save_state()
        self.plusses.set_stroke(width = 0.5)
        self.play(
            self.average.scale, 0.75,
            self.average.to_corner, DOWN+RIGHT,
        )

    def write_integral(self):
        integral = TexMobject("\\int_0^\\pi", "\\sin(x)", "\\,dx")
        integral.move_to(self.graph_portion_between_bounds)
        integral.to_edge(UP)

        self.play(Write(integral))
        self.wait(2)

        self.integral = integral

    def show_riemann_rectangles(self):
        kwargs = {
            "dx" : self.dx,
            "x_min" : self.bounds[0],
            "x_max" : self.bounds[1],
            "fill_opacity" : self.rect_opacity,
        }
        rects = self.get_riemann_rectangles(self.graph, **kwargs)
        rects.set_stroke(YELLOW, width = 1)
        flat_rects = self.get_riemann_rectangles(
            self.get_graph(lambda x : 0),
            **kwargs
        )
        thin_kwargs = dict(kwargs)
        thin_kwargs["dx"] = self.thin_dx
        thin_kwargs["stroke_width"] = 0
        self.thin_rects = self.get_riemann_rectangles(
            self.graph,
            **thin_kwargs
        )


        start_index = 20
        end_index = start_index + 5
        low_opacity = 0.5
        high_opacity = 1

        start_rect = rects[start_index]
        side_brace = Brace(start_rect, LEFT, buff = SMALL_BUFF)
        bottom_brace = Brace(start_rect, DOWN, buff = SMALL_BUFF)
        sin_x = TexMobject("\\sin(x)")
        sin_x.next_to(side_brace, LEFT, SMALL_BUFF)
        dx = bottom_brace.get_text("$dx$", buff = SMALL_BUFF)

        self.transform_between_riemann_rects(
            flat_rects, rects,
            replace_mobject_with_target_in_scene = True,
        )
        self.remove(self.v_lines)
        self.wait()

        rects.save_state()
        self.play(*it.chain(
            [
                ApplyMethod(
                    rect.set_style_data, BLACK, 1,
                    None, #Fill color
                    high_opacity if rect is start_rect else low_opacity
                )
                for rect in rects
            ],
            list(map(GrowFromCenter, [side_brace, bottom_brace])),
            list(map(Write, [sin_x, dx])),
        ))
        self.wait()
        for i in range(start_index+1, end_index):
            self.play(
                rects[i-1].set_fill, None, low_opacity,
                rects[i].set_fill, None, high_opacity,
                side_brace.set_height, rects[i].get_height(),
                side_brace.next_to, rects[i], LEFT, SMALL_BUFF,
                bottom_brace.next_to, rects[i], DOWN, SMALL_BUFF,
                MaintainPositionRelativeTo(sin_x, side_brace),
                MaintainPositionRelativeTo(dx, bottom_brace),
            )
            self.wait()
        self.play(
            rects.restore,
            *list(map(FadeOut, [sin_x, dx, side_brace, bottom_brace]))
        )

        self.rects = rects
        self.dx_brace = bottom_brace
        self.dx_label = dx

    def let_dx_approach_zero(self, restore = True):
        start_state = self.rects.copy()
        self.transform_between_riemann_rects(
            self.rects, self.thin_rects,
            run_time = 3
        )
        self.wait(2)
        if restore:
            self.transform_between_riemann_rects(
                self.rects, start_state.copy(),
                run_time = 2,
            )
            self.remove(self.rects)
            self.rects = start_state
            self.rects.set_fill(opacity = 1)
            self.play(
                self.rects.set_fill, None,
                    self.rect_opacity,
            )
            self.wait()

    def bring_back_average(self):
        num_samples = self.average[-1]

        example_dx = TexMobject("0.1")
        example_dx.move_to(self.dx_label)

        input_range = Line(*[
            self.coords_to_point(bound, 0)
            for bound in self.bounds
        ])
        input_range.set_color(RED)

        #Bring back average
        self.play(
            self.average.restore,
            self.average.center,
            self.average.to_edge, UP,
            self.integral.to_edge, DOWN,
            run_time = 2
        )
        self.wait()
        self.play(
            Write(self.dx_brace),
            Write(self.dx_label),
        )
        self.wait()
        self.play(
            FadeOut(self.dx_label),
            FadeIn(example_dx)
        )
        self.play(Indicate(example_dx))
        self.wait()
        self.play(ShowCreation(input_range))
        self.play(FadeOut(input_range))
        self.wait()

        #Ask how many there are
        num_samples_copy = num_samples.copy()
        v_lines = self.v_lines
        self.play(*[
            ApplyFunction(
                lambda l : l.shift(0.5*UP).set_color(GREEN),
                line,
                rate_func = squish_rate_func(
                    there_and_back, a, a+0.3
                ),
                run_time = 3,
            )
            for line, a in zip(
                self.v_lines, 
                np.linspace(0, 0.7, len(self.v_lines))
            )
        ] + [
            num_samples_copy.set_color, GREEN
        ])
        self.play(FadeOut(v_lines))
        self.wait()

        #Count number of samples
        num_samples_copy.generate_target()
        num_samples_copy.target.shift(DOWN + 0.5*LEFT)
        rhs = TexMobject("\\approx", "\\pi", "/", "0.1")
        rhs.next_to(num_samples_copy.target, RIGHT)
        self.play(
            MoveToTarget(num_samples_copy),
            Write(rhs.get_part_by_tex("approx")),
        )
        self.play(ShowCreation(input_range))
        self.play(ReplacementTransform(
            self.x_axis_labels[1].copy(), 
            rhs.get_part_by_tex("pi")
        ))
        self.play(FadeOut(input_range))
        self.play(
            ReplacementTransform(
                example_dx.copy(), 
                rhs.get_part_by_tex("0.1")
            ),
            Write(rhs.get_part_by_tex("/"))
        )
        self.wait(2)

        #Substitute number of samples
        self.play(ReplacementTransform(
            example_dx, self.dx_label
        ))
        dx = rhs.get_part_by_tex("0.1") 
        self.play(Transform(
            dx, TexMobject("dx").move_to(dx)
        ))
        self.wait(2)
        approx = rhs.get_part_by_tex("approx")
        rhs.remove(approx)
        self.play(
            FadeOut(num_samples),
            FadeOut(num_samples_copy),
            FadeOut(approx),
            rhs.next_to, self.average[1], DOWN
        )
        self.wait()

        self.pi_over_dx = rhs

    def distribute_dx(self):
        numerator, frac_line, denominator = self.average
        pi, over, dx = self.pi_over_dx
        integral = self.integral

        dx.generate_target()
        lp, rp = parens = TexMobject("()")
        parens.set_height(numerator.get_height())
        lp.next_to(numerator, LEFT)
        rp.next_to(numerator, RIGHT)
        dx.target.next_to(rp, RIGHT)

        self.play(
            MoveToTarget(dx, path_arc = np.pi/2),
            Write(parens),
            frac_line.stretch_to_fit_width, 
                parens.get_width() + dx.get_width(),
            frac_line.shift, dx.get_width()*RIGHT/2,
            FadeOut(over)
        )
        self.wait(2)

        average = VGroup(parens, numerator, dx, frac_line, pi)
        integral.generate_target()
        over_pi = TexMobject("\\frac{\\phantom{\\int \\sin(x)\\dx}}{\\pi}")
        integral.target.set_width(over_pi.get_width())
        integral.target.next_to(over_pi, UP)
        integral_over_pi = VGroup(integral.target, over_pi)
        integral_over_pi.to_corner(UP+RIGHT)
        arrow = Arrow(LEFT, RIGHT)
        arrow.next_to(integral.target, LEFT)

        self.play(
            average.scale, 0.9,
            average.next_to, arrow, LEFT,
            average.shift_onto_screen,
            ShowCreation(arrow),
            Write(over_pi),
            MoveToTarget(integral, run_time = 2)
        )
        self.wait(2)
        self.play(*list(map(FadeOut, [self.dx_label, self.dx_brace])))

        self.integral_over_pi = VGroup(integral, over_pi)
        self.average = average
        self.average_arrow = arrow

    def write_area_over_width(self):
        self.play(
            self.integral_over_pi.shift, 2*LEFT,
            *list(map(FadeOut, [self.average, self.average_arrow]))
        )

        average_height = TextMobject("Average height = ")
        area_over_width = TexMobject(
            "{\\text{Area}", "\\over\\,", "\\text{Width}}", "="
        )
        area_over_width.get_part_by_tex("Area").set_color_by_gradient(
            BLUE, GREEN
        )
        area_over_width.next_to(self.integral_over_pi[1][0], LEFT)
        average_height.next_to(area_over_width, LEFT)

        self.play(*list(map(FadeIn, [average_height, area_over_width])))
        self.wait()

    def show_moving_v_line(self):
        mean = np.mean(self.bounds)
        v_line = self.get_vertical_line_to_graph(
            mean, self.graph,
            line_class = DashedLine,
            color = WHITE,
        )
        self.play(ShowCreation(v_line))
        for count in range(2):
            for x in self.bounds + [mean]:
                self.play(self.get_v_line_change_anim(
                    v_line, self.graph, x,
                    run_time = 3
                ))

class Approx31(Scene):
    def construct(self):
        tex = TexMobject("\\approx 31")
        tex.set_width(FRAME_WIDTH - LARGE_BUFF)
        tex.to_edge(LEFT)
        self.play(Write(tex))
        self.wait(3)

class LetsSolveThis(TeacherStudentsScene):
    def construct(self):
        expression = TexMobject(
            "{\\int_0^\\pi ", " \\sin(x)", "\\,dx \\over \\pi}"
        )
        expression.to_corner(UP+LEFT)
        question = TextMobject(
            "What's the antiderivative \\\\ of",
            "$\\sin(x)$",
            "?"
        )
        for tex_mob in expression, question:
            tex_mob.set_color_by_tex("sin", BLUE)
        self.add(expression)

        self.teacher_says("Let's compute it.")
        self.wait()
        self.student_thinks(question)
        self.wait(2)

class Antiderivative(AverageOfSineStart):
    CONFIG = {
        "antideriv_color" : GREEN,
        "deriv_color" : BLUE,
        "riemann_rect_dx" : 0.01,
        "y_axis_label" : "",
        "graph_origin" : 4*LEFT + DOWN,
    }
    def construct(self):
        self.setup_axes()
        self.add_x_axis_labels()
        self.negate_derivative_of_cosine()
        self.walk_through_slopes()
        self.apply_ftoc()
        self.show_difference_in_antiderivative()
        self.comment_on_area()
        self.divide_by_pi()
        self.set_color_antiderivative_fraction()
        self.show_slope()
        self.bring_back_derivative()
        self.show_tangent_slope()

    def add_x_axis_labels(self):
        AverageOfSineStart.add_x_axis_labels(self)
        self.remove(*self.x_axis_labels[::2])

    def negate_derivative_of_cosine(self):
        cos, neg_cos, sin, neg_sin = graphs = [
            self.get_graph(func)
            for func in [
                np.cos, 
                lambda x : -np.cos(x), 
                np.sin,
                lambda x : -np.sin(x),
            ]
        ]
        VGroup(cos, neg_cos).set_color(self.antideriv_color)
        VGroup(sin, neg_sin).set_color(self.deriv_color)
        labels = ["\\cos(x)", "-\\cos(x)", "\\sin(x)", "-\\sin(x)"]
        x_vals = [2*np.pi, 2*np.pi, 5*np.pi/2, 5*np.pi/2]
        vects = [UP, DOWN, UP, DOWN]
        for graph, label, x_val, vect in zip(graphs, labels, x_vals, vects):
            graph.label = self.get_graph_label(
                graph, label,
                x_val = x_val,
                direction = vect,
                buff = SMALL_BUFF
            ) 

        derivs = []
        for F, f in ("\\cos", "-\\sin"), ("-\\cos", "\\sin"):
            deriv = TexMobject(
                "{d(", F, ")", "\\over\\,", "dx}", "(x)", 
                "=", f, "(x)"
            )
            deriv.set_color_by_tex(F, self.antideriv_color)
            deriv.set_color_by_tex(f, self.deriv_color)
            deriv.to_edge(UP)
            derivs.append(deriv)
        cos_deriv, neg_cos_deriv = derivs

        self.add(cos_deriv)
        for graph in cos, neg_sin:
            self.play(
                ShowCreation(graph, rate_func = smooth),
                Write(
                    graph.label, 
                    rate_func = squish_rate_func(smooth, 0.3, 1)
                ),
                run_time = 2
            )
            self.wait()
        self.wait()
        self.play(*[
            ReplacementTransform(*pair)
            for pair in [
                (derivs),
                (cos, neg_cos),
                (cos.label, neg_cos.label),
                (neg_sin, sin),
                (neg_sin.label, sin.label),
            ]
        ])
        self.wait(2)

        self.neg_cos = neg_cos
        self.sin = sin
        self.deriv = neg_cos_deriv

    def walk_through_slopes(self):
        neg_cos = self.neg_cos
        sin = self.sin

        faders = sin, sin.label
        for mob in faders:
            mob.save_state()
        sin_copy = self.get_graph(
            np.sin,
            x_min = 0,
            x_max = 2*np.pi,
            color = BLUE,
        )
        v_line = self.get_vertical_line_to_graph(
            0, neg_cos,
            line_class = DashedLine,
            color = WHITE
        )

        ss_group = self.get_secant_slope_group(
            0, neg_cos,
            dx = 0.001,
            secant_line_color = YELLOW
        )
        def quad_smooth(t):
            return 0.25*(np.floor(4*t) + smooth((4*t) % 1))

        self.play(*[
            ApplyMethod(m.fade, 0.6)
            for m in faders
        ])
        self.wait()
        self.play(*list(map(ShowCreation, ss_group)), run_time = 2)
        kwargs = {
            "run_time" : 20,
            "rate_func" : quad_smooth,
        }
        v_line_anim = self.get_v_line_change_anim(
            v_line, sin_copy, 2*np.pi,
            **kwargs
        )
        self.animate_secant_slope_group_change(
            ss_group,
            target_x = 2*np.pi,
            added_anims = [
                ShowCreation(sin_copy, **kwargs),
                v_line_anim
            ],
            **kwargs
        )
        self.play(
            *list(map(FadeOut, [ss_group, v_line, sin_copy]))
        )
        self.wait()

        self.ss_group = ss_group

    def apply_ftoc(self):
        deriv = self.deriv
        integral = TexMobject(
            "\\int", "^\\pi", "_0", "\\sin(x)", "\\,dx"
        )
        rhs = TexMobject(
            "=", "\\big(", "-\\cos", "(", "\\pi", ")", "\\big)",
            "-", "\\big(", "-\\cos", "(", "0", ")", "\\big)",
        )
        rhs.next_to(integral, RIGHT)
        equation = VGroup(integral, rhs)
        equation.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF)
        (start_pi, end_pi), (start_zero, end_zero) = start_end_pairs = [
            [
                m.get_part_by_tex(tex) 
                for m in (integral, rhs)
            ]
            for tex in ("\\pi", "0")
        ]

        for tex_mob in integral, rhs:
            tex_mob.set_color_by_tex("sin", self.deriv_color)
            tex_mob.set_color_by_tex("cos", self.antideriv_color)
            tex_mob.set_color_by_tex("0", YELLOW)
            tex_mob.set_color_by_tex("\\pi", YELLOW)

        self.play(
            Write(integral),
            self.deriv.scale, 0.5,
            self.deriv.center,
            self.deriv.to_edge, LEFT, MED_SMALL_BUFF,
            self.deriv.shift, UP,
        )
        self.wait()

        self.play(FadeIn(
            VGroup(*[part for part in rhs if part not in [end_pi, end_zero]]),
            lag_ratio = 0.5,
            run_time = 2,
        ))
        self.wait()
        for start, end in start_end_pairs:
            self.play(ReplacementTransform(
                start.copy(), end,
                path_arc = np.pi/6,
                run_time = 2
            ))
            self.wait()

        self.integral = integral
        self.rhs = rhs

    def show_difference_in_antiderivative(self):
        pi_point, zero_point = points = [
            self.input_to_graph_point(x, self.neg_cos)
            for x in reversed(self.bounds)
        ]
        interim_point = pi_point[0]*RIGHT + zero_point[1]*UP
        pi_dot, zero_dot = dots = [
            Dot(point, color = YELLOW)
            for point in points
        ]
        v_line = DashedLine(pi_point, interim_point)
        h_line = DashedLine(interim_point, zero_point)
        v_line_brace = Brace(v_line, RIGHT)
        two_height_label = v_line_brace.get_text(
            "$2$", buff = SMALL_BUFF
        )
        two_height_label.add_background_rectangle()

        pi = self.x_axis_labels[1]
        #Horrible hack
        black_pi = pi.copy().set_color(BLACK)
        self.add(black_pi, pi)

        cos_tex = self.rhs.get_part_by_tex("cos")

        self.play(ReplacementTransform(
            cos_tex.copy(), pi_dot
        ))
        self.wait()
        moving_dot = pi_dot.copy()
        self.play(
            ShowCreation(v_line),
            # Animation(pi),
            pi.shift, 0.8*pi.get_width()*(LEFT+UP),
            moving_dot.move_to, interim_point,
        )
        self.play(
            ShowCreation(h_line),
            ReplacementTransform(moving_dot, zero_dot)
        )
        self.play(GrowFromCenter(v_line_brace))
        self.wait(2)
        self.play(Write(two_height_label))
        self.wait(2)

        self.v_line = v_line
        self.h_line = h_line
        self.pi_dot = pi_dot
        self.zero_dot = zero_dot
        self.v_line_brace = v_line_brace
        self.two_height_label = two_height_label

    def comment_on_area(self):
        rects = self.get_riemann_rectangles(
            self.sin, 
            dx = self.riemann_rect_dx,
            stroke_width = 0,
            fill_opacity = 0.7,
            x_min = self.bounds[0],
            x_max = self.bounds[1],
        )
        area_two = TexMobject("2").replace(
            self.two_height_label
        )

        self.play(Write(rects))
        self.wait()
        self.play(area_two.move_to, rects)
        self.wait(2)

        self.rects = rects
        self.area_two = area_two

    def divide_by_pi(self):
        integral = self.integral
        rhs = self.rhs
        equals = rhs[0]
        rhs_without_eq = VGroup(*rhs[1:])
        frac_lines = VGroup(*[
            TexMobject("\\over\\,").stretch_to_fit_width(
                mob.get_width()
            ).move_to(mob)
            for mob in (integral, rhs_without_eq)
        ])
        frac_lines.shift(
            (integral.get_height()/2 + SMALL_BUFF)*DOWN
        )
        pi_minus_zeros = VGroup(*[
            TexMobject("\\pi", "-", "0").next_to(line, DOWN)
            for line in frac_lines
        ])
        for tex_mob in pi_minus_zeros:
            for tex in "pi", "0":
                tex_mob.set_color_by_tex(tex, YELLOW)

        answer = TexMobject(" = \\frac{2}{\\pi}")
        answer.next_to(
            frac_lines, RIGHT,
            align_using_submobjects = True
        )
        answer.shift_onto_screen()

        self.play(
            equals.next_to, frac_lines[0].copy(), RIGHT,
            rhs_without_eq.next_to, frac_lines[1].copy(), UP,
            *list(map(Write, frac_lines))
        )
        self.play(*[
            ReplacementTransform(
                integral.get_part_by_tex(tex).copy(),
                pi_minus_zeros[0].get_part_by_tex(tex)
            )
            for tex in ("\\pi","0")
        ] + [
            Write(pi_minus_zeros[0].get_part_by_tex("-"))
        ])
        self.play(*[
            ReplacementTransform(
                rhs.get_part_by_tex(
                    tex, substring = False
                ).copy(),
                pi_minus_zeros[1].get_part_by_tex(tex)
            )
            for tex in ("\\pi", "-", "0")
        ])
        self.wait(2)

        full_equation = VGroup(
            integral, frac_lines, rhs, pi_minus_zeros
        )
        background_rect = BackgroundRectangle(full_equation, fill_opacity = 1)
        background_rect.stretch_in_place(1.2, dim = 1)
        full_equation.add_to_back(background_rect)
        self.play(
            full_equation.shift, 
            (answer.get_width()+MED_LARGE_BUFF)*LEFT
        )
        self.play(Write(answer))
        self.wait()

        self.antiderivative_fraction = VGroup(
            rhs_without_eq,
            frac_lines[1],
            pi_minus_zeros[1]
        )
        self.integral_fraction = VGroup(
            integral,
            frac_lines[0],
            pi_minus_zeros[0],
            equals
        )

    def set_color_antiderivative_fraction(self):
        fraction = self.antiderivative_fraction
        big_rect = Rectangle(
            stroke_width = 0,
            fill_color = BLACK,
            fill_opacity = 0.75,
        )
        big_rect.set_width(FRAME_WIDTH)
        big_rect.set_height(FRAME_HEIGHT)
        morty = Mortimer()
        morty.to_corner(DOWN+RIGHT)

        self.play(
            FadeIn(big_rect),
            FadeIn(morty),
            Animation(fraction)
        )
        self.play(morty.change, "raise_right_hand", fraction)
        self.play(Blink(morty))
        self.wait(2)
        self.play(
            FadeOut(big_rect),
            FadeOut(morty),
            Animation(fraction)
        )

    def show_slope(self):
        line = Line(
            self.zero_dot.get_center(),
            self.pi_dot.get_center(),
        )
        line.set_color(RED)
        line.scale_in_place(1.2)

        new_v_line = self.v_line.copy().set_color(RED)
        new_h_line = self.h_line.copy().set_color(RED)

        pi = TexMobject("\\pi")
        pi.next_to(self.h_line, DOWN)

        self.play(
            FadeOut(self.rects),
            FadeOut(self.area_two)
        )
        self.play(ShowCreation(new_v_line))
        self.play(
            ShowCreation(new_h_line),
            Write(pi)
        )
        self.wait()
        self.play(ShowCreation(line, run_time = 2))
        self.wait()

    def bring_back_derivative(self):
        self.play(
            FadeOut(self.integral_fraction),
            self.deriv.scale, 1.7,
            self.deriv.to_corner, UP+LEFT, MED_LARGE_BUFF,
            self.deriv.shift, MED_SMALL_BUFF*DOWN,
        )
        self.wait()

    def show_tangent_slope(self):
        ss_group = self.get_secant_slope_group(
            0, self.neg_cos,
            dx = 0.001,
            secant_line_color = YELLOW,
            secant_line_length = 4,
        )

        self.play(*list(map(ShowCreation, ss_group)), run_time = 2)
        for count in range(2):
            for x in reversed(self.bounds):
                self.animate_secant_slope_group_change(
                    ss_group,
                    target_x = x,
                    run_time = 6,
                )

class GeneralAverage(AverageOfContinuousVariable):
    CONFIG = {
        "bounds" : [1, 6],
        "bound_colors" : [GREEN, RED],
        "graph_origin" : 5*LEFT + 2*DOWN,
        "num_rect_iterations" : 4,
        "max_dx" : 0.25,
    }
    def construct(self):
        self.setup_axes()
        self.add_graph()
        self.ask_about_average()
        self.show_signed_area()
        self.put_area_away()
        self.show_finite_sample()
        self.show_improving_samples()

    def add_graph(self):
        graph = self.get_graph(self.func)
        graph_label = self.get_graph_label(graph, "f(x)")
        v_lines = VGroup(*[
            self.get_vertical_line_to_graph(
                x, graph, line_class = DashedLine
            )
            for x in self.bounds
        ])
        for line, color in zip(v_lines, self.bound_colors):
            line.set_color(color)
        labels = list(map(TexMobject, "ab"))
        for line, label in zip(v_lines, labels):
            vect = line.get_start()-line.get_end()
            label.next_to(line, vect/get_norm(vect))
            label.set_color(line.get_color())

        self.y_axis_label_mob.shift(0.7*LEFT)

        self.play(
            ShowCreation(graph),
            Write(
                graph_label, 
                rate_func = squish_rate_func(smooth, 0.5, 1)
            ),
            run_time = 2
        )
        for line, label in zip(v_lines, labels):
            self.play(
                Write(label),
                ShowCreation(line)
            )
        self.wait()

        self.graph = graph
        self.graph_label = graph_label
        self.bounds_labels = labels
        self.bound_lines = v_lines

    def ask_about_average(self):
        v_line = self.get_vertical_line_to_graph(
            self.bounds[0], self.graph, 
            line_class = DashedLine,
            color = WHITE
        )
        average = TextMobject("Average = ")
        fraction = TexMobject(
            "{\\displaystyle \\int", "^b", "_a", "f(x)", "\\,dx",
            "\\over", "b", "-", "a}"
        )
        for color, tex in zip(self.bound_colors, "ab"):
            fraction.set_color_by_tex(tex, color)
        fraction.set_color_by_tex("displaystyle", WHITE)
        integral = VGroup(*fraction[:5])
        denominator = VGroup(*fraction[5:])
        average.next_to(fraction.get_part_by_tex("over"), LEFT)
        group = VGroup(average, fraction)
        group.center().to_edge(UP).shift(LEFT)

        self.count = 0
        def next_v_line_anim():
            target = self.bounds[0] if self.count%2 == 1 else self.bounds[1]
            self.count += 1
            return self.get_v_line_change_anim(
                v_line, self.graph, target,
                run_time = 4,
            )

        self.play(
            next_v_line_anim(),
            Write(average, run_time = 2),
        )
        self.play(
            next_v_line_anim(),
            Write(
                VGroup(*[
                    fraction.get_part_by_tex(tex)
                    for tex in ("int", "f(x)", "dx", "over")
                ]),
                rate_func = squish_rate_func(smooth, 0.25, 0.75),
                run_time = 4
            ),
            *[
                ReplacementTransform(
                    label.copy(),
                    fraction.get_part_by_tex(tex, substring = False),
                    run_time = 2
                )
                for label, tex in zip(
                    self.bounds_labels, 
                    ["_a", "^b"]
                )
            ]
        )
        self.play(
            next_v_line_anim(),
            Write(
                fraction.get_part_by_tex("-"),
                run_time = 4,
                rate_func = squish_rate_func(smooth, 0.5, 0.75),
            ),
            *[
                ReplacementTransform(
                    label.copy(),
                    fraction.get_part_by_tex(tex, substring = False),
                    run_time = 4,
                    rate_func = squish_rate_func(smooth, 0.25, 0.75)
                )
                for label, tex in zip(
                    self.bounds_labels, 
                    ["a}", "b"]
                )
            ]

        )
        self.play(next_v_line_anim())
        self.play(FadeOut(v_line))

        self.average_expression = VGroup(average, fraction)

    def show_signed_area(self):
        rect_list = self.get_riemann_rectangles_list(
            self.graph,
            self.num_rect_iterations,
            max_dx = self.max_dx,
            x_min = self.bounds[0],
            x_max = self.bounds[1],
            end_color = BLUE,
            fill_opacity = 0.75,
            stroke_width = 0.25,
        )
        rects = rect_list[0]
        plus = TexMobject("+")
        plus.move_to(self.coords_to_point(2, 2))
        minus = TexMobject("-")
        minus.move_to(self.coords_to_point(5.24, -1))

        self.play(FadeIn(
            rects, 
            run_time = 2,
            lag_ratio = 0.5
        ))
        for new_rects in rect_list[1:]:
            self.transform_between_riemann_rects(rects, new_rects)
        self.play(Write(plus))
        self.play(Write(minus))
        self.wait(2)

        self.area = VGroup(rects, plus, minus)

    def put_area_away(self):
        self.play(
            FadeOut(self.area),
            self.average_expression.scale, 0.75,
            self.average_expression.to_corner, DOWN+RIGHT,
        )
        self.wait()

    def show_finite_sample(self):
        v_lines = self.get_vertical_lines_to_graph(
            self.graph,
            x_min = self.bounds[0],
            x_max = self.bounds[1],
            color = GREEN
        )
        for line in v_lines:
            if self.y_axis.point_to_number(line.get_end()) < 0:
                line.set_color(RED)
            line.save_state()

        line_pair = VGroup(*v_lines[6:8])
        brace = Brace(line_pair, DOWN)
        dx = brace.get_text("$dx$")

        num_samples = TextMobject("Num. samples")
        approx = TexMobject("\\approx")
        rhs = TexMobject("{b", "-", "a", "\\over", "dx}")
        for tex, color in zip("ab", self.bound_colors):
            rhs.set_color_by_tex(tex, color)
        expression = VGroup(num_samples, approx, rhs)
        expression.arrange(RIGHT)
        expression.next_to(self.y_axis, RIGHT)
        rhs_copy = rhs.copy()

        f_brace = Brace(line_pair, LEFT, buff = 0)
        f_x = f_brace.get_text("$f(x)$")
        add_up_f_over = TexMobject("\\text{Add up $f(x)$}", "\\over")
        add_up_f_over.next_to(num_samples, UP)
        add_up_f_over.to_edge(UP)


        self.play(ShowCreation(v_lines, run_time = 2))
        self.play(*list(map(Write, [brace, dx])))
        self.wait()
        self.play(Write(VGroup(num_samples, approx, *rhs[:-1])))
        self.play(ReplacementTransform(
            dx.copy(), rhs.get_part_by_tex("dx")
        ))
        self.wait(2)

        self.play(
            FadeIn(add_up_f_over),
            *[
                ApplyFunction(
                    lambda m : m.fade().set_stroke(width = 2),
                    v_line
                )
                for v_line in v_lines
            ]
        )
        self.play(*[
            ApplyFunction(
                lambda m : m.restore().set_stroke(width = 5),
                v_line,
                run_time = 3,
                rate_func = squish_rate_func(
                    there_and_back, a, a+0.2
                )
            )
            for v_line, a in zip(v_lines, np.linspace(0, 0.8, len(v_lines)))
        ])
        self.play(*[vl.restore for vl in v_lines])
        self.play(rhs_copy.next_to, add_up_f_over, DOWN)
        self.wait(2)
        frac_line = add_up_f_over[1]
        self.play(
            FadeOut(rhs_copy.get_part_by_tex("over")),
            rhs_copy.get_part_by_tex("dx").next_to,
                add_up_f_over[0], RIGHT, SMALL_BUFF,
            frac_line.scale_about_point, 1.2, frac_line.get_left(),
            frac_line.stretch_to_fit_height, frac_line.get_height(),
        )
        rhs_copy.remove(rhs_copy.get_part_by_tex("over"))
        self.wait(2)

        int_fraction = self.average_expression[1].copy()
        int_fraction.generate_target()
        int_fraction.target.next_to(add_up_f_over, RIGHT, LARGE_BUFF)
        int_fraction.target.shift_onto_screen()
        double_arrow = TexMobject("\\Leftrightarrow")
        double_arrow.next_to(
            int_fraction.target.get_part_by_tex("over"), LEFT
        )

        self.play(
            MoveToTarget(int_fraction),
            VGroup(add_up_f_over, rhs_copy).shift, 0.4*DOWN
        )
        self.play(Write(double_arrow))
        self.play(*list(map(FadeOut, [
            dx, brace, num_samples, approx, rhs
        ])))
        self.wait()

        self.v_lines = v_lines

    def show_improving_samples(self):
        stroke_width = self.v_lines[0].get_stroke_width()
        new_v_lines_list = [
            self.get_vertical_lines_to_graph(
                self.graph,
                x_min = self.bounds[0],
                x_max = self.bounds[1],
                num_lines = len(self.v_lines)*(2**n),
                color = GREEN,
                stroke_width = float(stroke_width)/n
            )
            for n in range(1, 4)
        ]
        for new_v_lines in new_v_lines_list:
            for line in new_v_lines:
                if self.y_axis.point_to_number(line.get_end()) < 0:
                    line.set_color(RED)

        for new_v_lines in new_v_lines_list:
            self.play(Transform(
                self.v_lines, new_v_lines,
                run_time = 2,
                lag_ratio = 0.5
            ))
        self.wait()

    ####

    def func(self, x):
        return 0.09*(x+1)*(x-4)*(x-8)

class GeneralAntiderivative(GeneralAverage):
    def construct(self):
        self.force_skipping()
        self.setup_axes()
        self.add_graph()
        self.revert_to_original_skipping_status()

        self.fade_existing_graph()
        self.add_fraction()
        self.add_antiderivative_graph()
        self.show_average_in_terms_of_F()
        self.draw_slope()
        self.show_tangent_line_slopes()

    def fade_existing_graph(self):
        self.graph.fade(0.3)
        self.graph_label.fade(0.3)
        self.bound_lines.fade(0.3)

    def add_fraction(self):
        fraction = TexMobject(
            "{\\displaystyle \\int", "^b", "_a", "f(x)", "\\,dx",
            "\\over", "b", "-", "a}"
        )
        for tex, color in zip("ab", self.bound_colors):
            fraction.set_color_by_tex(tex, color)
        fraction.set_color_by_tex("display", WHITE)

        fraction.scale(0.8)
        fraction.next_to(self.y_axis, RIGHT)
        fraction.to_edge(UP, buff = MED_SMALL_BUFF)
        self.add(fraction)

        self.fraction = fraction

    def add_antiderivative_graph(self):
        x_max = 9.7
        antideriv_graph = self.get_graph(
            lambda x : scipy.integrate.quad(
                self.graph.underlying_function,
                1, x
            )[0],
            color = YELLOW,
            x_max = x_max,
        )
        antideriv_graph_label = self.get_graph_label(
            antideriv_graph, "F(x)",
            x_val = x_max
        )

        deriv = TexMobject(
            "{dF", "\\over", "dx}", "(x)", "=", "f(x)"
        )
        deriv.set_color_by_tex("dF", antideriv_graph.get_color())
        deriv.set_color_by_tex("f(x)", BLUE)
        deriv.next_to(
            antideriv_graph_label, DOWN, MED_LARGE_BUFF, LEFT
        )

        self.play(
            ShowCreation(antideriv_graph),
            Write(
                antideriv_graph_label, 
                rate_func = squish_rate_func(smooth, 0.5, 1)
            ),
            run_time = 2,
        )
        self.wait()
        self.play(Write(deriv))
        self.wait()

        self.antideriv_graph = antideriv_graph

    def show_average_in_terms_of_F(self):
        new_fraction = TexMobject(
            "=", 
            "{F", "(", "b", ")", "-", "F", "(", "a", ")",
            "\\over",
            "b", "-", "a}"
        )
        for tex, color in zip("abF", self.bound_colors+[YELLOW]):
            new_fraction.set_color_by_tex(tex, color)
        new_fraction.next_to(
            self.fraction.get_part_by_tex("over"), RIGHT,
            align_using_submobjects = True
        )   

        to_write = VGroup(*it.chain(*[
            new_fraction.get_parts_by_tex(tex)
            for tex in "=F()-"
        ]))
        to_write.remove(new_fraction.get_parts_by_tex("-")[-1])

        numerator = VGroup(*new_fraction[1:10])
        denominator = VGroup(*new_fraction[-3:])

        self.play(Write(to_write))
        self.play(*[
            ReplacementTransform(
                label.copy(),
                new_fraction.get_part_by_tex(tex),
                run_time = 2,
                rate_func = squish_rate_func(smooth, a, a+0.7)
            )
            for label, tex, a in zip(
                self.bounds_labels, "ab", [0, 0.3]
            )
        ])
        self.wait()

        self.show_change_in_height(numerator.copy())
        self.shift_antideriv_graph_up_and_down()
        self.play(Write(VGroup(*new_fraction[-4:])))
        self.wait()

        h_line_brace = Brace(self.h_line, DOWN)
        denominator_copy = denominator.copy()
        a_label = self.bounds_labels[0]
        self.play(
            GrowFromCenter(h_line_brace),
            a_label.shift, 0.7*a_label.get_width()*LEFT,
            a_label.shift, 2.2*a_label.get_height()*UP,
        )
        self.play(
            denominator_copy.next_to, h_line_brace, 
            DOWN, SMALL_BUFF
        )
        self.wait(3)

    def show_change_in_height(self, numerator):
        numerator.add_to_back(BackgroundRectangle(numerator))
        a_point, b_point = points = [
            self.input_to_graph_point(x, self.antideriv_graph)
            for x in self.bounds
        ]
        interim_point = b_point[0]*RIGHT + a_point[1]*UP

        v_line = Line(b_point, interim_point)
        h_line = Line(interim_point, a_point)
        VGroup(v_line, h_line).set_color(WHITE)
        brace = Brace(v_line, RIGHT, buff = SMALL_BUFF)

        graph_within_bounds = self.get_graph(
            self.antideriv_graph.underlying_function,
            x_min = self.bounds[0], 
            x_max = self.bounds[1],
            color = self.antideriv_graph.get_color()
        )

        b_label = self.bounds_labels[1]
        self.play(
            self.antideriv_graph.fade, 0.7,
            Animation(graph_within_bounds)
        )
        self.play(
            ShowCreation(v_line),
            b_label.shift, b_label.get_width()*RIGHT,
            b_label.shift, 1.75*b_label.get_height()*DOWN,
        )
        self.play(ShowCreation(h_line))
        self.play(
            numerator.scale, 0.75,
            numerator.next_to, brace.copy(), RIGHT, SMALL_BUFF,
            GrowFromCenter(brace),
        )
        self.wait(2)

        self.antideriv_graph.add(
            graph_within_bounds, v_line, h_line, numerator, brace
        )
        self.h_line = h_line
        self.graph_points_at_bounds = points

    def shift_antideriv_graph_up_and_down(self):
        for vect in 2*UP, 3*DOWN, UP:
            self.play(
                self.antideriv_graph.shift, vect,
                run_time = 2
            )
        self.wait()

    def draw_slope(self):
        line = Line(*self.graph_points_at_bounds)
        line.set_color(PINK)
        line.scale_in_place(1.3)

        self.play(ShowCreation(line, run_time = 2))
        self.wait()

    def show_tangent_line_slopes(self):
        ss_group = self.get_secant_slope_group(
            x = self.bounds[0],
            graph = self.antideriv_graph,
            dx = 0.001,
            secant_line_color = BLUE,
            secant_line_length = 2,
        )

        self.play(*list(map(ShowCreation, ss_group)))
        for x in range(2):
            for bound in reversed(self.bounds):
                self.animate_secant_slope_group_change(
                    ss_group,
                    target_x = bound,
                    run_time = 5,
                )

class LastVideoWrapper(Scene):
    def construct(self):
        title = TextMobject("Chapter 8: Integrals")
        title.to_edge(UP)
        rect = Rectangle(height = 9, width = 16)
        rect.set_stroke(WHITE)
        rect.set_height(1.5*FRAME_Y_RADIUS)
        rect.next_to(title, DOWN)

        self.play(Write(title), ShowCreation(rect))
        self.wait(5)

class ASecondIntegralSensation(TeacherStudentsScene):
    def construct(self):
        finite_average = TexMobject("{1+5+4+2 \\over 4}")
        numbers = VGroup(*finite_average[0:7:2])
        plusses = VGroup(*finite_average[1:7:2])
        denominator = VGroup(*finite_average[7:])
        finite_average.to_corner(UP+LEFT)
        finite_average.to_edge(LEFT)

        continuum = UnitInterval(
            color = GREY,
            unit_size = 6
        )
        continuum.next_to(finite_average, RIGHT, 2)
        line = Line(continuum.get_left(), continuum.get_right())
        line.set_color(YELLOW)
        arrow = Arrow(DOWN+RIGHT, ORIGIN)
        arrow.next_to(line.get_start(), DOWN+RIGHT, SMALL_BUFF)

        sigma_to_integral = TexMobject(
            "\\sum \\Leftrightarrow \\int"
        )
        sigma_to_integral.next_to(
            self.teacher.get_corner(UP+LEFT), UP, MED_LARGE_BUFF
        )

        self.teacher_says(
            "A second integral \\\\ sensation"
        )
        self.change_student_modes(*["erm"]*3)
        self.wait()
        self.play(
            Write(numbers),
            RemovePiCreatureBubble(self.teacher),
        )
        self.change_student_modes(
            *["pondering"]*3,
            look_at_arg = numbers
        )
        self.play(Write(plusses))
        self.wait()
        self.play(Write(denominator))
        self.wait()

        self.change_student_modes(
            *["confused"]*3,
            look_at_arg = continuum,
            added_anims = [Write(continuum, run_time = 2)]
        )
        self.play(ShowCreation(arrow))
        arrow.save_state()
        self.play(
            arrow.next_to, line.copy().get_end(), DOWN+RIGHT, SMALL_BUFF,
            ShowCreation(line),
            run_time = 2
        )
        self.play(*list(map(FadeOut, [arrow])))
        self.wait(2)
        self.change_student_modes(
            *["pondering"]*3,
            look_at_arg = sigma_to_integral,
            added_anims = [
                Write(sigma_to_integral),
                self.teacher.change_mode, "raise_right_hand"
            ]
        )
        self.wait(3)

class Chapter9PatreonThanks(PatreonThanks):
    CONFIG = {
        "specific_patrons" : [
            "Ali Yahya",
            "CrypticSwarm",
            "Kaustuv DeBiswas",
            "Kathryn Schmiedicke",
            "Karan Bhargava",
            "Ankit Agarwal",
            "Yu Jun",
            "Dave Nicponski",
            "Damion Kistler",
            "Juan Benet",
            "Othman Alikhan",
            "Markus Persson",
            "Dan Buchoff",
            "Derek Dai",
            "Joseph John Cox",
            "Luc Ritchie",
            "Zac Wentzell",
            "Robert Teed",
            "Jason Hise",
            "Meshal Alshammari",
            "Bernd Sing",
            "Nils Schneider",
            "James Thornton",
            "Mustafa Mahdi",
            "Jonathan Eppele",
            "Mathew Bramson",
            "Jerry Ling",
            "Mark Govea",
            "Vecht",
            "Shimin Kuang",
            "Rish Kundalia",
            "Achille Brighton",
            "Ripta Pasay",
        ],
    }

class Thumbnail(GraphScene):
    CONFIG = {
        "x_min" : -0.2,
        "x_max" : 3.5,
        "x_leftmost_tick" : 0,
        "x_tick_frequency" : np.pi/4,
        "x_axis_label" : "",
        "y_min" : -0.75,
        "y_max" : 0.75,
        "y_axis_height" : 4.5,
        "y_tick_frequency" : 0.25,
        "y_axis_label" : ""
    }
    def construct(self):
        self.setup_axes()
        self.remove(self.axes)

        sine = self.get_graph(np.sin)
        rects = self.get_riemann_rectangles(
            sine,
            x_min = 0,
            x_max = np.pi,
            dx = 0.01,
            stroke_width = 0,
        )
        sine.add_to_back(rects)
        sine.add(self.axes.copy())
        sine.to_corner(UP+LEFT, buff = SMALL_BUFF)
        sine.scale(0.9)

        area = TextMobject("Area")
        area.scale(3)
        area.move_to(rects)

        cosine = self.get_graph(lambda x : -np.cos(x))
        cosine.set_stroke(GREEN, 8)
        line = self.get_secant_slope_group(
            0.75*np.pi, cosine, 
            dx = 0.01
        ).secant_line
        line.set_stroke(PINK, 7)
        cosine.add(line)
        cosine.add(self.axes.copy())
        cosine.scale(0.7)
        cosine.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF)

        slope = TextMobject("Slope")
        slope.scale(3)
        # slope.next_to(cosine, LEFT, buff = 0)
        # slope.to_edge(DOWN)
        slope.to_corner(DOWN+RIGHT)

        double_arrow = DoubleArrow(
            area.get_bottom(),
            slope.get_left(),
            color = YELLOW,
            tip_length = 0.75,
            buff = MED_LARGE_BUFF
        )
        double_arrow.set_stroke(width = 18)

        triangle = Polygon(
            ORIGIN, UP, UP+RIGHT,
            stroke_width = 0,
            fill_color = BLUE_E,
            fill_opacity = 0.5,
        )
        triangle.stretch_to_fit_width(FRAME_WIDTH)
        triangle.stretch_to_fit_height(FRAME_HEIGHT)
        triangle.to_corner(UP+LEFT, buff = 0)

        alt_triangle = triangle.copy()
        alt_triangle.rotate(np.pi)
        alt_triangle.set_fill(BLACK, 1)

        self.add(
            triangle, sine, area, 
            alt_triangle, cosine, slope, 
            double_arrow,
        )







































