from big_ol_pile_of_manim_imports import *

X_COLOR = GREEN
Y_COLOR = RED
Z_COLOR = BLUE
OUTPUT_COLOR = YELLOW
INPUT_COLOR = MAROON_B


def get_cramer_matrix(matrix, output_vect, index=0):
    """
    The inputs matrix and output_vect should be Matrix mobjects
    """
    new_matrix = np.array(matrix.mob_matrix)
    new_matrix[:, index] = output_vect.mob_matrix[:, 0]
    # Create a new Matrix mobject with copies of these entries
    result = Matrix(new_matrix, element_to_mobject=lambda m: m.copy())
    result.match_height(matrix)
    return result


class LinearSystem(VGroup):
    CONFIG = {
        "matrix_config": {
            "element_to_mobject": Integer,
        },
        "dimensions": 3,
        "min_int": -9,
        "max_int": 10,
        "height": 4,
    }

    def __init__(self, matrix=None, output_vect=None, **kwargs):
        VGroup.__init__(self, **kwargs)
        if matrix is None:
            dim = self.dimensions
            matrix = np.random.randint(
                self.min_int,
                self.max_int,
                size=(dim, dim)
            )
        else:
            dim = len(matrix)
        self.matrix_mobject = Matrix(matrix, **self.matrix_config)
        self.equals = TexMobject("=")
        self.equals.scale(1.5)

        colors = [X_COLOR, Y_COLOR, Z_COLOR][:dim]
        chars = ["x", "y", "z"][:dim]
        self.input_vect_mob = Matrix(np.array(chars))
        self.input_vect_mob.elements.set_color_by_gradient(*colors)

        if output_vect is None:
            output_vect = np.random.randint(
                self.min_int, self.max_int, size=(dim, 1))
        self.output_vect_mob = IntegerMatrix(output_vect)
        self.output_vect_mob.elements.set_color(OUTPUT_COLOR)

        for mob in self.matrix_mobject, self.input_vect_mob, self.output_vect_mob:
            mob.scale_to_fit_height(self.height)

        self.add(
            self.matrix_mobject,
            self.input_vect_mob,
            self.equals,
            self.output_vect_mob,
        )
        self.arrange_submobjects(RIGHT, buff=SMALL_BUFF)


# Scenes


class CramerOpeningQuote(OpeningQuote):
    CONFIG = {
        "quote": ["Computers are useless. They \\\\ can only give you answers."],
        "author": "Pablo Picasso",
    }


class LeaveItToComputers(TeacherStudentsScene):
    CONFIG = {
        "random_seed": 1,
    }

    def construct(self):
        system = LinearSystem(height=3)
        system.next_to(self.pi_creatures, UP)
        system.generate_target()
        system.target.scale(0.5)
        system.target.to_corner(UL)

        colors = [X_COLOR, Y_COLOR, Z_COLOR]
        cramer_groups = VGroup()
        for i in range(3):
            numer_matrix = get_cramer_matrix(
                system.matrix_mobject, system.output_vect_mob,
                index=i
            )
            VGroup(*numer_matrix.mob_matrix[:, i]).set_color(colors[i])
            VGroup(*numer_matrix.mob_matrix[:, i]).set_stroke(colors[i], 1)
            numer = VGroup(
                get_det_text(numer_matrix, initial_scale_factor=3),
                numer_matrix
            )
            numer.to_corner(UP)
            denom_matrix_mobject = system.matrix_mobject.deepcopy()
            denom = VGroup(
                get_det_text(denom_matrix_mobject, initial_scale_factor=3),
                denom_matrix_mobject,
            )
            rhs = VGroup(numer, Line(LEFT, RIGHT).match_width(numer), denom)
            rhs.arrange_submobjects(DOWN)
            rhs.scale_to_fit_height(2.25)
            rhs.move_to(self.hold_up_spot, DOWN)
            rhs.to_edge(RIGHT, buff=LARGE_BUFF)
            equals = TexMobject("=").next_to(rhs, LEFT)
            variable = system.input_vect_mob.elements[i].copy()
            variable.next_to(equals, LEFT)
            cramer_group = VGroup(variable, equals, rhs)
            cramer_group.variable = variable
            cramer_group.equals = equals
            cramer_group.rhs = rhs
            cramer_groups.add(cramer_group)

        self.play(
            Write(system),
            self.teacher.change, "raise_right_hand",
            self.get_student_changes("pondering", "thinking", "hooray")
        )
        self.wait(2)
        self.play(
            PiCreatureSays(
                self.teacher, "Let the computer \\\\ handle it",
                target_mode="shruggie",
            ),
            MoveToTarget(system, path_arc=90 * DEGREES),
            self.get_student_changes(*["confused"] * 3)
        )
        self.wait(3)

        cg = cramer_groups[0]
        scale_factor = 1.5
        cg.scale(scale_factor, about_edge=DOWN)
        numer, line, denom = cg.rhs
        x_copy = system.input_vect_mob.elements[0].copy()
        self.play(
            RemovePiCreatureBubble(
                self.teacher, target_mode="raise_right_hand"),
            ShowCreation(line),
            ReplacementTransform(
                system.matrix_mobject.copy(),
                denom[1],
            ),
            Write(denom[0]),
            FadeIn(cg.equals),
            ReplacementTransform(x_copy, cg.variable),
        )
        denom_mover = denom.deepcopy()
        denom_mover.target = numer.deepcopy()
        column1 = VGroup(*denom_mover.target[1].mob_matrix[:, 0])
        column1.set_fill(opacity=0)
        column1.set_stroke(width=0)
        self.play(MoveToTarget(denom_mover))
        self.look_at(system)
        self.play(
            ReplacementTransform(
                system.output_vect_mob.elements.copy(),
                VGroup(*numer[1].mob_matrix[:, 0]),
                path_arc=180 * DEGREES
            ),
            self.teacher.change, "happy",
        )
        self.remove(denom_mover)
        self.add(cg)
        self.change_all_student_modes("sassy")
        self.wait(2)
        self.play(
            cramer_groups[0].scale, 1 / scale_factor,
            cramer_groups[0].next_to, cramer_groups[1], LEFT, MED_LARGE_BUFF,
            FadeIn(cramer_groups[1]),
            FadeOut(system),
            self.get_student_changes(*3 * ["horrified"], look_at_arg=UP),
        )
        self.wait()
        self.play(
            FadeIn(cramer_groups[2]),
            cramer_groups[:2].next_to, cramer_groups[2], LEFT, MED_LARGE_BUFF,
            self.get_student_changes(*3 * ["horrified"], look_at_arg=UP),
        )
        self.wait()

        brace = Brace(cramer_groups, UP)
        rule_text = brace.get_text("``Cramer's rule''")

        self.play(
            GrowFromCenter(brace),
            Write(rule_text),
            self.get_student_changes(
                "pondering", "erm", "maybe",
                look_at_arg=brace,
            )
        )
        self.wait(3)


class PrerequisiteKnowledge(TeacherStudentsScene):
    CONFIG = {
        "camera_config": {"background_alpha": 255}
    }

    def construct(self):
        self.remove(*self.pi_creatures)
        randy = self.students[1]
        self.add(randy)

        title = TextMobject("Prerequisites")
        title.to_edge(UP)

        h_line = Line(LEFT, RIGHT).scale(5)
        h_line.next_to(title, DOWN)

        images = Group(*[
            ImageMobject("eola%d_thumbnail" % d)
            for d in [5, 7, 6]
        ])
        images.arrange_submobjects(RIGHT, buff=LARGE_BUFF)
        images.next_to(h_line, DOWN, MED_LARGE_BUFF)
        for image in images:
            rect = SurroundingRectangle(image, color=BLUE)
            image.rect = rect

        self.add(title)
        self.play(
            ShowCreation(h_line),
            randy.change, "erm"
        )
        self.wait()
        for image in images:
            self.play(
                FadeInFromDown(image),
                FadeInFromDown(image.rect),
                randy.change, "pondering"
            )
            self.wait()


class NotTheMostComputationallyEfficient(Scene):
    CONFIG = {
        "words": "Not the most \\\\ computationally efficient",
        "word_height": 4,
        "opacity": 0.7,
    }

    def construct(self):
        big_rect = FullScreenFadeRectangle(opacity=self.opacity)
        self.add(big_rect)

        words = TextMobject(self.words)
        words.set_color(RED)
        words.set_stroke(WHITE, 1)
        words.scale_to_fit_width(FRAME_WIDTH - 2 * MED_LARGE_BUFF)
        self.play(Write(words))
        self.wait()


class WhyLearnIt(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "What?!?  Then why \\\\ learn it?",
            bubble_kwargs={"direction": LEFT},
            student_index=2,
            target_mode="angry",
        )
        self.change_all_student_modes("angry")
        self.wait()
        self.play(
            self.teacher.change, "raise_right_hand",
            self.get_student_changes("erm", "happy" "pondering"),
            RemovePiCreatureBubble(self.students[2], target_mode="pondering"),
        )
        self.look_at(self.screen)
        self.wait(10)


class SetupSimpleSystemOfEquations(LinearTransformationScene):
    CONFIG = {
        "matrix": [[3, 2], [-1, 2]],
        "output_vect": [-4, -2],
        "quit_before_final_transformation": False,
        "array_scale_factor": 0.75,
        "compare_to_big_system": True,
        "transition_to_geometric_view": True,
    }

    def construct(self):
        self.remove_grid()
        self.introduce_system()
        self.from_system_to_matrix()
        self.show_geometry()

    def remove_grid(self):
        self.clear()

    def introduce_system(self):
        system = self.system = self.get_system(self.matrix, self.output_vect)
        dim = len(self.matrix)
        big_dim = 7  # Big system size
        big_matrix = np.random.randint(-9, 10, size=(big_dim, big_dim))
        big_output_vect = np.random.randint(-9, 10, size=big_dim)
        big_matrix[:dim, :dim] = self.matrix
        big_output_vect[:dim] = self.output_vect
        big_system = self.get_system(big_matrix, big_output_vect)

        unknown_circles = VGroup(*[
            Circle(color=YELLOW).replace(term).scale(1.5)
            for term in system.unknowns
        ])
        unknown_circles.set_stroke(YELLOW, 2)
        for circle in unknown_circles:
            circle.save_state()
            circle.scale(5)
            circle.fade(1)
        row_rects = VGroup(*map(SurroundingRectangle, system))
        row_rects.set_stroke(BLUE, 2)

        self.add(system)
        self.wait()
        self.play(LaggedStart(
            ApplyMethod, unknown_circles,
            lambda m: (m.restore,),
            lag_ratio=0.7
        ))
        self.play(FadeOut(unknown_circles))
        self.play(LaggedStart(ShowCreation, row_rects,
                              run_time=1, lag_ratio=0.8))
        self.play(FadeOut(row_rects))
        self.wait()
        if self.compare_to_big_system:
            self.remove(system)
            self.play(ReplacementTransform(system.copy(), big_system))
            self.wait(2)
            # Oh yeah, super readable line...
            cutoff = 3 * dim - 1
            self.play(*[
                ReplacementTransform(big_system[i][:cutoff], system[i][:cutoff])
                for i in range(dim)
            ] + [
                ReplacementTransform(big_system[i][-2:], system[i][-2:])
                for i in range(dim)
            ] + [
                FadeOut(big_system[i][start:end])
                for i in range(big_dim)
                for start in [cutoff if i < dim else 0]
                for end in [-2 if i < dim else len(big_system[i])]
            ])
            self.remove(big_system, system)
            self.add(system)

    def from_system_to_matrix(self):
        # dim = len(self.matrix)
        system_in_lines = self.system
        matrix_system = self.matrix_system = LinearSystem(
            self.matrix, self.output_vect, height=2
        )
        matrix_system.center()

        corner_rect = self.corner_rect = SurroundingRectangle(
            matrix_system, buff=MED_SMALL_BUFF
        )
        corner_rect.set_stroke(width=0)
        corner_rect.set_fill(BLACK, opacity=0.8)
        corner_rect.scale_to_fit_height(2)
        corner_rect.to_corner(UL, buff=0)

        self.play(system_in_lines.to_edge, UP)
        system_in_lines_copy = system_in_lines.deepcopy()
        self.play(
            ReplacementTransform(
                VGroup(*map(VGroup, system_in_lines_copy.matrix_elements)),
                matrix_system.matrix_mobject.elements,
            ),
            Write(matrix_system.matrix_mobject.brackets),
            Write(matrix_system.output_vect_mob.brackets),
            Write(matrix_system.input_vect_mob.brackets),
            Write(matrix_system.equals)
        )
        self.play(ReplacementTransform(
            VGroup(*map(VGroup, system_in_lines_copy.output_vect_elements)),
            matrix_system.output_vect_mob.elements,
        ))
        self.play(*[
            ReplacementTransform(uk, elem)
            for uk, elem in zip(
                system_in_lines_copy.unknowns,
                it.cycle(matrix_system.input_vect_mob.elements)
            )
        ])
        self.wait()
        if self.transition_to_geometric_view:
            self.play(
                Write(self.background_plane),
                Write(self.plane),
                FadeOut(system_in_lines),
                FadeIn(corner_rect),
                matrix_system.scale_to_fit_height, corner_rect.get_height() - MED_LARGE_BUFF,
                matrix_system.move_to, corner_rect,
            )
            self.play(*map(GrowArrow, self.basis_vectors))

            self.add_foreground_mobject(corner_rect)
            self.add_foreground_mobject(matrix_system)

    def show_geometry(self):
        system = self.matrix_system
        matrix_mobject = system.matrix_mobject
        columns = VGroup(*[
            VGroup(*matrix_mobject.mob_matrix[:, i])
            for i in 0, 1
        ])

        matrix = np.array(self.matrix)
        first_half_matrix = np.identity(matrix.shape[0])
        first_half_matrix[:, 0] = matrix[:, 0]
        second_half_matrix = np.dot(
            matrix,
            np.linalg.inv(first_half_matrix),
        )

        scale_factor = self.array_scale_factor

        column_mobs = VGroup()
        for i in 0, 1:
            column_mob = IntegerMatrix(matrix[:, i])
            column_mob.elements.set_color([X_COLOR, Y_COLOR][i])
            column_mob.scale(scale_factor)
            column_mob.next_to(
                self.plane.coords_to_point(*matrix[:, i]), RIGHT)
            column_mob.add_to_back(BackgroundRectangle(column_mob))
            column_mobs.add(column_mob)

        output_vect_mob = self.get_vector(self.output_vect, color=OUTPUT_COLOR)
        output_vect_label = system.output_vect_mob.deepcopy()
        output_vect_label.add_to_back(BackgroundRectangle(output_vect_label))
        output_vect_label.generate_target()
        output_vect_label.target.scale(scale_factor)
        output_vect_label.target.next_to(
            output_vect_mob.get_end(), LEFT, SMALL_BUFF)

        input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect)
        input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR)
        q_marks = TexMobject("????")
        q_marks.set_color_by_gradient(INPUT_COLOR, OUTPUT_COLOR)
        q_marks.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF)
        q_marks_rect = SurroundingRectangle(q_marks, color=WHITE)

        # Show output vector
        self.play(
            GrowArrow(output_vect_mob),
            MoveToTarget(output_vect_label),
        )
        self.add_foreground_mobjects(output_vect_mob, output_vect_label)
        self.wait()

        # Show columns
        for column, color in zip(columns, [X_COLOR, Y_COLOR]):
            rect = SurroundingRectangle(column, color=WHITE)
            self.play(
                column.set_color, color,
                system.input_vect_mob.elements.set_color, WHITE,
                ShowPassingFlash(rect),
            )
        matrices = [first_half_matrix, second_half_matrix]
        for column, column_mob, m in zip(columns, column_mobs, matrices):
            column_mob.save_state()
            column_mob[0].scale(0).move_to(matrix_mobject)
            Transform(column_mob.elements, column).update(1)
            Transform(column_mob.brackets, matrix_mobject.brackets).update(1)
            self.add_foreground_mobject(column_mob)
            self.apply_matrix(m, added_anims=[
                ApplyMethod(column_mob.restore, path_arc=90 * DEGREES)
            ])
        self.wait()

        # Do inverse transformation to reveal input
        self.remove_foreground_mobjects(column_mobs)
        self.apply_inverse(self.matrix, run_time=1, added_anims=[
            ReplacementTransform(output_vect_mob.copy(), input_vect_mob),
            ReplacementTransform(output_vect_label.elements.copy(), q_marks),
            FadeOut(column_mobs)
        ])
        self.play(ShowPassingFlash(q_marks_rect))
        self.wait(2)
        if not self.quit_before_final_transformation:
            self.apply_matrix(self.matrix, added_anims=[
                FadeOut(q_marks),
                ReplacementTransform(input_vect_mob, output_vect_mob),
                FadeIn(column_mobs),
            ])
            self.wait()

        self.q_marks = q_marks
        self.input_vect_mob = input_vect_mob
        self.output_vect_mob = output_vect_mob
        self.output_vect_label = output_vect_label
        self.column_mobs = column_mobs

    # Helpers

    def get_system(self, matrix, output_vect):
        if len(matrix) <= 3:
            chars = "xyzwv"
        else:
            chars = ["x_%d" % d for d in range(len(matrix))]
        colors = [
            color
            for i, color in zip(
                range(len(matrix)),
                it.cycle([X_COLOR, Y_COLOR, Z_COLOR, YELLOW, MAROON_B, TEAL])
            )
        ]
        system = VGroup()
        system.matrix_elements = VGroup()
        system.output_vect_elements = VGroup()
        system.unknowns = VGroup()
        for row, num in zip(matrix, output_vect):
            args = []
            for i in range(len(row)):
                if i + 1 == len(row):
                    sign = "="
                elif row[i + 1] < 0:
                    sign = "-"
                else:
                    sign = "+"
                args += [str(abs(row[i])), chars[i], sign]
            args.append(str(num))
            line = TexMobject(*args)
            line.set_color_by_tex_to_color_map(dict([
                (char, color)
                for char, color in zip(chars, colors)
            ]))
            system.add(line)
            system.matrix_elements.add(*line[0:-1:3])
            system.unknowns.add(*line[1:-1:3])
            system.output_vect_elements.add(line[-1])

        system.output_vect_elements.set_color(OUTPUT_COLOR)
        system.arrange_submobjects(
            DOWN,
            buff=0.75,
            index_of_submobject_to_align=-2
        )
        return system


class ShowZeroDeterminantCase(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors": True,
        "matrix": [[3, -2.0], [1, -2.0 / 3]],
        "tex_scale_factor": 1.25,
        "det_eq_symbol": "=",
    }

    def construct(self):
        self.add_equation()
        self.show_det_zero()

    def add_equation(self):
        equation = self.equation = TexMobject(
            "A", "\\vec{\\textbf{x}}", "=", "\\vec{\\textbf{v}}"
        )
        equation.scale(self.tex_scale_factor)
        equation.set_color_by_tex("{x}", INPUT_COLOR)
        equation.set_color_by_tex("{v}", OUTPUT_COLOR)
        equation.add_background_rectangle()
        equation.to_corner(UL)
        self.add(equation)
        self.add_foreground_mobject(equation)

    def show_det_zero(self):
        matrix = self.matrix

        # vect_in_span = [2, 2.0 / 3]
        vect_in_span = [2, 2.0 / 3]
        vect_off_span = [1, 2]
        vect_in_span_mob = self.get_vector(vect_in_span, color=OUTPUT_COLOR)
        vect_off_span_mob = self.get_vector(vect_off_span, color=OUTPUT_COLOR)

        for vect_mob in vect_in_span_mob, vect_off_span_mob:
            circle = Circle(color=WHITE, radius=0.15, stroke_width=2)
            circle.move_to(vect_mob.get_end())
            vect_mob.circle = circle

        vect_off_span_words = TextMobject("No input lands here")
        vect_off_span_words.next_to(vect_off_span_mob.circle, UP)
        vect_off_span_words.add_background_rectangle()

        vect_in_span_words = TextMobject("Many inputs lands here")
        vect_in_span_words.next_to(vect_in_span_mob.circle, DR)
        vect_in_span_words.shift_onto_screen()
        vect_in_span_words.add_background_rectangle()

        moving_group = VGroup(self.plane, self.basis_vectors)
        moving_group.save_state()

        solution = np.dot(np.linalg.pinv(matrix), vect_in_span)
        import sympy
        null_space_basis = np.array(sympy.Matrix(matrix).nullspace())
        null_space_basis = null_space_basis.flatten().astype(float)
        solution_vectors = VGroup(*[
            self.get_vector(
                solution + x * null_space_basis,
                rectangular_stem_width=0.025,
                tip_length=0.2,
            )
            for x in np.linspace(-4, 4, 20)
        ])
        solution_vectors.set_color_by_gradient(YELLOW, MAROON_B)

        self.apply_matrix(matrix, path_arc=0)
        self.wait()
        self.show_det_equation()
        self.wait()

        # Mention zero determinants
        self.play(GrowArrow(vect_off_span_mob))
        self.play(
            ShowCreation(vect_off_span_mob.circle),
            Write(vect_off_span_words),
        )
        self.wait()
        self.play(
            FadeOut(vect_off_span_mob.circle),
            ReplacementTransform(vect_off_span_mob, vect_in_span_mob),
            ReplacementTransform(vect_off_span_words, vect_in_span_words),
            ReplacementTransform(vect_off_span_mob.circle,
                                 vect_in_span_mob.circle),
        )
        self.wait(2)
        self.play(
            FadeOut(vect_in_span_words),
            FadeOut(vect_in_span_mob.circle),
            ApplyMethod(moving_group.restore, run_time=2),
            ReplacementTransform(
                VGroup(vect_in_span_mob),
                solution_vectors,
                run_time=2,
            ),
        )
        self.wait()

    # Helpers

    def show_det_equation(self):
        equation = self.equation
        det_equation = TexMobject(
            "\\det(", "A", ")", self.det_eq_symbol, "0"
        )
        det_equation.scale(self.tex_scale_factor)
        det_equation.next_to(
            equation, DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
        det_rect = BackgroundRectangle(det_equation)
        self.play(
            FadeIn(det_rect),
            Write(det_equation[0]),
            ReplacementTransform(
                equation.get_part_by_tex("A").copy(),
                det_equation.get_part_by_tex("A").copy(),
            ),
            Write(det_equation[2:]),
        )
        self.add_foreground_mobject(det_rect, det_equation)


class NonZeroDeterminantCase(ShowZeroDeterminantCase, SetupSimpleSystemOfEquations):
    CONFIG = {
        "det_eq_symbol": "\\neq"
    }

    def construct(self):
        self.add_equation()
        self.show_det_equation()

        output_vect = self.output_vect
        matrix = self.matrix
        input_vect = np.dot(np.linalg.inv(matrix), output_vect)

        input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR)
        output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR)

        input_vect_label = TextMobject("Input")
        input_vect_label.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF)
        input_vect_label.match_color(input_vect_mob)
        output_vect_label = TextMobject("Output")
        output_vect_label.next_to(output_vect_mob.get_end(), DOWN, SMALL_BUFF)
        output_vect_label.match_color(output_vect_mob)
        for label in input_vect_label, output_vect_label:
            label.scale(1.25, about_edge=UP)
            label.add_background_rectangle()

        self.apply_matrix(matrix)
        self.wait()
        self.apply_inverse(matrix)
        self.wait()
        self.play(
            GrowArrow(input_vect_mob),
            Write(input_vect_label),
            run_time=1
        )
        self.wait()
        self.remove(input_vect_mob, input_vect_label)
        self.apply_matrix(matrix, added_anims=[
            ReplacementTransform(input_vect_mob.copy(), output_vect_mob),
            ReplacementTransform(input_vect_label.copy(), output_vect_label),
        ], run_time=2)
        self.wait()
        self.remove(output_vect_mob, output_vect_label)
        self.apply_inverse(matrix, added_anims=[
            ReplacementTransform(output_vect_mob.copy(), input_vect_mob),
            ReplacementTransform(output_vect_label.copy(), input_vect_label),
        ], run_time=2)
        self.wait()


class ThinkOfPuzzleAsLinearCombination(SetupSimpleSystemOfEquations):
    CONFIG = {
        "output_vect": [-4, -2],
    }

    def construct(self):
        self.force_skipping()
        super(ThinkOfPuzzleAsLinearCombination, self).construct()
        self.revert_to_original_skipping_status()

        self.rearrange_equation_as_linear_combination()
        self.show_linear_combination_of_vectors()

    def rearrange_equation_as_linear_combination(self):
        system = self.matrix_system
        corner_rect = self.corner_rect
        matrix, input_vect, equals, output_vect = system

        columns = VGroup(*[
            VGroup(*matrix.mob_matrix[:, i].flatten())
            for i in 0, 1
        ])
        column_arrays = VGroup(*[
            MobjectMatrix(matrix.deepcopy().mob_matrix[:, i])
            for i in 0, 1
        ])
        for column_array in column_arrays:
            column_array.match_height(output_vect)
        x, y = input_vect.elements
        movers = VGroup(x, y, equals, output_vect)
        for mover in movers:
            mover.generate_target()
        plus = TexMobject("+")

        new_system = VGroup(
            x.target, column_arrays[0], plus,
            y.target, column_arrays[1],
            equals.target,
            output_vect.target
        )
        new_system.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
        new_system.move_to(matrix, LEFT)

        corner_rect.generate_target()
        corner_rect.target.stretch_to_fit_width(
            new_system.get_width() + MED_LARGE_BUFF,
            about_edge=LEFT
        )

        self.remove_foreground_mobjects(corner_rect, system)
        self.play(
            MoveToTarget(corner_rect),
            FadeOut(input_vect.brackets),
            ReplacementTransform(matrix.brackets, column_arrays[0].brackets),
            ReplacementTransform(matrix.brackets.copy(),
                                 column_arrays[1].brackets),
            ReplacementTransform(columns[0], column_arrays[0].elements),
            ReplacementTransform(columns[1], column_arrays[1].elements),
            Write(plus, rate_func=squish_rate_func(smooth, 0.5, 1)),
            *[
                MoveToTarget(mover, replace_mobject_with_target_in_scene=True)
                for mover in movers
            ],
            path_arc=90 * DEGREES,
            run_time=2
        )
        self.add_foreground_mobject(corner_rect, new_system)
        self.wait()

    def show_linear_combination_of_vectors(self):
        basis_vectors = self.basis_vectors
        input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect)
        origin = self.plane.coords_to_point(0, 0)
        for basis, scalar in zip(basis_vectors, input_vect):
            basis.ghost = basis.copy()
            basis.ghost.set_color(average_color(basis.get_color(), BLACK))
            self.add_foreground_mobjects(basis.ghost, basis)
            basis.generate_target()
            basis_coords = np.array(
                self.plane.point_to_coords(basis.get_end()))
            new_coords = scalar * basis_coords
            basis.target.put_start_and_end_on(
                origin, self.plane.coords_to_point(*new_coords),
            )

        dashed_lines = VGroup(*[DashedLine(LEFT, RIGHT) for x in range(2)])

        def update_dashed_lines(lines):
            for i in 0, 1:
                lines[i].put_start_and_end_on(
                    basis_vectors[i].get_start(),
                    basis_vectors[i].get_end(),
                )
                lines[i].shift(basis_vectors[1 - i].get_end() - origin)
            return lines
        update_dashed_lines(dashed_lines)
        self.play(LaggedStart(ShowCreation, dashed_lines, lag_ratio=0.7))
        for basis in basis_vectors:
            self.play(
                MoveToTarget(basis, run_time=2),
                UpdateFromFunc(dashed_lines, update_dashed_lines)
            )
        self.wait()


class WrongButHelpful(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("What's next is wrong, \\\\ but helpful")
        self.change_student_modes("sassy", "sad", "angry")
        self.wait(3)


class LookAtDotProducts(SetupSimpleSystemOfEquations):
    CONFIG = {
        "quit_before_final_transformation": True,
        "equation_scale_factor": 0.7,
    }

    def construct(self):
        self.force_skipping()
        super(LookAtDotProducts, self).construct()
        self.revert_to_original_skipping_status()

        self.remove_corner_system()
        self.show_dot_products()

    def remove_corner_system(self):
        to_remove = [self.corner_rect, self.matrix_system]
        self.remove_foreground_mobjects(*to_remove)
        self.remove(*to_remove)

        q_marks = self.q_marks
        input_vect_mob = self.input_vect_mob

        equations = self.equations = VGroup()
        for i in 0, 1:
            basis = [0, 0]
            basis[i] = 1
            equation = VGroup(
                Matrix(["x", "y"]),
                TexMobject("\\cdot"),
                IntegerMatrix(basis),
                TexMobject("="),
                TexMobject(["x", "y"][i]),
            )
            for part in equation:
                if isinstance(part, Matrix):
                    part.scale(self.array_scale_factor)
            equation[2].elements.set_color([X_COLOR, Y_COLOR][i])
            equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
            equation.scale(self.equation_scale_factor)
            equations.add(equation)
        equations.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
        equations.to_corner(UL)
        corner_rect = self.corner_rect = BackgroundRectangle(
            equations, opacity=0.8)
        self.resize_corner_rect_to_mobjet(corner_rect, equations)
        corner_rect.save_state()
        self.resize_corner_rect_to_mobjet(corner_rect, equations[0])

        xy_vect_mob = Matrix(["x", "y"], include_background_rectangle=True)
        xy_vect_mob.scale(self.array_scale_factor)
        xy_vect_mob.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF)
        q_marks.add_background_rectangle()
        q_marks.next_to(xy_vect_mob, RIGHT)

        origin = self.plane.coords_to_point(0, 0)
        input_vect_end = input_vect_mob.get_end()
        x_point = (input_vect_end - origin)[0] * RIGHT + origin
        y_point = (input_vect_end - origin)[1] * UP + origin
        v_dashed_line = DashedLine(input_vect_end, x_point)
        h_dashed_line = DashedLine(input_vect_end, y_point)

        h_brace = Brace(Line(x_point, origin), UP, buff=SMALL_BUFF)
        v_brace = Brace(Line(y_point, origin), RIGHT, buff=SMALL_BUFF)

        self.add(xy_vect_mob)
        self.wait()
        self.play(
            FadeIn(corner_rect),
            FadeIn(equations[0][:-1]),
            ShowCreation(v_dashed_line),
            GrowFromCenter(h_brace),
        )
        self.play(
            ReplacementTransform(h_brace.copy(), equations[0][-1])
        )
        self.wait()
        self.play(
            corner_rect.restore,
            Animation(equations[0]),
            FadeIn(equations[1]),
        )
        self.wait()
        self.play(
            ReplacementTransform(equations[1][-1].copy(), v_brace),
            ShowCreation(h_dashed_line),
            GrowFromCenter(v_brace)
        )
        self.wait()

        self.to_fade = VGroup(
            h_dashed_line, v_dashed_line,
            h_brace, v_brace,
            xy_vect_mob, q_marks,
        )

    def show_dot_products(self):
        moving_equations = self.equations.copy()
        transformed_equations = VGroup()
        implications = VGroup()
        transformed_input_rects = VGroup()
        transformed_basis_rects = VGroup()
        for equation in moving_equations:
            equation.generate_target()
            xy_vect, dot, basis, equals, coord = equation.target
            T1, lp1, rp1 = TexMobject("T", "(", ")")
            lp1.scale(1, about_edge=LEFT)
            rp1.scale(1, about_edge=LEFT)
            for paren in lp1, rp1:
                paren.stretch_to_fit_height(equation.get_height())
            T2, lp2, rp2 = T1.copy(), lp1.copy(), rp1.copy()
            transformed_equation = VGroup(
                T1, lp1, xy_vect, rp1, dot,
                T2, lp2, basis, rp2, equals,
                coord
            )
            transformed_equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
            # transformed_equation.scale(self.equation_scale_factor)

            implies = TexMobject("\\Rightarrow").scale(1.2)
            implies.next_to(equation, RIGHT)
            implies.set_color(BLUE)

            transformed_equation.next_to(implies, RIGHT)
            implications.add(implies)

            transformed_input_rects.add(SurroundingRectangle(
                transformed_equation[:4],
                color=OUTPUT_COLOR
            ))
            transformed_basis_rects.add(SurroundingRectangle(
                transformed_equation[5:5 + 4],
                color=basis.elements.get_color()
            ))

            for mob in [implies]:
                mob.add(TexMobject("?").next_to(mob, UP, SMALL_BUFF))

            transformed_equation.parts_to_write = VGroup(
                T1, lp1, rp1, T2, lp2, rp2
            )

            transformed_equations.add(transformed_equation)

        corner_rect = self.corner_rect
        corner_rect.generate_target()
        group = VGroup(self.equations, transformed_equations)
        self.resize_corner_rect_to_mobjet(corner_rect.target, group)

        for array in [self.output_vect_label] + list(self.column_mobs):
            array.rect = SurroundingRectangle(array)
            array.rect.match_color(array.elements)

        self.play(
            MoveToTarget(corner_rect),
            Animation(self.equations),
            FadeOut(self.to_fade),
            LaggedStart(Write, implications),
        )
        self.remove(self.input_vect_mob)
        self.apply_matrix(self.matrix, added_anims=[
            Animation(VGroup(corner_rect, self.equations, implications)),
            MoveToTarget(moving_equations[0]),
            LaggedStart(FadeIn, transformed_equations[0].parts_to_write),
            FadeIn(self.column_mobs),
            ReplacementTransform(
                self.input_vect_mob.copy(), self.output_vect_mob)
        ])
        self.play(
            MoveToTarget(moving_equations[1]),
            LaggedStart(FadeIn, transformed_equations[1].parts_to_write),
            path_arc=-30 * DEGREES,
            run_time=2
        )
        self.wait()

        # Show rectangles
        self.play(
            LaggedStart(ShowCreation, transformed_input_rects, lag_ratio=0.8),
            ShowCreation(self.output_vect_label.rect),
        )
        for tbr, column_mob in zip(transformed_basis_rects, self.column_mobs):
            self.play(
                ShowCreation(tbr),
                ShowCreation(column_mob.rect),
            )
        self.wait()
        self.play(FadeOut(VGroup(
            transformed_input_rects,
            transformed_basis_rects,
            self.output_vect_label.rect,
            *[cm.rect for cm in self.column_mobs]
        )))

        # These computations assume plane is centered at ORIGIN
        output_vect = self.output_vect_mob.get_end()
        c1 = self.basis_vectors[0].get_end()
        c2 = self.basis_vectors[1].get_end()
        x_point = c1 * np.dot(output_vect, c1) / (np.linalg.norm(c1)**2)
        y_point = c2 * np.dot(output_vect, c2) / (np.linalg.norm(c2)**2)

        dashed_line_to_x = DashedLine(self.output_vect_mob.get_end(), x_point)
        dashed_line_to_y = DashedLine(self.output_vect_mob.get_end(), y_point)

        self.play(ShowCreation(dashed_line_to_x))
        self.play(ShowCreation(dashed_line_to_y))
        self.wait()

    # Helpers

    def resize_corner_rect_to_mobjet(self, rect, mobject):
        rect.stretch_to_fit_width(
            mobject.get_width() + MED_LARGE_BUFF + SMALL_BUFF)
        rect.stretch_to_fit_height(
            mobject.get_height() + MED_LARGE_BUFF + SMALL_BUFF)
        rect.to_corner(UL, buff=0)
        return rect


class NotAtAllTrue(NotTheMostComputationallyEfficient):
    CONFIG = {
        "words": "Not at all \\\\ True",
        "word_height": 4,
    }


class ShowDotProductChanging(LinearTransformationScene):
    CONFIG = {
        "matrix": [[2, -5.0 / 3], [-5.0 / 3, 2]],
        "v": [1, 2],
        "w": [2, 1],
        "v_label": "v",
        "w_label": "w",
        "v_color": YELLOW,
        "w_color": MAROON_B,
        "rhs1": "> 0",
        "rhs2": "< 0",
        "foreground_plane_kwargs": {
            "x_radius": 2 * FRAME_WIDTH,
            "y_radius": 2 * FRAME_HEIGHT,
        },
        "equation_scale_factor": 1.5,
    }

    def construct(self):
        v_mob = self.add_vector(self.v, self.v_color, animate=False)
        w_mob = self.add_vector(self.w, self.w_color, animate=False)
        kwargs = {
            "transformation_name": "T",
            "at_tip": True,
            "animate": False,
        }
        v_label = self.add_transformable_label(v_mob, self.v_label, **kwargs)
        w_label = self.add_transformable_label(w_mob, self.w_label, **kwargs)

        start_equation = self.get_equation(v_label, w_label, self.rhs1)
        start_equation.to_corner(UR)
        self.play(
            Write(start_equation[0::2]),
            ReplacementTransform(v_label.copy(), start_equation[1]),
            ReplacementTransform(w_label.copy(), start_equation[3]),
        )
        self.wait()
        self.add_foreground_mobject(start_equation)
        self.apply_matrix(self.matrix)
        self.wait()

        end_equation = self.get_equation(v_label, w_label, self.rhs2)
        end_equation.next_to(start_equation, DOWN, aligned_edge=RIGHT)
        self.play(
            FadeIn(end_equation[0]),
            ReplacementTransform(
                start_equation[2::2].copy(),
                end_equation[2::2],
            ),
            ReplacementTransform(v_label.copy(), end_equation[1]),
            ReplacementTransform(w_label.copy(), end_equation[3]),
        )
        self.wait(2)

    def get_equation(self, v_label, w_label, rhs):
        equation = VGroup(
            v_label.copy(),
            TexMobject("\\cdot"),
            w_label.copy(),
            TexMobject(rhs),
        )
        equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
        equation.add_to_back(BackgroundRectangle(equation))
        equation.scale(self.equation_scale_factor)
        return equation


class ShowDotProductChangingAwayFromZero(ShowDotProductChanging):
    CONFIG = {
        "matrix": [[2, 2], [0, -1]],
        "v": [1, 0],
        "w": [0, 1],
        "v_label": "x",
        "w_label": "y",
        "v_color": X_COLOR,
        "w_color": Y_COLOR,
        "rhs1": "= 0",
        "rhs2": "\\ne 0",
    }


class OrthonormalWords(Scene):
    def construct(self):
        v_tex = "\\vec{\\textbf{v}}"
        w_tex = "\\vec{\\textbf{w}}"
        top_words = TexMobject(
            "\\text{If }",
            "T(", v_tex, ")", "\\cdot",
            "T(", w_tex, ")", "=",
            v_tex, "\\cdot", w_tex,
            "\\text{ for all }", v_tex, "\\text{ and }", w_tex,
        )
        top_words.set_color_by_tex_to_color_map({
            v_tex: YELLOW,
            w_tex: MAROON_B,
        })
        bottom_words = TextMobject(
            "$T$", "is", "``Orthonormal''"
        )
        bottom_words.set_color_by_tex("Orthonormal", BLUE)
        words = VGroup(top_words, bottom_words)
        words.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
        for word in words:
            word.add_background_rectangle()
        words.to_edge(UP)

        self.play(Write(words))
        self.wait()


class ShowSomeOrthonormalTransformations(LinearTransformationScene):
    CONFIG = {
        "random_seed": 1,
        "n_angles": 5
    }

    def construct(self):
        for x in range(self.n_angles):
            angle = TAU * np.random.random() - TAU / 2
            matrix = rotation_matrix(angle, OUT)[:2, :2]
            self.apply_matrix(matrix)


class SolvingASystemWithOrthonormalMatrix(LinearTransformationScene):
    CONFIG = {
        "array_scale_factor": 0.6,
    }

    def construct(self):
        # Setup system
        angle = TAU / 12
        matrix = np.array([
            [np.cos(angle), -np.sin(angle)],
            [np.sin(angle), np.cos(angle)]
        ])
        output_vect = [1, 2]
        symbolic_matrix = [
            ["\\cos(30^\\circ)", "-\\sin(30^\\circ)"],
            ["\\sin(30^\\circ)", "\\cos(30^\\circ)"],
        ]
        system = LinearSystem(
            matrix=symbolic_matrix,
            output_vect=output_vect,
            matrix_config={
                "h_buff": 2.5,
                "element_to_mobject": TexMobject,
            },
            height=1.25,
        )
        system.to_corner(UL)
        system.matrix_mobject.set_color_columns(X_COLOR, Y_COLOR)
        system.input_vect_mob.elements.set_color(WHITE)
        system_rect = BackgroundRectangle(system, buff=MED_SMALL_BUFF)

        matrix_brace = Brace(system.matrix_mobject, DOWN, buff=SMALL_BUFF)
        orthonomal_label = TextMobject("Orthonormal")
        orthonomal_label.set_color(WHITE)
        orthonomal_label.next_to(matrix_brace, DOWN, SMALL_BUFF)
        orthonomal_label.add_background_rectangle()

        # Input and output vectors
        output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR)
        output_vect_label = system.output_vect_mob.copy()
        output_vect_label.add_background_rectangle()
        output_vect_label.scale(self.array_scale_factor)
        output_vect_label.next_to(
            output_vect_mob.get_end(), RIGHT, buff=SMALL_BUFF)

        input_vect = np.dot(np.linalg.inv(matrix), output_vect)
        input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR)
        input_vect_label = TextMobject("Mystery input vector")
        input_vect_label.add_background_rectangle()
        input_vect_label.next_to(input_vect_mob.get_end(), RIGHT, SMALL_BUFF)
        input_vect_label.match_color(input_vect_mob)

        # Column arrays
        column_mobs = VGroup()
        for i, vect in zip(range(2), [DR, DL]):
            elements = system.matrix_mobject.deepcopy().mob_matrix[:, i]
            column_mob = MobjectMatrix(elements)
            column_mob.add_background_rectangle()
            column_mob.scale(self.array_scale_factor)
            column_mob.next_to(
                self.get_vector(matrix[:, i]).get_end(), vect,
                buff=SMALL_BUFF
            )
            column_mobs.add(column_mob)
        column_mobs[1].shift(SMALL_BUFF * UP)

        # Dot product lines
        x_point = self.plane.coords_to_point(input_vect[0], 0)
        y_point = self.plane.coords_to_point(0, input_vect[1])
        input_dashed_lines = VGroup(
            DashedLine(input_vect_mob.get_end(), x_point),
            DashedLine(input_vect_mob.get_end(), y_point),
        )
        output_dashed_lines = input_dashed_lines.copy()
        output_dashed_lines.apply_matrix(matrix)

        self.add_foreground_mobjects(system_rect, system)
        self.add_foreground_mobjects(matrix_brace, orthonomal_label)
        self.add_foreground_mobjects(output_vect_mob, output_vect_label)
        self.plane.set_stroke(width=2)

        self.apply_matrix(matrix)
        self.wait()
        self.play(LaggedStart(ShowCreation, output_dashed_lines))
        self.play(*self.get_column_animations(system.matrix_mobject, column_mobs))
        self.wait()
        self.remove(*output_dashed_lines)
        self.apply_inverse(matrix, added_anims=[
            FadeOut(column_mobs),
            ReplacementTransform(output_vect_mob.copy(), input_vect_mob),
            ReplacementTransform(
                output_dashed_lines.copy(), input_dashed_lines),
            FadeIn(input_vect_label),
        ])
        self.wait()
        self.remove(input_dashed_lines, input_vect_mob)
        self.apply_matrix(matrix, added_anims=[
            FadeOut(input_vect_label),
            ReplacementTransform(input_vect_mob.copy(), output_vect_mob),
            ReplacementTransform(input_dashed_lines.copy(),
                                 output_dashed_lines),
            FadeIn(column_mobs),
        ])

        # Write dot product equations
        equations = VGroup()
        for i in 0, 1:
            moving_output_vect_label = output_vect_label.copy()
            moving_column_mob = column_mobs[i].copy()
            moving_var = system.input_vect_mob.elements[i].copy()
            equation = VGroup(
                moving_var.generate_target(),
                TexMobject("="),
                moving_output_vect_label.generate_target(),
                TexMobject("\\cdot"),
                moving_column_mob.generate_target(use_deepcopy=True)
            )
            equation.movers = VGroup(
                moving_var, moving_output_vect_label, moving_column_mob
            )
            for element in moving_column_mob.target.submobject_family():
                if not isinstance(element, TexMobject):
                    continue
                tex_string = element.get_tex_string()
                if "sin" in tex_string or "cos" in tex_string:
                    element.set_stroke(width=1)
                    element.scale(1.25)
            equation.to_write = equation[1::2]
            equation[2].match_height(equation[4])
            equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
            equation.background_rectangle = BackgroundRectangle(equation)
            equation.add_to_back(equation.background_rectangle)
            equations.add(equation)
        equations.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
        equations.scale(1.25)
        equations.to_corner(UR, buff=MED_SMALL_BUFF)
        equations_rect = BackgroundRectangle(equations, buff=MED_LARGE_BUFF)
        equations_rect.set_fill(opacity=0.9)

        for i, equation in enumerate(equations):
            anims = [
                FadeIn(equation.background_rectangle),
                Write(equation.to_write),
                LaggedStart(
                    MoveToTarget, equation.movers,
                    path_arc=60 * DEGREES
                )
            ]
            if i == 0:
                anims.insert(0, FadeIn(equations_rect))
            self.play(*anims)
            self.wait()

    def get_column_animations(self, matrix_mobject, column_mobs):
        def get_kwargs(i):
            return {
                "rate_func": squish_rate_func(smooth, 0.4 * i, 0.6 + 0.4 * i),
                "run_time": 2,
            }
        return list(it.chain(*[
            [
                FadeIn(cm[0], **get_kwargs(i)),
                ReplacementTransform(
                    matrix_mobject.brackets.copy(),
                    cm.brackets,
                    **get_kwargs(i)
                ),
                ReplacementTransform(
                    VGroup(*matrix_mobject.mob_matrix[:, i]).copy(),
                    cm.elements,
                    **get_kwargs(i)
                ),
            ]
            for i, cm in enumerate(column_mobs)
        ]))


class TransitionToParallelogramIdea(TeacherStudentsScene):
    def construct(self):
        teacher_words = TextMobject(
            "Now ask about \\\\ other geometric \\\\ views of",
            "$x$", "and", "$y$"
        )
        teacher_words.set_color_by_tex_to_color_map({
            "$x$": X_COLOR,
            "$y$": Y_COLOR,
        })

        self.student_says(
            "But that's a super \\\\ specific case",
            target_mode="sassy",
            added_anims=[self.teacher.change, "guilty"]
        )
        self.change_student_modes("confused", "sassy", "angry")
        self.wait()
        self.teacher_says(
            teacher_words,
            added_anims=[self.get_student_changes(*["pondering"] * 3)]
        )
        self.wait()


class TransformingAreasYCoord(LinearTransformationScene):
    CONFIG = {
        # Determines whether x-coordinate or y-coordinate is computed
        "index": 1,
        "matrix": [[2, -1], [0, 1]],
        "input_vect": [3, 2],
        "array_scale_factor": 0.7,
    }

    def construct(self):
        self.init_matrix()
        self.init_plane()
        self.show_coord_parallelogram()
        self.transform_space()
        self.solve_for_coord()

    def init_matrix(self):
        self.matrix = np.array(self.matrix)

    def init_plane(self):
        self.plane.set_stroke(width=2)

    def show_coord_parallelogram(self):
        index = self.index
        non_index = (index + 1) % 2

        input_vect = self.input_vect
        input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR)
        input_vect_label = Matrix(["x", "y"])
        input_vect_label.add_background_rectangle()
        input_vect_label.scale(self.array_scale_factor)
        self.set_input_vect_label_position(input_vect_mob, input_vect_label)

        mystery_words = TextMobject("Mystery input vector")
        mystery_words.next_to(input_vect_label, RIGHT)
        mystery_words.add_background_rectangle()

        # Add basis vector labels
        basis_labels = self.basis_labels = VGroup()
        basis_vectors = self.basis_vectors
        chars = ["\\imath", "\\jmath"]
        directions = ["right", "left"]
        for basis, char, direction in zip(basis_vectors, chars, directions):
            label = self.get_vector_label(
                basis, "\\mathbf{\\hat{%s}}" % char,
                direction=direction
            )
            self.basis_labels.add(label)

        ip = self.get_input_parallelogram(input_vect_mob)
        area_arrow_direction = 1.5 * DOWN + RIGHT if self.index == 0 else DR
        area_arrow = Vector(
            area_arrow_direction, color=WHITE,
            rectangular_stem_width=0.025,
            tip_length=0.2,
        )
        area_arrow.shift(ip.get_center() - area_arrow.get_end() + SMALL_BUFF * DL)
        area_words = TexMobject(
            "\\text{Area}", "=", "1", "\\times",
            ["x", "y"][index]
        )
        area_words.next_to(
            area_arrow.get_start(), UL, SMALL_BUFF,
            submobject_to_align=area_words[0]
        )
        area_words.set_color_by_tex_to_color_map({
            "Area": YELLOW,
            "x": X_COLOR,
            "y": Y_COLOR,
        })
        area_words.rect = BackgroundRectangle(area_words)

        origin = self.plane.coords_to_point(0, 0)
        unit_brace = Brace(
            Line(origin, basis_vectors[non_index].get_end()),
            [DOWN, LEFT][non_index],
            buff=SMALL_BUFF
        )
        one = unit_brace.get_tex("1")
        one.add_background_rectangle()
        coord_brace = self.get_parallelogram_braces(ip)[index]

        self.add(input_vect_mob, input_vect_label)
        self.add(basis_labels)

        self.play(
            FadeIn(ip),
            Animation(VGroup(basis_vectors, input_vect_mob))
        )
        self.play(
            ShowCreationThenDestruction(SurroundingRectangle(basis_labels[non_index])),
            GrowArrow(self.basis_vectors[non_index].copy(), remover=True)
        )
        self.play(
            ShowCreationThenDestruction(SurroundingRectangle(input_vect_label)),
            GrowArrow(input_vect_mob.copy(), remover=True),
        )
        self.wait()
        self.play(
            FadeIn(area_words.rect),
            Write(area_words[:2]),
            GrowArrow(area_arrow),
        )
        self.wait()
        self.play(
            FadeOut(basis_labels),
            GrowFromCenter(unit_brace),
            FadeIn(one),
            FadeIn(area_words[2])
        )
        self.wait()
        self.play(
            GrowFromCenter(coord_brace),
            FadeIn(coord_brace.label),
            FadeIn(area_words[3:]),
        )
        self.wait()
        self.play(
            area_words.rect.stretch_to_fit_width,
            area_words[:3].get_width() + SMALL_BUFF, {"about_edge": LEFT},
            FadeOut(area_words[2:4]),
            area_words[4].shift,
            (area_words[2].get_left()[0] - area_words[4].get_left()[0]) * RIGHT,
            Animation(area_words[:2]),
        )
        area_words.remove(*area_words[2:4])
        self.wait()

        # Run with me
        morty = Mortimer(height=2).flip()
        morty.to_corner(DL)
        randy = Randolph(height=2, color=BLUE_C).flip()
        randy.move_to(4 * RIGHT)
        randy.to_edge(DOWN)
        self.play(FadeIn(randy))
        self.play(randy.change, "confused", ip)
        self.play(Blink(randy), FadeIn(morty))
        self.play(
            PiCreatureSays(morty, "Run with \\\\ me here...", look_at_arg=randy.eyes),
            randy.look_at, morty.eyes,
        )
        self.play(Blink(morty))
        self.play(FadeOut(VGroup(morty, morty.bubble, morty.bubble.content, randy)))

        # Signed area
        signed = TextMobject("Signed")
        signed.match_color(area_words[0])
        signed.next_to(area_words, LEFT)

        brace_group = VGroup(coord_brace, coord_brace.label)

        def update_brace_group(brace_group):
            new_brace = self.get_parallelogram_braces(ip)[index]
            new_group = VGroup(new_brace, new_brace.label)
            Transform(brace_group, new_group).update(1)

        area_words.add_to_back(signed)
        self.play(
            area_words.rect.stretch_to_fit_width, area_words.get_width(),
            {"about_edge": RIGHT},
            Write(signed),
            Animation(area_words),
        )
        self.play(
            UpdateFromFunc(
                ip, lambda m: Transform(m, self.get_input_parallelogram(input_vect_mob)).update(1)
            ),
            input_vect_mob.rotate, np.pi, {"about_point": origin},
            Animation(self.basis_vectors),
            UpdateFromFunc(brace_group, update_brace_group),
            UpdateFromFunc(
                input_vect_label,
                lambda ivl: self.set_input_vect_label_position(input_vect_mob, ivl)
            ),
            MaintainPositionRelativeTo(area_arrow, ip),
            MaintainPositionRelativeTo(area_words.rect, area_arrow),
            MaintainPositionRelativeTo(area_words, area_arrow),
            run_time=9,
            rate_func=there_and_back_with_pause,
        )

        # Fade out unneeded bits
        self.play(LaggedStart(FadeOut, VGroup(
            unit_brace, one, coord_brace, coord_brace.label,
        )))

        self.input_parallelogram = ip
        self.area_words = area_words
        self.area_arrow = area_arrow
        self.input_vect_mob = input_vect_mob
        self.input_vect_label = input_vect_label

    def transform_space(self):
        matrix = self.matrix
        ip = self.input_parallelogram
        area_words = self.area_words
        area_arrow = self.area_arrow
        input_vect_mob = self.input_vect_mob
        input_vect_label = self.input_vect_label
        basis_vectors = self.basis_vectors
        index = self.index
        non_index = (index + 1) % 2

        apply_words = TextMobject("Apply")
        apply_words.add_background_rectangle()
        matrix_mobject = IntegerMatrix(self.matrix)
        matrix_mobject.set_color_columns(X_COLOR, Y_COLOR)
        matrix_mobject.add_background_rectangle()
        matrix_mobject.next_to(apply_words, RIGHT)
        matrix_brace = Brace(matrix_mobject, DOWN, buff=SMALL_BUFF)
        matrix_label = matrix_brace.get_tex("A")
        matrix_label.add_background_rectangle()
        apply_group = VGroup(apply_words, matrix_mobject, matrix_brace, matrix_label)
        apply_group.to_corner(UL, buff=MED_SMALL_BUFF)

        area_scale_words = TextMobject("All areas get scaled by", "$\\det(A)$")
        area_scale_words.scale(1.5)
        area_scale_words.move_to(2 * DOWN)
        area_scale_words.add_background_rectangle()

        blobs = VGroup(
            Circle(radius=0.5).move_to(2 * LEFT + UP),
            Square(side_length=1).rotate(TAU / 12).move_to(2 * UP + 0.5 * RIGHT),
            TexMobject("\\pi").scale(3).move_to(3 * RIGHT)
        )
        blobs.set_stroke(YELLOW, 3)
        blobs.set_fill(YELLOW, 0.3)

        # Initial transform
        self.add_transformable_mobject(ip)
        self.add(self.basis_vectors)
        self.add_vector(input_vect_mob, animate=False)
        self.play(
            Write(apply_words), FadeIn(matrix_mobject),
            GrowFromCenter(matrix_brace), Write(matrix_label),
        )
        self.add_foreground_mobjects(apply_group)
        self.play(*map(FadeOut, [
            area_words.rect, area_words, area_arrow, input_vect_label,
        ]))
        self.apply_matrix(matrix)
        self.wait(2)
        self.apply_inverse(matrix, run_time=0)

        # Show many areas
        self.play(
            LaggedStart(DrawBorderThenFill, blobs),
            Write(area_scale_words)
        )
        self.add_transformable_mobject(blobs)
        self.add_foreground_mobject(area_scale_words)
        self.apply_matrix(matrix)

        # Ask about parallelogram
        ip_copy = ip.copy()
        ip_copy.set_stroke(BLACK, 4)
        ip_copy.set_fill(BLACK, 0)

        q_marks = TexMobject("???")
        q_marks.scale(1.5)
        q_marks.rect = BackgroundRectangle(q_marks)
        q_marks_group = VGroup(q_marks, q_marks.rect)
        q_marks_group.rotate(input_vect_mob.get_angle())
        q_marks_group.move_to(ip)

        column_mobs = VGroup()
        for i, vect in zip(range(2), [DOWN, LEFT]):
            column = matrix_mobject.deepcopy().mob_matrix[:, i]
            column_mob = MobjectMatrix(column)
            column_mob.scale(self.array_scale_factor)
            column_mob.next_to(basis_vectors[i].get_end(), vect)
            column_mob.add_background_rectangle()
            column_mobs.add(column_mob)
        column_mob = column_mobs[non_index]

        transformed_input_vect_label = VGroup(input_vect_label.copy())
        transformed_input_vect_label.add_to_back(matrix_label.copy())
        transformed_input_vect_label.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
        transformed_input_vect_label.next_to(input_vect_mob.get_end(), UP)

        self.play(
            ShowPassingFlash(ip_copy),
            FadeIn(q_marks.rect),
            Animation(ip),
            Animation(basis_vectors),
            Animation(input_vect_mob),
            Write(q_marks),
            LaggedStart(FadeOut, blobs),
        )
        self.transformable_mobjects.remove(blobs)
        self.play(
            FadeIn(column_mob.background_rectangle),
            ReplacementTransform(
                matrix_mobject.brackets.copy(), column_mob.brackets
            ),
            ReplacementTransform(
                VGroup(*matrix_mobject.mob_matrix[:, non_index]).copy(),
                column_mob.elements
            ),
        )
        self.wait()
        self.play(
            ReplacementTransform(matrix_label.copy(), transformed_input_vect_label[0]),
            FadeIn(transformed_input_vect_label[1])
        )
        self.wait(2)

        # Back to input state
        self.remove(q_marks.rect)
        self.apply_inverse(matrix, added_anims=[
            FadeOut(q_marks),
            FadeOut(transformed_input_vect_label),
            FadeOut(column_mob),
            FadeIn(area_words.rect),
            FadeIn(area_words),
            FadeIn(area_arrow),
        ])
        self.wait(2)

        # Show how parallelogram scales by det(A)
        self.apply_matrix(matrix, added_anims=[
            UpdateFromFunc(
                area_arrow,
                lambda a: a.put_start_and_end_on(
                    area_arrow.get_start(), ip.get_center() + SMALL_BUFF * DL
                )
            ),
            Animation(area_words.rect),
            Animation(area_words),
        ])

        det_A = area_scale_words.get_part_by_tex("det").copy()
        det_A.generate_target()
        det_A.target.scale(1.0 / 1.5)
        det_A.target.next_to(
            area_words[1:3], RIGHT, SMALL_BUFF,
            aligned_edge=DOWN,
            submobject_to_align=det_A.target[0]
        )
        coord = area_words[-1]
        coord.generate_target()
        coord.target.next_to(det_A.target, RIGHT, SMALL_BUFF)

        self.play(
            area_words.rect.match_width, VGroup(area_words, coord.target),
            {"stretch": True, "about_edge": LEFT},
            Animation(area_words),
            MoveToTarget(det_A),
            MoveToTarget(coord),
        )
        self.wait(2)
        area_words.submobjects.insert(-1, det_A)

        self.area_scale_words = area_scale_words
        self.apply_group = apply_group

    def solve_for_coord(self):
        apply_group = self.apply_group
        area_words = self.area_words.copy()
        index = self.index
        non_index = (index + 1) % 2

        # Setup rearrangement
        signed, area, equals, det, coord = area_words
        for part in area_words:
            part.add_background_rectangle()
            part.generate_target()
        apply_word, matrix_mobject, matrix_brace, matrix_label = apply_group

        h_line = Line(LEFT, RIGHT).match_width(det)
        frac = VGroup(area.target, h_line, det.target)
        frac.arrange_submobjects(DOWN)
        coord_equation = VGroup(coord.target, equals.target, frac)
        equals.target.next_to(coord.target, RIGHT)
        frac.next_to(equals.target, RIGHT, submobject_to_align=h_line)
        coord_equation.next_to(ORIGIN, DOWN, buff=1.2)
        coord_equation.to_edge(LEFT)

        output_vect = np.dot(self.matrix, self.input_vect)
        new_matrix = np.array(self.matrix)
        new_matrix[:, self.index] = output_vect

        # Setup rhs
        frac_matrix_height = 1.5
        matrix_mobject_copy = matrix_mobject.copy()
        matrix_mobject_copy.scale_to_fit_height(frac_matrix_height)
        denom_det_text = get_det_text(matrix_mobject_copy)
        top_matrix_mobject = IntegerMatrix(new_matrix)
        top_matrix_mobject.scale_to_fit_height(frac_matrix_height)
        top_matrix_mobject.set_color_columns(X_COLOR, Y_COLOR)
        VGroup(*top_matrix_mobject.mob_matrix[:, self.index]).set_color(MAROON_B)
        top_matrix_mobject.add_background_rectangle()
        num_det_text = get_det_text(top_matrix_mobject)
        rhs_h_line = Line(LEFT, RIGHT)
        rhs_h_line.match_width(num_det_text)
        rhs = VGroup(
            VGroup(num_det_text, top_matrix_mobject),
            rhs_h_line,
            VGroup(matrix_mobject_copy, denom_det_text)
        )
        rhs.arrange_submobjects(DOWN, buff=SMALL_BUFF)
        rhs_equals = TexMobject("=")
        rhs_equals.next_to(h_line, RIGHT)
        rhs.next_to(rhs_equals, submobject_to_align=rhs_h_line)

        # Setup linear system
        output_vect_label = IntegerMatrix(output_vect)
        output_vect_label.elements.match_color(self.input_vect_mob)
        output_vect_label.scale(self.array_scale_factor)
        output_vect_label.add_background_rectangle()
        self.set_input_vect_label_position(self.input_vect_mob, output_vect_label)

        matrix_mobject.generate_target()
        system_input = Matrix(["x", "y"])
        system_input.add_background_rectangle()
        system_output = output_vect_label.copy()
        system_output.generate_target()
        system_eq = TexMobject("=")
        for array in system_input, system_output.target:
            array.match_height(matrix_mobject.target)
        system = VGroup(
            matrix_mobject.target,
            system_input, system_eq, system_output.target
        )
        system.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
        system.to_corner(UL)

        # Rearrange
        self.play(
            FadeOut(self.area_scale_words),
            ShowCreation(h_line),
            *map(MoveToTarget, area_words[1:]),
            run_time=3
        )
        self.wait()
        self.play(
            ReplacementTransform(matrix_mobject.copy(), matrix_mobject_copy),
            Write(rhs_equals),
            Write(denom_det_text),
            ShowCreation(rhs_h_line)
        )
        self.wait(2)

        # Show output coord
        self.play(
            FadeIn(output_vect_label.background_rectangle),
            ReplacementTransform(
                self.input_vect_mob.copy(),
                output_vect_label.elements,
            ),
            Write(output_vect_label.brackets),
        )
        self.wait()
        self.play(
            FadeOut(apply_word),
            MoveToTarget(matrix_mobject),
            MaintainPositionRelativeTo(matrix_brace, matrix_mobject),
            MaintainPositionRelativeTo(matrix_label, matrix_mobject),
        )
        self.play(
            FadeIn(system_input),
            FadeIn(system_eq),
            MoveToTarget(system_output),
            area_words.rect.stretch_to_fit_width, self.area_words[1:].get_width(),
            {"about_edge": RIGHT},
            Animation(self.area_words[1:]),
            FadeOut(self.area_words[0]),
        )
        self.wait()
        replaced_column = VGroup(*top_matrix_mobject.mob_matrix[:, self.index])
        replaced_column.set_fill(opacity=0)
        self.play(
            ReplacementTransform(matrix_mobject_copy.copy(), top_matrix_mobject),
            ReplacementTransform(denom_det_text.copy(), num_det_text),
        )
        self.wiggle_vector(self.basis_vectors[non_index])
        self.wait()
        self.wiggle_vector(self.input_vect_mob)
        self.remove(replaced_column)
        self.play(
            Transform(
                output_vect_label.elements.copy(),
                replaced_column.copy().set_fill(opacity=1),
                remover=True
            )
        )
        replaced_column.set_fill(opacity=1)
        self.wait()

        # Circle equation
        equation = VGroup(
            area_words[1:], h_line,
            rhs_equals, rhs,
        )
        equation_rect = SurroundingRectangle(equation)
        equation_rect.set_stroke(YELLOW, 3)
        equation_rect.set_fill(BLACK, 0.9)

        self.play(
            DrawBorderThenFill(equation_rect),
            Animation(equation)
        )
        self.wait()

    # Helpers

    def get_input_parallelogram(self, input_vect_mob):
        input_vect = np.array(self.plane.point_to_coords(input_vect_mob.get_end()))
        matrix = self.matrix
        dim = matrix.shape[0]
        # Transofmration from unit square to input parallelogram
        square_to_ip = self.square_to_ip = np.identity(dim)
        square_to_ip[:, self.index] = np.array(input_vect)

        ip = self.get_unit_square()
        ip.apply_matrix(self.square_to_ip)
        if input_vect[self.index] < 0:
            ip.set_color(GOLD)

        return ip

    def get_parallelogram_braces(self, input_parallelogram):
        braces = VGroup(*[
            Brace(
                input_parallelogram, vect,
                buff=SMALL_BUFF,
                min_num_quads=3,
                max_num_quads=3,
            )
            for vect in DOWN, RIGHT
        ])
        for brace, tex, color in zip(braces, "xy", [X_COLOR, Y_COLOR]):
            brace.label = brace.get_tex(tex, buff=SMALL_BUFF)
            brace.label.add_background_rectangle()
            brace.label.set_color(color)
        return braces

    def set_input_vect_label_position(self, input_vect_mob, input_vect_label):
        direction = np.sign(input_vect_mob.get_vector())
        input_vect_label.next_to(input_vect_mob.get_end(), direction, buff=SMALL_BUFF)
        return input_vect_label

    def wiggle_vector(self, vector_mob):
        self.play(
            Rotate(
                vector_mob, 15 * DEGREES,
                about_point=ORIGIN,
                rate_func=wiggle,
            )
        )


class TransformingAreasXCoord(TransformingAreasYCoord):
    CONFIG = {
        "index": 0,
    }


class ThreeDDotVectorWithKHat(ExternallyAnimatedScene):
    pass


class ZEqualsVDotK(Scene):
    def construct(self):
        equation = VGroup(
            TexMobject("z"),
            TexMobject("="),
            Matrix(["x", "y", "z"]),
            TexMobject("\\cdot"),
            IntegerMatrix([0, 0, 1]),
        )
        equation[2].elements.set_color_by_gradient(X_COLOR, Y_COLOR, Z_COLOR)
        equation[4].elements.set_color(BLUE)
        equation.arrange_submobjects(RIGHT, buff=MED_SMALL_BUFF)
        equation.to_edge(LEFT)

        self.play(Write(equation))
        self.wait()


class InputParallelepipedAngledView(ExternallyAnimatedScene):
    pass


class InputParallelepipedTopViewToSideView(ExternallyAnimatedScene):
    pass


class ParallelepipedForXCoordinate(ExternallyAnimatedScene):
    pass


class ParallelepipedForYCoordinate(ExternallyAnimatedScene):
    pass


class ThreeDCoordinatesAsVolumes(Scene):
    def construct(self):
        colors = [X_COLOR, Y_COLOR, Z_COLOR]
        x, y, z = coords = VGroup(*map(TexMobject, "xyz"))
        coords.set_color_by_gradient(*colors)
        matrix = IntegerMatrix(np.identity(3))
        matrix.set_color_columns(*colors)
        det_text = get_det_text(matrix)
        equals = TexMobject("=")
        equals.next_to(det_text[0], LEFT)
        equals.shift(SMALL_BUFF * DOWN)
        coords.next_to(equals, LEFT)

        columns = VGroup(*[
            VGroup(*matrix.mob_matrix[:, i])
            for i in range(3)
        ])

        coord_column = coords.copy()
        for coord, m_elem in zip(coord_column, columns[2]):
            coord.move_to(m_elem)
        coord_column.set_color(WHITE)
        coord_column[2].set_color(BLUE)
        coord_column.generate_target()

        self.play(LaggedStart(FadeIn, VGroup(
            z, equals, det_text, matrix.brackets,
            VGroup(*matrix.mob_matrix[:, :2].flatten()),
            coord_column
        )))
        self.wait(2)
        coord_column.target.move_to(columns[1])
        coord_column.target.set_color_by_gradient(WHITE, Y_COLOR, WHITE)
        self.play(
            MoveToTarget(coord_column, path_arc=np.pi),
            FadeOut(columns[1]),
            FadeIn(columns[2]),
            FadeInFromDown(y),
            z.shift, UP,
            z.fade, 1,
        )
        self.wait(2)
        coord_column.target.move_to(columns[0])
        coord_column.target.set_color_by_gradient(X_COLOR, WHITE, WHITE)
        self.play(
            MoveToTarget(coord_column, path_arc=np.pi),
            FadeOut(columns[0]),
            FadeIn(columns[1]),
            FadeInFromDown(x),
            y.shift, UP,
            y.fade, 1,
        )
        self.wait(2)


class WriteCramersRule(Scene):
    def construct(self):
        words = TextMobject("``Cramer's Rule''")
        words.scale_to_fit_width(FRAME_WIDTH - LARGE_BUFF)
        words.add_background_rectangle()
        self.play(Write(words))
        self.wait()


class CramersYEvaluation(Scene):
    def construct(self):
        frac = TexMobject("{(2)(2) - (4)(0) \\over (2)(1) - (-1)(0)}")
        VGroup(frac[1], frac[11], frac[15], frac[26]).set_color(GREEN)
        VGroup(frac[4], frac[8]).set_color(MAROON_B)
        VGroup(frac[18], frac[22], frac[23]).set_color(RED)

        group = VGroup(
            TexMobject("="), frac,
            TexMobject("= \\frac{4}{2}"), TexMobject("=2")
        )
        group.arrange_submobjects(RIGHT)
        group.add_to_back(BackgroundRectangle(group))

        self.add(group)
        self.wait(1)


# Largely copy-pasted.  Not great, but what are you gonna do.
class CramersXEvaluation(Scene):
    def construct(self):
        frac = TexMobject("{(4)(1) - (-1)(2) \\over (2)(1) - (-1)(0)}")
        VGroup(frac[1], frac[12]).set_color(MAROON_B)
        VGroup(frac[4], frac[8], frac[9]).set_color(RED)
        VGroup(frac[16], frac[27]).set_color(GREEN)
        VGroup(frac[19], frac[23], frac[24]).set_color(RED)

        group = VGroup(
            TexMobject("="), frac,
            TexMobject("= \\frac{6}{2}"), TexMobject("=3")
        )
        group.arrange_submobjects(RIGHT)
        group.add_to_back(BackgroundRectangle(group))

        self.add(group)
        self.wait(1)


class Equals2(Scene):
    def construct(self):
        self.add(TexMobject("=2").add_background_rectangle())
        self.wait()


class Equals3(Scene):
    def construct(self):
        self.add(TexMobject("=3").add_background_rectangle())
        self.wait()


class Introduce3DSystem(SetupSimpleSystemOfEquations):
    CONFIG = {
        "matrix": [[3, 2, 7], [-1, 2, -4], [4, 0, 1]],
        "output_vect": [-4, -2, 5],
        "compare_to_big_system": False,
        "transition_to_geometric_view": False,
    }

    def construct(self):
        self.remove_grid()
        self.introduce_system()
        self.from_system_to_matrix()


class ThinkItThroughYourself(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Try thinking \\\\ it through!",
            target_mode="hooray"
        )
        self.change_all_student_modes("pondering")
        self.wait(4)


class TransformParallelepipedWithoutGrid(ExternallyAnimatedScene):
    pass


class TransformParallelepipedWithGrid(ExternallyAnimatedScene):
    pass


class AreYouPausingAndPondering(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Are you pausing \\\\ and pondering?",
            target_mode="sassy",
            added_anims=[self.get_student_changes(*3 * ["guilty"])]
        )
        self.wait()
        self.change_all_student_modes(
            "thinking",
            added_anims=[
                RemovePiCreatureBubble(self.teacher, target_mode="raise_right_hand")
            ],
            look_at_arg=self.screen
        )
        self.wait(6)


class Thumbnail(TransformingAreasYCoord):
    def construct(self):
        self.matrix = np.array(self.matrix)
        vect = self.add_vector([1, 1.5], color=MAROON_B)
        ip = self.get_input_parallelogram(vect)
        self.add_transformable_mobject(ip)
        self.apply_transposed_matrix([[2, -0.5], [1, 2]])

        words = TextMobject("Cramer's", "rule")
        words.scale_to_fit_width(7)
        # words.add_background_rectangle_to_parts()
        words.add_background_rectangle()
        words.to_edge(UP)
        self.add(words)
