from big_ol_pile_of_manim_imports import *
from active_projects.eop.reusable_imports import *


class BrickRowScene(PiCreatureScene):


    def split_tallies(self, row, direction = DOWN):
        # Split all tally symbols at once and move the copies
        # either horizontally on top of the brick row
        # or diagonally into the bricks

        self.tallies_copy = self.tallies.copy()
        self.add_foreground_mobject(self.tallies_copy)

        tally_targets_left = [
            rect.get_center() + 0.25 * rect.get_width() * LEFT 
            for rect in row.rects
        ]

        tally_targets_right = [
            rect.get_center() + 0.25 * rect.get_width() * RIGHT 
            for rect in row.rects
        ]

        if np.all(direction == LEFT) or np.all(direction == RIGHT):

            tally_y_pos = self.tallies[0].anchor[1]
            for target in tally_targets_left:
                target[1] = tally_y_pos
            for target in tally_targets_right:
                target[1] = tally_y_pos

        for (i, tally) in enumerate(self.tallies):

            target_left = tally_targets_left[i]
            new_tally_left = TallyStack(tally.nb_heads + 1, tally.nb_tails)
            new_tally_left.move_anchor_to(target_left)
            v = target_left - tally.anchor
            
            self.play(
                tally.move_anchor_to, target_left,
            )
            tally.anchor = target_left
            self.play(Transform(tally, new_tally_left))
            
            tally_copy = self.tallies_copy[i]

            target_right = tally_targets_right[i]
            new_tally_right = TallyStack(tally.nb_heads, tally.nb_tails + 1)
            new_tally_right.move_anchor_to(target_right)
            v = target_right - tally_copy.anchor
            
            self.play(tally_copy.move_anchor_to, target_right)
            tally_copy.anchor = target_right
            self.play(Transform(tally_copy, new_tally_right))

            tally_copy.nb_heads = new_tally_right.nb_heads
            tally_copy.nb_tails = new_tally_right.nb_tails
            tally.nb_heads = new_tally_left.nb_heads
            tally.nb_tails = new_tally_left.nb_tails




    def tally_split_animations(self, row, direction = DOWN):
        # Just creates the animations and returns them
        # Execution can be timed afterwards
        # Returns two lists: first all those going left, then those to the right

        self.tallies_copy = self.tallies.copy()
        self.add_foreground_mobject(self.tallies_copy)

        tally_targets_left = [
            rect.get_center() + 0.25 * rect.get_width() * LEFT 
            for rect in row.rects
        ]

        tally_targets_right = [
            rect.get_center() + 0.25 * rect.get_width() * RIGHT 
            for rect in row.rects
        ]

        if np.all(direction == LEFT) or np.all(direction == RIGHT):

            tally_y_pos = self.tallies[0].anchor[1]
            for target in tally_targets_left:
                target[1] = tally_y_pos
            for target in tally_targets_right:
                target[1] = tally_y_pos


        anims1 = []

        for (i, tally) in enumerate(self.tallies):

            new_tally_left = TallyStack(tally.nb_heads + 1, tally.nb_tails)
            target_left = tally_targets_left[i]
            new_tally_left.move_to(target_left)

            anims1.append(Transform(tally, new_tally_left))
            
            tally.anchor = target_left
            tally.nb_heads = new_tally_left.nb_heads
            tally.nb_tails = new_tally_left.nb_tails
            

        anims2 = []

        for (i, tally) in enumerate(self.tallies_copy):

            new_tally_right = TallyStack(tally.nb_heads, tally.nb_tails + 1)
            target_right = tally_targets_right[i]
            new_tally_right.move_to(target_right)
            
            anims2.append(Transform(tally, new_tally_right))

            tally.anchor = target_right
            tally.nb_heads = new_tally_right.nb_heads
            tally.nb_tails = new_tally_right.nb_tails

        return anims1, anims2



    def split_tallies_at_once(self, row, direction = DOWN):
        anims1, anims2 = self.tally_split_animations(row, direction = direction)
        self.play(*(anims1 + anims2))

    def split_tallies_in_two_steps(self, row, direction = DOWN):
        # First all those to the left, then those to the right
        anims1, anims2 = self.tally_split_animations(row, direction = direction)
        self.play(*anims1)
        self.wait(0.3)
        self.play(*anims2)




    def merge_rects_by_subdiv(self, row):

        half_merged_row = row.copy()
        half_merged_row.subdiv_level += 1
        half_merged_row.generate_points()
        half_merged_row.move_to(row)

        self.play(FadeIn(half_merged_row))
        self.remove(row)
        return half_merged_row




    def merge_tallies(self, row, target_pos = UP):
        
        r = row.subdiv_level
        
        if np.all(target_pos == DOWN):
            tally_targets = [
                rect.get_center()
                for rect in row.get_rects_for_level(r)
            ]
        elif np.all(target_pos == UP):
            y_pos = row.get_center()[1] + 1.2 * 0.5 * row.get_height()
            for target in tally_targets:
                target[1] = y_pos
        else:
            raise Exception("Invalid target position (either UP or DOWN)")

        anims = []
        for (tally, target) in zip(self.tallies[1:], tally_targets[1:-1]):
            anims.append(tally.move_anchor_to)
            anims.append(target)

        for (tally, target) in zip(self.tallies_copy[:-1], tally_targets[1:-1]):
            anims.append(tally.move_anchor_to)
            anims.append(target)

        self.play(*anims)
        # update anchors
        for (tally, target) in zip(self.tallies[1:], tally_targets[1:-1]):
            tally.anchor = target
        for (tally, target) in zip(self.tallies_copy[:-1], tally_targets[1:-1]):
            tally.anchor = target

        self.remove(self.tallies_copy)
        self.tallies.add(self.tallies_copy[-1])


    def merge_rects_by_coloring(self, row):

        merged_row = row.copy()
        merged_row.coloring_level += 1
        merged_row.generate_points()
        merged_row.move_to(row)

        self.play(FadeIn(merged_row))
        self.remove(row)
        return merged_row



    def move_tallies_on_top(self, row):
        self.play(
            self.tallies.shift, 1.2 * 0.5 * row.height * UP
        )
        for tally in self.tallies:
            tally.anchor += 1.2 * 0.5 * row.height * UP

    def create_pi_creature(self):
        randy = CoinFlippingPiCreature(color = MAROON_E)
        return randy





        

    def construct(self):
        self.force_skipping()
        
        randy = self.get_primary_pi_creature()
        randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT)
        #self.add(randy)
        self.row = BrickRow(0, height = 2, width = 10)
        self.wait()

        self.play(FadeIn(self.row))

        self.wait()


        # move in all kinds of sequences
        coin_seqs = VGroup()
        for i in range(20):
            n = np.random.randint(1,10)
            seq = [np.random.choice(["H", "T"]) for j in range(n)]
            coin_seq = CoinSequence(seq).scale(1.5)
            loc = np.random.uniform(low = -10, high = 10) * RIGHT
            loc += np.random.uniform(low = -6, high = 6) * UP
            coin_seq.move_to(loc)
            
            coin_seq.target = coin_seq.copy().scale(0.3).move_to(0.4 * loc)
            coin_seq.target.fade(1)
            coin_seqs.add(coin_seq)

        self.play(
            LaggedStartMap(
                Succession, coin_seqs, lambda m: (FadeIn(m, run_time = 0.1), MoveToTarget(m)),
                run_time = 5,
                lag_ratio = 0.5
            )
        )
        
        # # # # # # # #
        # FIRST  FLIP #
        # # # # # # # #


        self.play(FlipCoin(randy))

        self.play(SplitRectsInBrickWall(self.row))
        self.row = self.merge_rects_by_subdiv(self.row)
        self.row = self.merge_rects_by_coloring(self.row)
        #
        # put tallies on top

        single_flip_labels = VGroup(UprightHeads(), UprightTails())
        for (label, rect) in zip(single_flip_labels, self.row.rects):
            label.next_to(rect, UP)
            self.play(FadeIn(label))
            self.wait()

        self.wait()


        # # # # # # # #
        # SECOND FLIP #
        # # # # # # # #

        self.play(FlipCoin(randy))
        self.wait()

        self.play(
            SplitRectsInBrickWall(self.row)
        )
        self.wait()


        # split sequences
        single_flip_labels_copy = single_flip_labels.copy()
        self.add(single_flip_labels_copy)

        
        v = self.row.get_outcome_centers_for_level(2)[0] - single_flip_labels[0].get_center()
        self.play(
            single_flip_labels.shift, v
        )
        new_heads = VGroup(UprightHeads(), UprightHeads())
        for i in range(2):
            new_heads[i].move_to(single_flip_labels[i])
            new_heads[i].shift(COIN_SEQUENCE_SPACING * DOWN)
        self.play(FadeIn(new_heads))

        v = self.row.get_outcome_centers_for_level(2)[-1] - single_flip_labels_copy[-1].get_center()
        self.play(
            single_flip_labels_copy.shift, v
        )
        new_tails = VGroup(UprightTails(), UprightTails())
        for i in range(2):
            new_tails[i].move_to(single_flip_labels_copy[i])
            new_tails[i].shift(COIN_SEQUENCE_SPACING * DOWN)
        self.play(FadeIn(new_tails))

        self.add_foreground_mobject(single_flip_labels)
        self.add_foreground_mobject(new_heads)
        self.add_foreground_mobject(single_flip_labels_copy)
        self.add_foreground_mobject(new_tails)

        # get individual outcomes
        outcomes = self.row.get_outcome_rects_for_level(2, with_labels = False,
            inset = True)
        grouped_outcomes = VGroup(outcomes[0], outcomes[1:3], outcomes[3])

        decimal_tallies = VGroup()
        # introduce notion of tallies
        rects = self.row.get_rects_for_level(2)

        rect = rects[0]
        tally = DecimalTally(2,0)
        tally.next_to(rect, UP)
        decimal_tallies.add(tally)
        self.play(
            FadeIn(tally),
            FadeIn(grouped_outcomes[0])
        )
        self.wait()

        rect = rects[1]
        tally = DecimalTally(1,1)
        tally.next_to(rect, UP)
        decimal_tallies.add(tally)
        self.play(
            FadeIn(tally),
            FadeOut(grouped_outcomes[0]),
            FadeIn(grouped_outcomes[1])
        )
        self.wait()

        rect = rects[2]
        tally = DecimalTally(0,2)
        tally.next_to(rect, UP)
        decimal_tallies.add(tally)
        self.play(
            FadeIn(tally),
            FadeOut(grouped_outcomes[1]),
            FadeIn(grouped_outcomes[2])
        )
        self.wait()

        self.play(
            FadeOut(grouped_outcomes[2])
        )
        self.wait()

        self.wait()
        self.play(
            FadeOut(single_flip_labels),
            FadeOut(new_heads),
            FadeOut(single_flip_labels_copy),
            FadeOut(new_tails)
        )
        self.wait()

        self.tallies = VGroup()
        for (i, rect) in enumerate(self.row.get_rects_for_level(2)):
            tally = TallyStack(2-i, i, show_decimals = False)
            tally.move_to(rect)
            self.tallies.add(tally)


        self.play(FadeIn(self.tallies))
        self.wait()


        anims = []
        for (decimal_tally, tally_stack) in zip(decimal_tallies, self.tallies):
            anims.append(ApplyFunction(
                tally_stack.position_decimal_tally, decimal_tally
            ))
        
        self.play(*anims)
        self.wait()


        # replace the original decimal tallies with
        # the ones that belong to the TallyStacks
        for (decimal_tally, tally_stack) in zip(decimal_tallies, self.tallies):
            self.remove(decimal_tally)
            tally_stack.position_decimal_tally(tally_stack.decimal_tally)
            tally_stack.add(tally_stack.decimal_tally)
        
        self.add_foreground_mobject(self.tallies)
        self.row = self.merge_rects_by_subdiv(self.row)
        self.wait()
        self.row = self.merge_rects_by_coloring(self.row)
        self.wait()
        

        # # # # # # # # # # # # #
        # CALLBACK TO SEQUENCES #
        # # # # # # # # # # # # #

        outcomes = self.row.get_outcome_rects_for_level(2, with_labels = True,
            inset = True)
        subdivs = self.row.get_sequence_subdivs_for_level(2)
        self.play(
            FadeIn(outcomes),
            FadeIn(subdivs),
            FadeOut(self.tallies)
        )

        self.wait()

        rect_to_dice = self.row.get_outcome_rects_for_level(2, with_labels = False,
            inset = False)[1]
        N = 10
        dice_width = rect_to_dice.get_width()/N
        dice_height = rect_to_dice.get_height()/N
        prototype_dice = Rectangle(
            width = dice_width,
            height = dice_height,
            stroke_width = 2,
            stroke_color = WHITE,
            fill_color = WHITE,
            fill_opacity = 0
        )
        prototype_dice.align_to(rect_to_dice, direction = UP)
        prototype_dice.align_to(rect_to_dice, direction = LEFT)
        all_dice = VGroup()
        for i in range(N):
            for j in range(N):
                dice_copy = prototype_dice.copy()
                dice_copy.shift(j * dice_width * RIGHT + i * dice_height * DOWN)
                all_dice.add(dice_copy)

        self.play(
            LaggedStartMap(FadeIn, all_dice),
            FadeOut(outcomes[1])
        )
        self.wait()

        table = Ellipse(width = 1.5, height = 1)
        table.set_fill(color = GREEN_E, opacity = 1)
        table.next_to(rect_to_dice, UP)
        self.add(table)
        coin1 = UprightHeads(radius = 0.1)
        coin2 = UprightTails(radius = 0.1)

        def get_random_point_in_ellipse(ellipse):
            width = ellipse.get_width()
            height = ellipse.get_height()
            x = y = 1
            while x**2 + y**2 > 0.9:
                x = np.random.uniform(-1,1)
                y = np.random.uniform(-1,1)            
            x *= width/2
            y *= height/2
            return ellipse.get_center() + x * RIGHT + y * UP


        for dice in all_dice:
            p1 = get_random_point_in_ellipse(table)
            p2 = get_random_point_in_ellipse(table)
            coin1.move_to(p1)
            coin2.move_to(p2)
            self.add(coin1, coin2)
            self.play(
                ApplyMethod(dice.set_fill, {"opacity" : 0.5},
                    rate_func = there_and_back,
                    run_time = 0.05)
            )

        self.wait()

        self.play(
            FadeOut(outcomes),
            FadeOut(subdivs),
            FadeOut(all_dice),
            FadeOut(table),
            FadeOut(coin1),
            FadeOut(coin2),
            FadeIn(self.tallies)
        )

        self.wait()

        # # # # # # # #
        # THIRD  FLIP #
        # # # # # # # #

        # move row up, leave a copy without tallies below
        new_row = self.row.copy()
        self.clear()
        self.add(randy, self.row, self.tallies)
        self.bring_to_back(new_row)
        self.play(
            self.row.shift, 2.5 * UP,
            self.tallies.shift, 2.5 * UP,
        )

        old_row = self.row
        self.row = new_row

        self.play(FlipCoin(randy))

        self.wait()

        self.play(
            SplitRectsInBrickWall(self.row)
        )
        self.wait()

        self.split_tallies_in_two_steps(self.row)
        self.wait()

        self.add_foreground_mobject(self.tallies)
        self.add_foreground_mobject(self.tallies_copy)


        

        self.remove(new_row)
        new_row = self.row



        self.clear()
        self.add(randy, self.row, old_row)
        self.add_foreground_mobject(self.tallies)
        self.add_foreground_mobject(self.tallies_copy)
        

        self.play(
            self.row.fade, 0.7,
            old_row.fade, 0.7,
            FadeOut(self.tallies),
            FadeOut(self.tallies_copy),
        )

        
        # # # # # # # # # # # # # # # # #
        # SHOW SPLITTING  WITH OUTCOMES #
        # # # # # # # # # # # # # # # # #

        # # show individual outcomes
        # old_outcomes = old_row.get_outcome_rects_for_level(2, with_labels = True)
        # old_outcomes_copy = old_outcomes.copy()
        # new_outcomes = self.row.get_outcome_rects_for_level(3, with_labels = True)
        
        # self.play(
        #     FadeIn(old_outcomes[0]),
        #     FadeIn(old_outcomes_copy[0]),
        # )

        # self.wait()

        # self.play(
        #     Transform(old_outcomes_copy[0], new_outcomes[1])
        # )

        # self.wait()

        # self.play(
        #     FadeIn(old_outcomes[1:3]),
        #     FadeIn(old_outcomes_copy[1:3]),
        # )

        # self.wait()

        # self.play(
        #     Transform(old_outcomes_copy[1:3], new_outcomes[2:4])
        # )

        # self.wait()

        # self.row = self.merge_rects_by_subdiv(self.row)
        # self.wait()

        # self.play(
        #     FadeOut(old_row),
        #     FadeOut(old_outcomes[0:3]),
        #     FadeOut(new_outcomes[1:4]),
        #     self.row.fade,0,
        #     FadeIn(self.tallies[1]),
        #     FadeIn(self.tallies_copy[0]),
        # )

        # # rest of the new row
        # self.play(
        #     FadeIn(self.tallies[:1]),
        #     FadeIn(self.tallies[2:]),
        #     FadeIn(self.tallies_copy[1:])
        # )

        # self.wait()

        # self.merge_tallies(self.row, target_pos = DOWN)
        # self.add_foreground_mobject(self.tallies)
        # self.row = self.merge_rects_by_coloring(self.row)
        # self.wait()


        # # # # # # # # # # # # # # # #
        # SHOW SPLITTING WITH TALLIES #
        # # # # # # # # # # # # # # # #

        tally_left = TallyStack(2,0)
        tally_left.move_to(old_row.rects[0])
        tally_right = TallyStack(1,1)
        tally_right.move_to(old_row.rects[1])

        rect_left = old_row.rects[0].copy()
        rect_right = old_row.rects[1].copy()

        self.play(
            FadeIn(rect_left),
            FadeIn(rect_right),
            FadeIn(tally_left),
            FadeIn(tally_right)
        )

        rect_left.target = rect_left.copy()
        rect_left.target.stretch(0.5,0)
        left_target_pos = self.row.get_outcome_centers_for_level(3)[1]
        left_v = left_target_pos - rect_left.get_center()
        rect_left.target.move_to(left_target_pos)

        rect_right.target = rect_right.copy()
        rect_right.target.stretch(0.5,0)
        right_target_pos = 0.5 * (self.row.get_outcome_centers_for_level(3)[2] + self.row.get_outcome_centers_for_level(3)[3])
        right_v = right_target_pos - rect_right.get_center()
        rect_right.target.move_to(right_target_pos)

        self.play(
            MoveToTarget(rect_left),
            tally_left.move_to, left_target_pos
        )
        #tally_left.anchor += left_v
        
        self.wait()

        new_tally_left = TallyStack(2,1)
        #new_tally_left.move_anchor_to(tally_left.anchor)
        new_tally_left.move_to(tally_left)
        
        self.play(
            Transform(tally_left, new_tally_left)
        )

        self.play(
            MoveToTarget(rect_right),
            tally_right.move_to, right_target_pos
        )
        #tally_right.anchor += right_v
        
        self.wait()
        new_tally_right = TallyStack(2,1)
        #new_tally_right.move_anchor_to(tally_right.anchor)
        new_tally_right.move_to(tally_right)

        self.play(
            Transform(tally_right, new_tally_right)
        )

        self.wait()

        self.row = self.merge_rects_by_subdiv(self.row)
        self.wait()

        self.play(
            FadeOut(old_row),
            self.row.fade,0,
            FadeOut(new_tally_left),
            FadeOut(new_tally_right),
            FadeIn(self.tallies[1]),
            FadeIn(self.tallies_copy[0]),
        )

        # rest of the new row
        self.play(
            FadeIn(self.tallies[:1]),
            FadeIn(self.tallies[2:]),
            FadeIn(self.tallies_copy[1:])
        )

        self.wait()

        self.merge_tallies(self.row, target_pos = DOWN)
        self.add_foreground_mobject(self.tallies)
        self.row = self.merge_rects_by_coloring(self.row)
        self.wait()

        
        # show the 8 individual outcomes
        outcomes = self.row.get_outcome_rects_for_level(3,
            with_labels = True,
            inset = True)
        self.play(FadeOut(self.tallies))
        self.wait()
        self.play(LaggedStartMap(
            FadeIn, outcomes,
            #rate_func = there_and_back_with_pause,
            run_time = 5))
        self.wait()

        braces = VGroup(*[Brace(rect, UP) for rect in self.row.rects])
        counts = [choose(3, i) for i in range(4)]
        probs = VGroup(*[TexMobject("{" + str(k) + "\over 8}") for k in counts])
        for (brace, prob) in zip(braces, probs):
            prob.next_to(brace, UP)


        self.play(
            LaggedStartMap(ShowCreation, braces),
            LaggedStartMap(Write, probs)
        )
        self.wait()
        self.play(LaggedStartMap(
            FadeOut, outcomes,
            #rate_func = there_and_back_with_pause,
            run_time = 5),
        )
        self.play(
            FadeIn(self.tallies)
        )
        self.wait()

        self.play(
            FadeOut(braces),
            FadeOut(probs)
        )
        self.wait()
 

        # put visuals for other probability distribtuions here

        # back to three coin flips, show all 8 outcomes
        run_time = 5
        self.play(
            LaggedStartMap(FadeIn, outcomes,
                #rate_func = there_and_back_with_pause,
                run_time = run_time),
            FadeOut(self.tallies,
                run_time = run_time)
        )
        self.wait()
        self.play(
            LaggedStartMap(FadeOut, outcomes,
                #rate_func = there_and_back_with_pause,
                run_time = 5),
            FadeIn(self.tallies,
                run_time = run_time)
        )




        # # # # # # # #
        # FOURTH FLIP #
        # # # # # # # #



        
        previous_row = self.row.copy()
        self.add(previous_row)

        v = 1.25 * self.row.height * UP
        self.play(
            previous_row.shift, v,
            self.tallies.shift, v,
        )
        self.add_foreground_mobject(self.tallies)

        self.play(
            SplitRectsInBrickWall(self.row)
        )
        self.wait()

        self.row = self.merge_rects_by_subdiv(self.row)
        

        self.revert_to_original_skipping_status()

        self.wait()

        n = 3 # level to split
        k = 1 # tally to split

        # show individual outcomes
        outcomes = previous_row.get_outcome_rects_for_level(n,
            with_labels = False,
            inset = True
        )
        grouped_outcomes = VGroup()
        index = 0
        for i in range(n + 1):
            size = choose(n,i)
            grouped_outcomes.add(VGroup(outcomes[index:index + size]))
            index += size


        grouped_outcomes_copy = grouped_outcomes.copy()

        original_grouped_outcomes = grouped_outcomes.copy()
        # for later reference

        self.play(
            LaggedStartMap(FadeIn, grouped_outcomes),
            LaggedStartMap(FadeIn, grouped_outcomes_copy),
        )
        self.wait()

        # show how the outcomes in one tally split into two copies
        # going into the neighboring tallies

        #self.revert_to_original_skipping_status()

        target_outcomes = self.row.get_outcome_rects_for_level(n + 1,
            with_labels = False,
            inset = True
        )
        grouped_target_outcomes = VGroup()
        index = 0
        old_tally_sizes = [choose(n,i) for i in range(n + 1)]
        new_tally_sizes = [choose(n + 1,i) for i in range(n + 2)]
                
        for i in range(n + 2):
            size = new_tally_sizes[i]
            grouped_target_outcomes.add(VGroup(target_outcomes[index:index + size]))
            index += size

        old_tally_sizes.append(0) # makes the edge cases work properly
        
        # split all tallies
        for i in range(n + 1):
            self.play(
                Transform(grouped_outcomes[i][0],
                    grouped_target_outcomes[i][0][old_tally_sizes[i - 1]:]
                ),
                Transform(grouped_outcomes_copy[i][0],
                    grouped_target_outcomes[i + 1][0][:old_tally_sizes[i]]
                )
            )
        return
        
        self.wait()

        # fade in new tallies
        new_rects = self.row.get_rects_for_level(4)
        new_tallies = VGroup(*[
            TallyStack(n + 1 - i, i).move_to(rect) for (i, rect) in enumerate(new_rects)
        ])
        self.play(FadeIn(new_tallies))
        self.add_foreground_mobject(new_tallies[1])
        # remove outcomes and sizes except for one tally
        anims = []
        for i in range(n + 1):
            if i != k - 1:
                anims.append(FadeOut(grouped_outcomes_copy[i]))
            if i != k:
                anims.append(FadeOut(grouped_outcomes[i]))
                anims.append(FadeOut(new_tallies[i]))

        #anims.append(FadeOut(self.tallies[0]))
        #anims.append(FadeOut(self.tallies[2:]))
        anims.append(FadeOut(new_tallies[-1]))

        self.play(*anims)

        self.wait()

        self.play(
            Transform(grouped_outcomes_copy[k - 1], original_grouped_outcomes[k - 1])
        )

        self.play(
            Transform(grouped_outcomes[k], original_grouped_outcomes[k])
        )

        new_rects = self.row.get_rects_for_level(n + 1)

        self.play(
            Transform(grouped_outcomes[k][0],grouped_target_outcomes[k][0][old_tally_sizes[k - 1]:]),
            Transform(grouped_outcomes_copy[k - 1][0],grouped_target_outcomes[k][0][:old_tally_sizes[k - 1]]),
        )

        self.play(
            FadeOut(previous_row),
            FadeOut(self.tallies),
        )

        self.row = self.merge_rects_by_coloring(self.row)

        self.play(
            FadeIn(new_tallies[0]),
            FadeIn(new_tallies[2:]),
        )




        # # # # # # # # # #
        # EVEN MORE FLIPS #
        # # # # # # # # # #

        self.play(FadeOut(new_tallies))
        self.clear()
        self.row = BrickRow(3)
        self.add(randy, self.row)       
        

        for i in range(3):

            self.play(FlipCoin(randy))

            self.wait()

            previous_row = self.row.copy()

            self.play(previous_row.shift, 1.25 * self.row.height * UP)

            self.play(
                SplitRectsInBrickWall(self.row)
            )
            self.wait()
            self.row = self.merge_rects_by_subdiv(self.row)
            self.wait()
            self.row = self.merge_rects_by_coloring(self.row)
            self.wait()

            self.play(FadeOut(previous_row))






class ShowProbsInBrickRow3(BrickRowScene):

    def construct(self):

        randy = self.get_primary_pi_creature()
        randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT)
        #self.add(randy)
        self.row = BrickRow(3, height = 2, width = 10)
        self.wait()

        self.add(self.row)

        tallies = VGroup()
        for (i, rect) in enumerate(self.row.get_rects_for_level(3)):
            tally = TallyStack(3-i, i, show_decimals = False)
            tally.move_to(rect)
            tallies.add(tally)

        self.add(tallies)
        self.wait(6)

        braces = VGroup(*[Brace(rect, UP) for rect in self.row.rects])
        counts = [choose(3, i) for i in range(4)]
        probs = VGroup(*[TexMobject("{" + str(k) + "\over 8}") for k in counts])
        for (brace, prob) in zip(braces, probs):
            prob.next_to(brace, UP)

        self.wait()
        self.play(
            LaggedStartMap(ShowCreation, braces, run_time = 3),
            LaggedStartMap(Write, probs, run_time = 3)
        )
        self.wait()

        self.play(FadeOut(braces),FadeOut(probs))




class ShowOutcomesInBrickRow4(BrickRowScene):

    def construct(self):

        randy = self.get_primary_pi_creature()
        randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT)
        #self.add(randy)
        self.row = BrickRow(3, height = 2, width = 10)
        
        previous_row = self.row.copy()
        v = 1.25 * self.row.height * UP
        self.play(
            previous_row.shift, v,
        )

        self.add(self.row)
        self.add(previous_row)

                


        self.wait()
        previous_outcomes = previous_row.get_outcome_rects_for_level(3,
            with_labels = True, inset = True)

        previous_outcomes_copy = previous_outcomes.copy()



        self.play(
            LaggedStartMap(FadeIn, previous_outcomes),
            LaggedStartMap(FadeIn, previous_outcomes_copy),
        )
        
        self.wait()

        new_outcomes = self.row.get_outcome_rects_for_level(4,
            with_labels = True, inset = True)
        # remove each last coin


        new_outcomes_left = VGroup(
            new_outcomes[0],
            new_outcomes[2],
            new_outcomes[3],
            new_outcomes[4],
            new_outcomes[8],
            new_outcomes[9],
            new_outcomes[10],
            new_outcomes[14]
        )
        new_outcomes_right = VGroup(
            new_outcomes[1],
            new_outcomes[5],
            new_outcomes[6],
            new_outcomes[7],
            new_outcomes[11],
            new_outcomes[12],
            new_outcomes[13],
            new_outcomes[15]
        )
        heads_labels = VGroup(*[outcome.label[-1] for outcome in new_outcomes_left])
        tails_labels = VGroup(*[outcome.label[-1] for outcome in new_outcomes_right])
        heads_labels.save_state()
        tails_labels.save_state()
        for outcome in new_outcomes:
            outcome.label[-1].fade(1)

        run_time = 0.5
        self.play(Transform(previous_outcomes[0], new_outcomes_left[0], run_time = run_time))
        self.play(Transform(previous_outcomes[1:4], new_outcomes_left[1:4], run_time = run_time))
        self.play(Transform(previous_outcomes[4:7], new_outcomes_left[4:7], run_time = run_time))
        self.play(Transform(previous_outcomes[7], new_outcomes_left[7], run_time = run_time))


        self.play(heads_labels.restore)


        self.play(Transform(previous_outcomes_copy[0], new_outcomes_right[0], run_time = run_time))
        self.play(Transform(previous_outcomes_copy[1:4], new_outcomes_right[1:4], run_time = run_time))
        self.play(Transform(previous_outcomes_copy[4:7], new_outcomes_right[4:7], run_time = run_time))
        self.play(Transform(previous_outcomes_copy[7], new_outcomes_right[7], run_time = run_time))


        self.play(tails_labels.restore)


        self.wait()

        anims = [FadeOut(previous_outcomes),FadeOut(previous_outcomes_copy)]

        for outcome in new_outcomes_left:
            anims.append(FadeOut(outcome.label[-1]))
        for outcome in new_outcomes_right:
            anims.append(FadeOut(outcome.label[-1]))

        self.play(*anims)

        self.wait()





class SplitTalliesIntoBrickRow4(BrickRowScene):

    def construct(self):

        randy = self.get_primary_pi_creature()
        randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT)
        #self.add(randy)
        self.row = BrickRow(3, height = 2, width = 10)
        
        previous_row = self.row.copy()
        v = 1.25 * self.row.height * UP
        self.play(
            previous_row.shift, v,
        )

        tallies = VGroup()
        for (i, rect) in enumerate(previous_row.get_rects_for_level(3)):
            tally = TallyStack(3-i, i, show_decimals = True)
            tally.move_to(rect)
            tallies.add(tally)

        moving_tallies_left = tallies.copy()
        moving_tallies_right = tallies.copy()

        self.add(self.row, previous_row)
        self.add_foreground_mobject(tallies)
        self.add_foreground_mobject(moving_tallies_left)
        self.add_foreground_mobject(moving_tallies_right)


        self.play(SplitRectsInBrickWall(self.row))

        anims = []
        for (tally, rect) in zip(moving_tallies_left, previous_row.rects):
            anims.append(tally.move_to)
            anims.append(rect.get_center() + rect.get_width() * 0.25 * LEFT)

        self.play(*anims)

        new_tallies_left = VGroup()
        for (i, tally) in enumerate(moving_tallies_left):
            new_tally = TallyStack(4-i,i, with_labels = True)
            new_tally.move_to(tally)
            new_tallies_left.add(new_tally)

        self.play(Transform(moving_tallies_left, new_tallies_left))

        anims = []
        for (tally, rect) in zip(moving_tallies_right, previous_row.rects):
            anims.append(tally.move_to)
            anims.append(rect.get_center() + rect.get_width() * 0.25 * RIGHT)

        self.play(*anims)

        new_tallies_right = VGroup()
        for (i, tally) in enumerate(moving_tallies_right):
            new_tally = TallyStack(3-i,i+1, with_labels = True)
            new_tally.move_to(tally)
            new_tallies_right.add(new_tally)

        self.play(Transform(moving_tallies_right, new_tallies_right))


        hypothetical_new_row = BrickRow(4, height = 2, width = 10)
        anims = []
        for (tally, rect) in zip(moving_tallies_left[1:], hypothetical_new_row.rects[1:-1]):
            anims.append(tally.move_to)
            anims.append(rect)
        for (tally, rect) in zip(moving_tallies_right[:-1], hypothetical_new_row.rects[1:-1]):
            anims.append(tally.move_to)
            anims.append(rect)
        self.play(*anims)
        self.wait()
        self.row = self.merge_rects_by_subdiv(self.row)
        self.wait()
        self.row = self.merge_rects_by_coloring(self.row)
        self.wait()





