from big_ol_pile_of_manim_imports import *
from old_projects.eola.chapter3 import MatrixVectorMultiplicationAbstract


class Blob(Circle):
    CONFIG = {
        "stroke_color" : TEAL,
        "fill_color" : BLUE_E,
        "fill_opacity" : 1,
        "random_seed" : 1,
        "random_nudge_size" : 0.5,
        "height" : 2,
    }
    def __init__(self, **kwargs):
        Circle.__init__(self, **kwargs)
        random.seed(self.random_seed)
        self.apply_complex_function(
            lambda z : z*(1+self.random_nudge_size*(random.random()-0.5))
        )
        self.set_height(self.height).center()

    def probably_contains(self, point):
        border_points = np.array(self.get_anchors_and_handles()[0])
        distances = [get_norm(p-point) for p in border_points]
        min3 = border_points[np.argsort(distances)[:3]]
        center_direction = self.get_center() - point
        in_center_direction = [np.dot(p-point, center_direction) > 0 for p in min3]
        return sum(in_center_direction) <= 2
            
class RightHand(VMobject):
    def __init__(self, **kwargs):
        hand = SVGMobject("RightHandOutline")
        self.inlines = VMobject(*hand.split()[:-4])
        self.outline = VMobject(*hand.split()[-4:])
        self.outline.set_stroke(color = WHITE, width = 5)
        self.inlines.set_stroke(color = DARK_GREY, width = 3)
        VMobject.__init__(self, self.outline, self.inlines)
        self.center().set_height(3)

class OpeningQuote(Scene):
    def construct(self):
        words = TextMobject([
            "``The purpose of computation is \\\\",
            "insight",
            ", not ",
            "numbers.",
            "''",
        ], arg_separator = "")
        # words.set_width(FRAME_WIDTH - 2)
        words.to_edge(UP)
        words.split()[1].set_color(BLUE)
        words.split()[3].set_color(GREEN)
        author = TextMobject("-Richard Hamming")
        author.set_color(YELLOW)
        author.next_to(words, DOWN, buff = 0.5)

        self.play(FadeIn(words))
        self.wait(2)
        self.play(Write(author, run_time = 3))
        self.wait()

class MovingForward(TeacherStudentsScene):
    def construct(self):
        self.setup()
        student = self.get_students()[1]
        bubble = student.get_bubble(direction = RIGHT, width = 5)
        bubble.rotate(-np.pi/12)
        bubble.next_to(student, UP, aligned_edge = RIGHT)
        bubble.shift(0.5*LEFT)
        bubble.make_green_screen()

        self.teacher_says("""
            Y'all know about linear
            transformations, right?
        """, width = 7)
        self.play(
            ShowCreation(bubble),
            student.change_mode, "pondering"
        )
        self.wait(2)

class StretchingTransformation(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_title("Generally stretches space")
        self.apply_transposed_matrix([[3, 1], [-1, 2]])
        self.wait()

class SquishingTransformation(LinearTransformationScene):
    CONFIG = {
        "foreground_plane_kwargs" : {
            "x_radius" : 3*FRAME_X_RADIUS,
            "y_radius" : 3*FRAME_X_RADIUS,
            "secondary_line_ratio" : 0
        },
    }
    def construct(self):
        self.setup()
        self.add_title("Generally squishes space")
        self.apply_transposed_matrix([[1./2, -0.5], [1, 1./3]])
        self.wait()

class AskAboutStretching(LinearTransformationScene):
    def construct(self):
        self.setup()
        words = TextMobject("""
            Exactly how much are 
            things being stretched?
        """)
        words.add_background_rectangle()
        words.to_corner(UP+RIGHT)
        words.set_color(YELLOW)
        self.apply_transposed_matrix(
            [[2, 1], [-1, 3]],
            added_anims = [Write(words)]
        )
        self.wait()

class AskAboutStretchingSpecifically(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_title(["How much are", "areas", "scaled?"])
        hma, areas, scaled = self.title.split()[1].split()
        areas.set_color(YELLOW)
        blob = Blob().shift(UP+RIGHT)

        label = TextMobject("Area")
        label.set_color(YELLOW)
        label = VMobject(VectorizedPoint(label.get_left()), label)
        label.move_to(blob)
        target_label = TexMobject(["c \\cdot", "\\text{Area}"])
        target_label.split()[1].set_color(YELLOW)

        self.add_transformable_mobject(blob)
        self.add_moving_mobject(label, target_label)
        self.wait()
        self.apply_transposed_matrix([[2, -1], [1, 1]])
        arrow = Arrow(scaled, label.target.split()[0])
        self.play(ShowCreation(arrow))
        self.wait()

class BeautyNowUsesLater(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.teacher_says("Beauty now, uses later")
        self.wait()

class DiagonalExample(LinearTransformationScene):
    CONFIG = {
        "show_square" : False, 
        "show_coordinates" : True,
        "transposed_matrix" : [[3, 0], [0, 2]]
    }
    def construct(self):
        self.setup()
        matrix = Matrix(np.array(self.transposed_matrix).transpose())
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        matrix.next_to(ORIGIN, LEFT).to_edge(UP)
        matrix_background = BackgroundRectangle(matrix)
        self.play(ShowCreation(matrix_background), Write(matrix))
        if self.show_square:
            self.add_unit_square(animate = True)
        self.add_foreground_mobject(matrix_background, matrix)
        self.wait()
        self.apply_transposed_matrix([self.transposed_matrix[0], [0, 1]])
        self.apply_transposed_matrix([[1, 0], self.transposed_matrix[1]])
        self.wait()
        if self.show_square:


            bottom_brace = Brace(self.i_hat, DOWN)
            right_brace = Brace(self.square, RIGHT)
            width = TexMobject(str(self.transposed_matrix[0][0]))
            height = TexMobject(str(self.transposed_matrix[1][1]))
            width.next_to(bottom_brace, DOWN)
            height.next_to(right_brace, RIGHT)
            for mob in bottom_brace, width, right_brace, height:
                mob.add_background_rectangle()
                self.play(Write(mob, run_time = 0.5))
            self.wait()

            width_target, height_target = width.copy(), height.copy()
            det = np.linalg.det(self.transposed_matrix)
            times, eq_det = list(map(TexMobject, ["\\times", "=%d"%det]))
            words = TextMobject("New area $=$")
            equation = VMobject(
                words, width_target, times, height_target, eq_det
            )
            equation.arrange(RIGHT, buff = 0.2)
            equation.next_to(self.square, UP, aligned_edge = LEFT)
            equation.shift(0.5*RIGHT)
            background_rect = BackgroundRectangle(equation)

            self.play(
                ShowCreation(background_rect),                
                Transform(width.copy(), width_target),
                Transform(height.copy(), height_target),
                *list(map(Write, [words, times, eq_det]))
            )
            self.wait()

class DiagonalExampleWithSquare(DiagonalExample):
    CONFIG = {
        "show_square" : True
    }

class ShearExample(DiagonalExample):
    CONFIG = {
        "show_square" : False, 
        "show_coordinates" : True,
        "transposed_matrix" : [[1, 0], [1, 1]]
    }

class ShearExampleWithSquare(DiagonalExample):
    CONFIG = {
        "show_square" : True, 
        "show_coordinates" : True,
        "show_coordinates" : False,
        "transposed_matrix" : [[1, 0], [1, 1]]
    }

class ThisSquareTellsEverything(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_unit_square()
        words = TextMobject("""
            This square gives you
            everything you need.
        """)
        words.to_corner(UP+RIGHT)
        words.set_color(YELLOW)
        words.add_background_rectangle()
        arrow = Arrow(
            words.get_bottom(), self.square.get_right(), 
            color = WHITE
        )

        self.play(Write(words, run_time = 2))
        self.play(ShowCreation(arrow))
        self.add_foreground_mobject(words, arrow)
        self.wait()
        self.apply_transposed_matrix([[1.5, -0.5], [1, 1.5]])
        self.wait()

class WhatHappensToOneSquareHappensToAll(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_unit_square()
        pairs = [
            (2*RIGHT+UP, 1),
            (3*LEFT, 2),
            (2*LEFT+DOWN, 0.5),          
            (3.5*RIGHT+2.5*UP, 1.5),
            (RIGHT+2*DOWN, 0.25),
            (3*LEFT+3*DOWN, 1),
        ]
        squares = VMobject()
        for position, side_length in pairs:
            square = self.square.copy()
            square.scale(side_length)
            square.shift(position)
            squares.add(square)
        self.play(FadeIn(
            squares, lag_ratio = 0.5,
            run_time = 3
        ))
        self.add_transformable_mobject(squares)
        self.apply_transposed_matrix([[1, -1], [0.5, 1]])
        self.wait()

class BreakBlobIntoGridSquares(LinearTransformationScene):
    CONFIG = {
        "square_size" : 0.5,
        "blob_height" : 3,
    }
    def construct(self):
        self.setup()
        blob = Blob(
            height = self.blob_height, 
            random_seed = 5,
            random_nudge_size = 0.2,
        )
        blob.next_to(ORIGIN, UP+RIGHT)
        self.add_transformable_mobject(blob)
        arange = np.arange(
            0, self.blob_height + self.square_size, 
            self.square_size
        )
        square = Square(side_length = self.square_size)
        square.set_stroke(YELLOW, width = 2)
        square.set_fill(YELLOW, opacity = 0.3)
        squares = VMobject()
        for x, y in it.product(*[arange]*2):
            point = x*RIGHT + y*UP
            if blob.probably_contains(point):
                squares.add(square.copy().shift(point))
        self.play(ShowCreation(
            squares, lag_ratio = 0.5,
            run_time = 2,
        ))
        self.add_transformable_mobject(squares)
        self.wait()
        self.apply_transposed_matrix([[1, -1], [0.5, 1]])
        self.wait()

class BreakBlobIntoGridSquaresGranular(BreakBlobIntoGridSquares):
    CONFIG = {
        "square_size" : 0.25
    }

class BreakBlobIntoGridSquaresMoreGranular(BreakBlobIntoGridSquares):
    CONFIG = {
        "square_size" : 0.15
    }    

class BreakBlobIntoGridSquaresVeryGranular(BreakBlobIntoGridSquares):
    CONFIG = {
        "square_size" : 0.1
    }

class NameDeterminant(LinearTransformationScene):
    CONFIG = {
        "t_matrix" : [[3, 0], [2, 2]]
    }
    def construct(self):
        self.setup()
        self.plane.fade(0.3)
        self.add_unit_square(color = YELLOW_E, opacity = 0.5)
        self.add_title(
            ["The", "``determinant''", "of a transformation"],
            scale_factor = 1
        )
        self.title.split()[1].split()[1].set_color(YELLOW)

        matrix_background, matrix, det_text = self.get_matrix()
        self.add_foreground_mobject(matrix_background, matrix)

        A = TexMobject("A")
        area_label = VMobject(A.copy(), A.copy(), A)
        area_label.move_to(self.square)
        det = np.linalg.det(self.t_matrix)
        if np.round(det) == det:
            det = int(det)
        area_label_target = VMobject(
            TexMobject(str(det)), TexMobject("\\cdot"), A.copy()
        )
        if det < 1 and det > 0:
            area_label_target.scale(det)
        area_label_target.arrange(RIGHT, buff = 0.1)
        self.add_moving_mobject(area_label, area_label_target)
        
        self.wait()
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()
        det_mob_copy = area_label.split()[0].copy()
        new_det_mob = det_mob_copy.copy().set_height(
            det_text.split()[0].get_height()
        )
        new_det_mob.next_to(det_text, RIGHT, buff = 0.2)
        new_det_mob.add_background_rectangle()
        det_mob_copy.add_background_rectangle(opacity = 0)
        self.play(Write(det_text))
        self.play(Transform(det_mob_copy, new_det_mob))
        self.wait()


    def get_matrix(self):
        matrix = Matrix(np.array(self.t_matrix).transpose())
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        matrix.next_to(self.title, DOWN, buff = 0.5)
        matrix.shift(2*LEFT)
        matrix_background = BackgroundRectangle(matrix)
        det_text = get_det_text(matrix, 0)
        det_text.remove(det_text.split()[-1])
        return matrix_background, matrix, det_text

class SecondDeterminantExample(NameDeterminant):
    CONFIG = {
        "t_matrix" : [[-1, -1], [1, -1]]
    }

class DeterminantIsThree(NameDeterminant):
    CONFIG = {
        "t_matrix" : [[0, -1.5], [2, 1]]
    }

class DeterminantIsOneHalf(NameDeterminant):
    CONFIG = {
        "t_matrix" : [[0.5, -0.5], [0.5, 0.5]],
        "foreground_plane_kwargs" : {
            "x_radius" : FRAME_WIDTH,
            "y_radius" : FRAME_WIDTH,
            "secondary_line_ratio" : 0
        },
    }

class DeterminantIsZero(NameDeterminant):
    CONFIG = {
        "t_matrix" : [[4, 2], [2, 1]],
    }

class SecondDeterminantIsZeroExamlpe(NameDeterminant):
    CONFIG = {
        "t_matrix" : [[0, 0], [0, 0]],
        "show_basis_vectors" : False
    }

class NextFewVideos(Scene):
    def construct(self):
        icon = SVGMobject("video_icon")
        icon.center()
        icon.set_width(FRAME_WIDTH/12.)
        icon.set_stroke(color = WHITE, width = 0)
        icon.set_fill(WHITE, opacity = 1)
        icons = VMobject(*[icon.copy() for x in range(10)])
        icons.set_submobject_colors_by_gradient(BLUE_A, BLUE_D)
        icons.arrange(RIGHT)
        icons.to_edge(LEFT)

        self.play(
            FadeIn(icons, lag_ratio = 0.5),
            run_time = 3
        )
        self.wait()

class UnderstandingBeforeApplication(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.teacher_says("""
            Just the visual 
            understanding for now
        """)
        self.random_blink()
        self.wait()

class WhatIveSaidSoFar(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.teacher_says("""
            What I've said so far
            is not quite right...
        """)
        self.wait()

class NegativeDeterminant(Scene):
    def construct(self):
        numerical_matrix = [[1, 2], [3, 4]]
        matrix = Matrix(numerical_matrix)
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        det_text = get_det_text(matrix, np.linalg.det(numerical_matrix))
        words = TextMobject("""
            How can you scale area
            by a negative number?
        """)
        words.set_color(YELLOW)
        words.to_corner(UP+RIGHT)
        det_num = det_text.split()[-1]
        arrow = Arrow(words.get_bottom(), det_num)

        self.add(matrix)
        self.play(Write(det_text))
        self.wait()
        self.play(
            Write(words, run_time = 2),
            ShowCreation(arrow)
        )
        self.play(det_num.set_color, YELLOW)
        self.wait()

class FlipSpaceOver(Scene):
    def construct(self):
        plane1 = NumberPlane(y_radius = FRAME_X_RADIUS)
        plane2 = NumberPlane(
            y_radius = FRAME_X_RADIUS,
            color = RED_D, secondary_color = RED_E
        )
        axis = UP
        for word, plane in ("Front", plane1), ("Back", plane2):
            text = TextMobject(word)
            if word == "Back":
                text.rotate(np.pi, axis = axis)
            text.scale(2)
            text.next_to(ORIGIN, RIGHT).to_edge(UP)
            text.add_background_rectangle()
            plane.add(text)

        self.play(ShowCreation(
            plane1, lag_ratio = 0.5,
            run_time = 1
        ))
        self.wait()
        self.play(Rotate(
            plane1, axis = axis,
            rate_func = lambda t : smooth(t/2),
            run_time = 1.5,
            path_arc = np.pi/2,
        ))
        self.remove(plane1)
        self.play(Rotate(
            plane2, axis = axis,
            rate_func = lambda t : smooth((t+1)/2),
            run_time = 1.5,
            path_arc = np.pi/2,
        ))
        self.wait()

class RandyThinking(Scene):
    def construct(self):
        randy = Randolph().to_corner()
        bubble = randy.get_bubble()
        bubble.make_green_screen()

        self.play(
            randy.change_mode, "pondering",
            ShowCreation(bubble)
        )
        self.wait()
        self.play(Blink(randy))
        self.wait(2)
        self.play(Blink(randy))

class NegativeDeterminantTransformation(LinearTransformationScene):
    CONFIG = {
        "t_matrix" : [[1, 1], [2, -1]],
    }
    def construct(self):
        self.setup()
        self.add_title("Feels like flipping space")
        self.wait()
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()

class ThinkAboutFlippingPaper(Scene):
    def construct(self):
        pass

class NegativeDeterminantTransformation2(NegativeDeterminantTransformation):
    CONFIG  ={
        "t_matrix" : [[-2, 1], [2, 1]]
    }

class IHatJHatOrientation(NegativeDeterminantTransformation):
    def construct(self):
        self.setup()
        i_label, j_label = self.get_basis_vector_labels()
        self.add_transformable_label(self.i_hat, i_label, color = X_COLOR)
        self.add_transformable_label(self.j_hat, j_label, color = Y_COLOR)

        arc = Arc(start_angle = 0, angle = np.pi/2, color = YELLOW)
        arc.shift(0.5*(RIGHT+UP)).scale(1/1.6)
        arc.add_tip()
        words1 = TextMobject([
            "$\\hat{\\jmath}$",
            "is to the",
            "left",
            "of",
            "$\\hat{\\imath}$",
        ])
        words1.split()[0].set_color(Y_COLOR)
        words1.split()[2].set_color(YELLOW)
        words1.split()[-1].set_color(X_COLOR)
        words1.add_background_rectangle()
        words1.next_to(arc, UP+RIGHT)

        words2 = TextMobject([
            "$L(\\hat{\\jmath})$",
            "is to the \\\\",
            "\\emph{right}",
            "of",
            "$L(\\hat{\\imath})$",
        ])
        words2.split()[0].set_color(Y_COLOR)
        words2.split()[2].set_color(YELLOW)
        words2.split()[-1].set_color(X_COLOR)
        words2.add_background_rectangle()


        self.play(ShowCreation(arc))
        self.play(Write(words1))
        self.wait()
        self.remove(words1, arc)
        self.apply_transposed_matrix(self.t_matrix)
        arc.submobjects = []
        arc.apply_function(self.get_matrix_transformation(self.t_matrix))
        arc.add_tip()
        words2.next_to(arc, RIGHT)
        self.play(
            ShowCreation(arc),
            Write(words2, run_time = 2),
        )
        self.wait()
        title = TextMobject("Orientation has been reversed")
        title.to_edge(UP)
        title.add_background_rectangle()
        self.play(Write(title, run_time = 1))
        self.wait()

class WriteNegativeDeterminant(NegativeDeterminantTransformation):
    def construct(self):
        self.setup()
        self.add_unit_square()
        matrix = Matrix(np.array(self.t_matrix).transpose())
        matrix.next_to(ORIGIN, LEFT)
        matrix.to_edge(UP)
        matrix.set_column_colors(X_COLOR, Y_COLOR)

        det_text = get_det_text(
            matrix, determinant = np.linalg.det(self.t_matrix)
        )
        three = VMobject(*det_text.split()[-1].split()[1:])
        for mob in det_text.split():
            if isinstance(mob, TexMobject):
                mob.add_background_rectangle()
        matrix_background = BackgroundRectangle(matrix)
        self.play(
            ShowCreation(matrix_background),
            Write(matrix),
            Write(det_text),
        )
        self.add_foreground_mobject(matrix_background, matrix, det_text)
        self.wait()
        self.apply_transposed_matrix(self.t_matrix)

        self.play(three.copy().move_to, self.square)
        self.wait()

class AltWriteNegativeDeterminant(WriteNegativeDeterminant):
    CONFIG = {
        "t_matrix" : [[2, -1], [1, -3]]
    }

class WhyNegativeScaling(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.student_says("""
            Why does negative area 
            relate to orientation-flipping? 
        """)
        other_students = np.array(self.get_students())[[0, 2]]
        self.play(*[
            ApplyMethod(student.change_mode, "confused")
            for student in other_students
        ])
        self.random_blink()
        self.wait()
        self.random_blink()

class SlowlyRotateIHat(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_unit_square()
        self.apply_transposed_matrix(
            [[-1, 0], [0, 1]],
            path_arc = np.pi,
            run_time = 30,
            rate_func=linear,
        )

class DeterminantGraphForRotatingIHat(Scene):
    def construct(self):
        t_axis = NumberLine(
            numbers_with_elongated_ticks = [],
            x_min = 0,
            x_max = 10,
            color = WHITE,
        )
        det_axis = NumberLine(
            numbers_with_elongated_ticks = [],
            x_min = -2,
            x_max = 2,
            color = WHITE
        )
        det_axis.rotate(np.pi/2)
        t_axis.next_to(ORIGIN, RIGHT, buff = 0)
        det_axis.move_to(t_axis.get_left())
        axes = VMobject(det_axis, t_axis)
        graph = FunctionGraph(np.cos, x_min = 0, x_max = np.pi)
        graph.next_to(det_axis, RIGHT, buff = 0)
        graph.set_color(YELLOW)
        det_word = TextMobject("Det")
        det_word.next_to(det_axis, RIGHT, aligned_edge = UP)
        time_word = TextMobject("time")
        time_word.next_to(t_axis, UP)
        time_word.to_edge(RIGHT)
        everything = VMobject(axes, det_word, time_word, graph)
        everything.scale(1.5)

        self.add(axes, det_word, time_word)
        self.play(ShowCreation(
            graph, rate_func=linear, run_time = 10
        ))

class WhatAboutThreeDimensions(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.student_says("""
            What about 3D 
            transformations?
        """)
        self.random_blink()
        self.wait()
        self.random_blink()

class Transforming3DCube(Scene):
    def construct(self):
        pass

class NameParallelepiped(Scene):
    def construct(self):
        word = TextMobject("``Parallelepiped''")
        word.scale(2)
        pp_part1 = VMobject(*word.split()[:len(word.split())/2])
        pp_part2 = VMobject(*word.split()[len(word.split())/2:])
        pp_part1.set_submobject_colors_by_gradient(X_COLOR, Y_COLOR)
        pp_part2.set_submobject_colors_by_gradient(Y_COLOR, Z_COLOR)
        self.play(Write(word))
        self.wait(2)

class DeterminantIsVolumeOfParallelepiped(Scene):
    def construct(self):
        matrix = Matrix([[1, 0, 0.5], [0.5, 1, 0], [1, 0, 1]])
        matrix.shift(3*LEFT)
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        det_text = get_det_text(matrix)
        eq = TexMobject("=")
        eq.next_to(det_text, RIGHT)
        words = TextMobject([
            "Volume of this\\\\",
            "parallelepiped"
        ])
        pp = words.split()[1]
        pp_part1 = VMobject(*pp.split()[:len(pp.split())/2])
        pp_part2 = VMobject(*pp.split()[len(pp.split())/2:])
        pp_part1.set_submobject_colors_by_gradient(X_COLOR, Y_COLOR)
        pp_part2.set_submobject_colors_by_gradient(Y_COLOR, Z_COLOR)

        words.next_to(eq, RIGHT)

        self.play(Write(matrix))
        self.wait()
        self.play(Write(det_text), Write(words), Write(eq))
        self.wait()

class Degenerate3DTransformation(Scene):
    def construct(self):
        pass

class WriteZeroDeterminant(Scene):
    def construct(self):
        matrix = Matrix([[1, 0, 1], [0.5, 1, 1.5], [1, 0, 1]])
        matrix.shift(2*LEFT)
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        det_text = get_det_text(matrix, 0)
        brace = Brace(matrix, DOWN)
        words = TextMobject("""
            Columns must be
            linearly dependent
        """)
        words.set_color(YELLOW)
        words.next_to(brace, DOWN)

        self.play(Write(matrix))
        self.wait()
        self.play(Write(det_text))
        self.wait()
        self.play(
            GrowFromCenter(brace),
            Write(words, run_time = 2)
        )
        self.wait()

class AskAboutNegaive3DDeterminant(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.student_says("""
            What would det$(M) < 0$ mean?
        """)
        self.random_blink()
        self.play(self.teacher.change_mode, "pondering")
        self.wait()
        self.random_blink()

class OrientationReversing3DTransformation(Scene):
    def construct(self):
        pass

class RightHandRule(Scene):
    CONFIG = {
        "flip" : False,
        "labels_tex" : ["\\hat{\\imath}", "\\hat{\\jmath}", "\\hat{k}"],
        "colors" : [X_COLOR, Y_COLOR, Z_COLOR],
    }
    def construct(self):
        hand = RightHand()
        v1 = Vector([-1.75, 0.5])
        v2 = Vector([-1.4, -0.7])
        v3 = Vector([0, 1.7])
        vects = [v1, v2, v3]        
        if self.flip:
            VMobject(hand, *vects).flip()

        v1_label, v2_label, v3_label = [
            TexMobject(label_tex).scale(1.5)
            for label_tex in self.labels_tex
        ]
        v1_label.next_to(v1.get_end(), UP)
        v2_label.next_to(v2.get_end(), DOWN)
        v3_label.next_to(v3.get_end(), UP)

        labels = [v1_label, v2_label, v3_label]

        # self.add(NumberPlane())
        self.play(
            ShowCreation(hand.outline, run_time = 2, rate_func=linear),
            FadeIn(hand.inlines)
        )
        self.wait()
        for vect, label, color in zip(vects, labels, self.colors):
            vect.set_color(color)
            label.set_color(color)
            vect.set_stroke(width = 8)
            self.play(ShowCreation(vect))
            self.play(Write(label))
            self.wait()

class LeftHandRule(RightHandRule):
    CONFIG = {
        "flip" : True
    }

class AskHowToCompute(TeacherStudentsScene):
    def construct(self):
        self.setup()
        student = self.get_students()[1]
        self.student_says("How do you \\\\ compute this?")
        self.play(student.change_mode, "confused")
        self.random_blink()
        self.wait()
        self.random_blink()

class TwoDDeterminantFormula(Scene):
    def construct(self):
        eq = TextMobject("=")
        matrix = Matrix([["a", "b"], ["c", "d"]])
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        ma, mb, mc, md = matrix.get_entries().split()
        ma.shift(0.1*DOWN)
        mc.shift(0.7*mc.get_height()*DOWN)
        det_text = get_det_text(matrix)
        VMobject(matrix, det_text).next_to(eq, LEFT)
        formula = TexMobject(list("ad-bc"))
        formula.next_to(eq, RIGHT)
        formula.shift(0.1*UP)

        a, d, minus, b, c = formula.split()
        VMobject(a, c).set_color(X_COLOR)
        VMobject(b, d).set_color(Y_COLOR)

        for mob in mb, mc, b, c:
            if mob is c:
                mob.zero = TexMobject("\\cdot 0")
            else:
                mob.zero = TexMobject("0")
            mob.zero.move_to(mob, aligned_edge = DOWN+LEFT)
            mob.zero.set_color(mob.get_color())
            mob.original = mob.copy()
        c.zero.shift(0.1*RIGHT)

        self.add(matrix)
        self.play(Write(det_text, run_time = 1))
        self.play(Write(eq), Write(formula))
        self.wait()
        self.play(*[
            Transform(m, m.zero)
            for m in (mb, mc, b, c)
        ])
        self.wait()
        for pair in (mb, b), (mc, c):
            self.play(*[
                Transform(m, m.original)
                for m in pair
            ])
            self.wait()

class TwoDDeterminantFormulaIntuition(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_unit_square()
        a, b, c, d = 3, 2, 3.5, 2

        self.wait()
        self.apply_transposed_matrix([[a, 0], [0, 1]])
        i_brace = Brace(self.i_hat, DOWN)
        width = TexMobject("a").scale(1.5)
        i_brace.put_at_tip(width)
        width.set_color(X_COLOR)
        width.add_background_rectangle()
        self.play(GrowFromCenter(i_brace), Write(width))
        self.wait()

        self.apply_transposed_matrix([[1, 0], [0, d]])
        side_brace = Brace(self.square, RIGHT)
        height = TexMobject("d").scale(1.5)
        side_brace.put_at_tip(height)
        height.set_color(Y_COLOR)
        height.add_background_rectangle()
        self.play(GrowFromCenter(side_brace), Write(height))
        self.wait()

        self.apply_transposed_matrix(
            [[1, 0], [float(b)/d, 1]],
            added_anims = [
                ApplyMethod(m.shift, b*RIGHT)
                for m in (side_brace, height)
            ]
        )
        self.wait()
        self.play(*list(map(FadeOut, [i_brace, side_brace, width, height])))
        matrix1 = np.dot(
            [[a, b], [c, d]],
            np.linalg.inv([[a, b], [0, d]])
        )
        matrix2 = np.dot(
            [[a, b], [-c, d]],
            np.linalg.inv([[a, b], [c, d]])
        )
        self.apply_transposed_matrix(matrix1.transpose(), path_arc = 0)
        self.wait()
        self.apply_transposed_matrix(matrix2.transpose(), path_arc = 0)
        self.wait()

class FullFormulaExplanation(LinearTransformationScene):
    def construct(self):
        self.setup()
        self.add_unit_square()
        self.apply_transposed_matrix([[3, 1], [1, 2]], run_time = 0)
        self.add_braces()        
        self.add_polygons()
        self.show_formula()

    def get_matrix(self):
        matrix = Matrix([["a", "b"], ["c", "d"]])
        matrix.set_column_colors(X_COLOR, Y_COLOR)
        ma, mb, mc, md = matrix.get_entries().split()
        ma.shift(0.1*DOWN)
        mc.shift(0.7*mc.get_height()*DOWN)
        matrix.shift(2*DOWN+4*LEFT)
        return matrix

    def add_polygons(self):
        a = self.i_hat.get_end()[0]*RIGHT
        b = self.j_hat.get_end()[0]*RIGHT
        c = self.i_hat.get_end()[1]*UP
        d = self.j_hat.get_end()[1]*UP

        shapes_colors_and_tex = [
            (Polygon(ORIGIN, a, a+c), MAROON, "ac/2"),
            (Polygon(ORIGIN, d+b, d, d), TEAL, "\\dfrac{bd}{2}"),
            (Polygon(a+c, a+b+c, a+b+c, a+b+c+d), TEAL, "\\dfrac{bd}{2}"),
            (Polygon(b+d, a+b+c+d, b+c+d), MAROON, "ac/2"),
            (Polygon(a, a+b, a+b+c, a+c), PINK, "bc"),
            (Polygon(d, d+b, d+b+c, d+c), PINK, "bc"),
        ]
        everyone = VMobject()
        for shape, color, tex in shapes_colors_and_tex:
            shape.set_stroke(width = 0)
            shape.set_fill(color = color, opacity = 0.7)
            tex_mob = TexMobject(tex)
            tex_mob.scale(0.7)
            tex_mob.move_to(shape.get_center_of_mass())
            everyone.add(shape, tex_mob)
        self.play(FadeIn(
            everyone, 
            lag_ratio = 0.5,
            run_time = 1
        ))



    def add_braces(self):
        a = self.i_hat.get_end()[0]*RIGHT
        b = self.j_hat.get_end()[0]*RIGHT
        c = self.i_hat.get_end()[1]*UP
        d = self.j_hat.get_end()[1]*UP

        quads = [
            (ORIGIN, a, DOWN, "a"),
            (a, a+b, DOWN, "b"),
            (a+b, a+b+c, RIGHT, "c"),
            (a+b+c, a+b+c+d, RIGHT, "d"),
            (a+b+c+d, b+c+d, UP, "a"),
            (b+c+d, d+c, UP, "b"),
            (d+c, d, LEFT, "c"),
            (d, ORIGIN, LEFT, "d"),
        ]
        everyone = VMobject()
        for p1, p2, direction, char in quads:
            line = Line(p1, p2)
            brace = Brace(line, direction, buff = 0)
            text = brace.get_text(char)
            text.add_background_rectangle()
            if char in ["a", "c"]:
                text.set_color(X_COLOR)
            else:
                text.set_color(Y_COLOR)
            everyone.add(brace, text)
        self.play(Write(everyone), run_time = 1)


    def show_formula(self):
        matrix = self.get_matrix()
        det_text = get_det_text(matrix)
        f_str = "=(a+b)(c+d)-ac-bd-2bc=ad-bc"
        formula = TexMobject(f_str)

        formula.next_to(det_text, RIGHT)
        everyone = VMobject(det_text, matrix, formula)
        everyone.set_width(FRAME_WIDTH - 1)
        everyone.next_to(DOWN, DOWN)
        background_rect = BackgroundRectangle(everyone)
        self.play(
            ShowCreation(background_rect),
            Write(everyone)
        )
        self.wait()

class ThreeDDetFormula(Scene):
    def construct(self):
        matrix = Matrix([list("abc"), list("def"), list("ghi")])
        matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR)
        m1 = Matrix([["e", "f"], ["h", "i"]])
        m1.set_column_colors(Y_COLOR, Z_COLOR)
        m2 = Matrix([["d", "f"], ["g", "i"]])
        m2.set_column_colors(X_COLOR, Z_COLOR)
        m3 = Matrix([["d", "e"], ["g", "h"]])
        m3.set_column_colors(X_COLOR, Y_COLOR)

        for m in matrix, m1, m2, m3:
            m.add(get_det_text(m))
        a, b, c = matrix.get_entries().split()[:3]
        parts = it.starmap(VMobject, [
            [matrix], 
            [TexMobject("="), a.copy(), m1], 
            [TexMobject("-"), b.copy(), m2],
            [TexMobject("+"), c.copy(), m3],
        ])
        parts = list(parts)
        for part in parts:
            part.arrange(RIGHT, buff = 0.2)
        parts[1].next_to(parts[0], RIGHT)
        parts[2].next_to(parts[1], DOWN, aligned_edge = LEFT)
        parts[3].next_to(parts[2], DOWN, aligned_edge = LEFT)
        everyone = VMobject(*parts)
        everyone.center().to_edge(UP)
        for part in parts:
            self.play(Write(part))
        self.wait(2)

class QuizTime(TeacherStudentsScene):
    def construct(self):
        self.setup()
        self.teacher_says("Quiz time!")
        self.random_blink()
        self.wait()
        self.random_blink()

class ProductProperty(Scene):
    def construct(self):
        lhs = TexMobject([
            "\\text{det}(",
            "M_1", 
            "M_2",
            ")"
        ])
        det, m1, m2, rp = lhs.split()
        m1.set_color(TEAL)
        m2.set_color(PINK)

        rhs = TexMobject([
            "=\\text{det}(",
            "M_1", 
            ")\\text{det}(",
            "M_2",
            ")"
        ])
        rhs.split()[1].set_color(TEAL)
        rhs.split()[3].set_color(PINK)

        rhs.next_to(lhs, RIGHT)
        formula = VMobject(lhs, rhs)
        formula.center()

        title = TextMobject("Explain in one sentence")
        title.set_color(YELLOW)
        title.next_to(formula, UP, buff = 0.5)

        self.play(Write(m1))
        self.play(Write(m2))
        self.wait()
        self.play(Write(det), Write(rp))
        self.play(Write(rhs))
        self.wait(2)
        self.play(Write(title))
        self.wait(2)

class NextVideo(Scene):
    def construct(self):
        title = TextMobject("""
            Next video: Inverse matrices, column space and null space
        """)
        title.set_width(FRAME_WIDTH - 2)
        title.to_edge(UP)
        rect = Rectangle(width = 16, height = 9, color = BLUE)
        rect.set_height(6)
        rect.next_to(title, DOWN)

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












