import numpy as np
import itertools as it

from big_ol_pile_of_manim_imports import *
from old_projects.brachistochrone.light import PhotonScene
from old_projects.brachistochrone.curves import *


class MultilayeredScene(Scene):
    CONFIG = {
        "n_layers" : 5,
        "top_color" : BLUE_E,
        "bottom_color" : BLUE_A,
        "total_glass_height" : 5,
        "top" : 3*UP,
        "RectClass" : Rectangle #FilledRectangle
    }

    def get_layers(self, n_layers = None):
        if n_layers is None:
            n_layers = self.n_layers
        width = FRAME_WIDTH
        height = float(self.total_glass_height)/n_layers
        rgb_pair = [
            np.array(Color(color).get_rgb())
            for color in (self.top_color, self.bottom_color)
        ]
        rgb_range = [
            interpolate(*rgb_pair+[x])
            for x in np.arange(0, 1, 1./n_layers)
        ]
        tops = [
            self.top + x*height*DOWN
            for x in range(n_layers)
        ]
        color = Color()
        result = []
        for top, rgb in zip(tops, rgb_range):
            color.set_rgb(rgb)
            rect = self.RectClass(
                height = height, 
                width = width, 
                color = color
            )
            rect.shift(top-rect.get_top())
            result.append(rect)
        return result

    def add_layers(self):
        self.layers = self.get_layers()
        self.add(*self.layers)
        self.freeze_background()

    def get_bottom(self):
        return self.top + self.total_glass_height*DOWN

    def get_continuous_glass(self):
        result = self.RectClass(
            width = FRAME_WIDTH,
            height = self.total_glass_height,
        )
        result.sort_points(lambda p : -p[1])
        result.set_color_by_gradient(self.top_color, self.bottom_color)
        result.shift(self.top-result.get_top())
        return result


class TwoToMany(MultilayeredScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        glass = self.get_glass()
        layers = self.get_layers()

        self.add(glass)
        self.wait()
        self.play(*[
            FadeIn(
                layer,
                rate_func = squish_rate_func(smooth, x, 1)
            )
            for layer, x in zip(layers[1:], it.count(0, 0.2))
        ]+[
            Transform(glass, layers[0])
        ])
        self.wait()

    def get_glass(self):
        return self.RectClass(
            height = FRAME_Y_RADIUS,
            width = FRAME_WIDTH,
            color = BLUE_E
        ).shift(FRAME_Y_RADIUS*DOWN/2)


class RaceLightInLayers(MultilayeredScene, PhotonScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        self.add_layers()
        line = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT)
        lines = [
            line.copy().shift(layer.get_center())
            for layer in self.layers
        ]

        def rate_maker(x):
            return lambda t : min(x*x*t, 1)
        min_rate, max_rate = 1., 2.
        rates = np.arange(min_rate, max_rate, (max_rate-min_rate)/self.n_layers)
        self.play(*[
            self.photon_run_along_path(
                line,
                rate_func = rate_maker(rate),
                run_time = 2
            )
            for line, rate in zip(lines, rates)
        ])

class ShowDiscretePath(MultilayeredScene, PhotonScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        self.add_layers()
        self.cycloid = Cycloid(end_theta = np.pi)

        self.generate_discrete_path()
        self.play(ShowCreation(self.discrete_path))
        self.wait()
        self.play(self.photon_run_along_path(
            self.discrete_path,
            rate_func = rush_into,
            run_time = 3
        ))
        self.wait()


    def generate_discrete_path(self):
        points = self.cycloid.points
        tops = [mob.get_top()[1] for mob in self.layers]
        tops.append(tops[-1]-self.layers[0].get_height())
        indices = [
            np.argmin(np.abs(points[:, 1]-top))
            for top in tops
        ]
        self.bend_points = points[indices[1:-1]]
        self.path_angles = []
        self.discrete_path = Mobject1D(
            color = WHITE,
            density = 3*DEFAULT_POINT_DENSITY_1D
        )
        for start, end in zip(indices, indices[1:]):
            start_point, end_point = points[start], points[end]
            self.discrete_path.add_line(
                start_point, end_point
            )
            self.path_angles.append(
                angle_of_vector(start_point-end_point)-np.pi/2
            )
        self.discrete_path.add_line(
            points[end], FRAME_X_RADIUS*RIGHT+(tops[-1]-0.5)*UP
        )

class NLayers(MultilayeredScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        self.add_layers()
        brace = Brace(
            Mobject(
                Point(self.top),
                Point(self.get_bottom())
            ),
            RIGHT
        )
        n_layers = TextMobject("$n$ layers")
        n_layers.next_to(brace)

        self.wait()

        self.add(brace)
        self.show_frame()

        self.play(
            GrowFromCenter(brace),
            GrowFromCenter(n_layers)
        )
        self.wait()

class ShowLayerVariables(MultilayeredScene, PhotonScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        self.add_layers()
        v_equations = []
        start_ys = []
        end_ys = []
        center_paths = []
        braces = []
        for layer, x in zip(self.layers[:3], it.count(1)):
            eq_mob = TexMobject(
                ["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"],
                size = "\\Large"
            )
            eq_mob.shift(layer.get_center()+2*LEFT)
            v_eq = eq_mob.split()
            v_eq[0].set_color(layer.get_color())
            path = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT)
            path.shift(layer.get_center())
            brace_endpoints = Mobject(
                Point(self.top),
                Point(layer.get_bottom())
            )
            brace = Brace(brace_endpoints, RIGHT)
            brace.shift(x*RIGHT)

            start_y = TexMobject("y_%d"%x, size = "\\Large")
            end_y = start_y.copy()
            start_y.next_to(brace, RIGHT)
            end_y.shift(v_eq[-1].get_center())
            nudge = 0.2*RIGHT
            end_y.shift(nudge)

            v_equations.append(v_eq)
            start_ys.append(start_y)
            end_ys.append(end_y)
            center_paths.append(path)            
            braces.append(brace)

        for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]):
            photon_run = self.photon_run_along_path(
                path,
                rate_func=linear
            )
            self.play(
                FadeToColor(v_eq[0], WHITE),
                photon_run,
                run_time = time
            )
        self.wait()

        starts = [0, 0.3, 0.6]
        self.play(*it.chain(*[
            [
                GrowFromCenter(
                    mob,
                    rate_func=squish_rate_func(smooth, start, 1)
                )
                for mob, start in zip(mobs, starts)
            ]
            for mobs in (start_ys, braces)
        ]))
        self.wait()

        triplets = list(zip(v_equations, start_ys, end_ys))
        anims = []
        for v_eq, start_y, end_y in triplets:
            anims += [
                ShowCreation(v_eq[1]),
                ShowCreation(v_eq[2]),
                Transform(start_y.copy(), end_y)
            ]
        self.play(*anims)
        self.wait()


class LimitingProcess(MultilayeredScene):
    CONFIG = {
        "RectClass" : FilledRectangle
    }
    def construct(self):
        num_iterations = 3
        layer_sets = [
            self.get_layers((2**x)*self.n_layers)
            for x in range(num_iterations)
        ]
        glass_sets = [
            Mobject(*[
                Mobject(
                    *layer_sets[x][(2**x)*index:(2**x)*(index+1)]
                )
                for index in range(self.n_layers)
            ]).ingest_submobjects()
            for x in range(num_iterations)
        ]
        glass_sets.append(self.get_continuous_glass())
        for glass_set in glass_sets:
            glass_set.sort_points(lambda p : p[1])
        curr_set = glass_sets[0]
        self.add(curr_set)
        for layer_set in glass_sets[1:]:
            self.wait()
            self.play(Transform(curr_set, layer_set))
        self.wait()



class ShowLightAndSlidingObject(MultilayeredScene, TryManyPaths, PhotonScene):
    CONFIG = {
        "show_time" : False,
        "wait_and_add" : False,
        "RectClass" : FilledRectangle
    }
    def construct(self):
        glass = self.get_continuous_glass()
        self.play(ApplyMethod(glass.fade, 0.8))
        self.freeze_background()

        paths = self.get_paths()
        for path in paths:
            if path.get_height() > self.total_glass_height:
                path.stretch(0.7, 1)
                path.shift(self.top - path.get_top())
            path.rgbas[:,2] = 0
        loop = paths.pop(1) ##Bad!
        randy = Randolph()
        randy.scale(RANDY_SCALE_FACTOR)
        randy.shift(-randy.get_bottom())
        photon_run = self.photon_run_along_path(
            loop, 
            rate_func = lambda t : smooth(1.2*t, 2),
            run_time = 4.1
        )
        text = self.get_text().to_edge(UP, buff = 0.2)

        self.play(ShowCreation(loop))
        self.wait()
        self.play(photon_run)
        self.remove(photon_run.mobject)
        randy = self.slide(randy, loop)
        self.add(randy)
        self.wait()
        self.remove(randy)
        self.play(ShimmerIn(text))
        for path in paths:
            self.play(Transform(
                loop, path,
                path_func = path_along_arc(np.pi/2),
                run_time = 2
            ))


class ContinuouslyObeyingSnellsLaw(MultilayeredScene):
    CONFIG = {
        "arc_radius" : 0.5,
        "RectClass" : FilledRectangle
    }
    def construct(self):
        glass = self.get_continuous_glass()
        self.add(glass)
        self.freeze_background()

        cycloid = Cycloid(end_theta = np.pi)
        cycloid.set_color(YELLOW)
        chopped_cycloid = cycloid.copy()
        n = cycloid.get_num_points()
        chopped_cycloid.filter_out(lambda p : p[1] > 1 and p[0] < 0)
        chopped_cycloid.reverse_points()


        self.play(ShowCreation(cycloid))
        ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid)
        self.show_equation(chopped_cycloid, ref_mob)

    def snells_law_at_every_point(self, cycloid, chopped_cycloid):
        square = Square(side_length = 0.2, color = WHITE)
        words = TextMobject(["Snell's law ", "everywhere"])
        snells, rest = words.split()
        colon = TextMobject(":")
        words.next_to(square)
        words.shift(0.3*UP)
        combo = Mobject(square, words)
        combo.get_center = lambda : square.get_center()
        new_snells = snells.copy().center().to_edge(UP, buff = 1.5)
        colon.next_to(new_snells)
        colon.shift(0.05*DOWN)
            
        self.play(MoveAlongPath(
            combo, cycloid,
            run_time = 5
        ))
        self.play(MoveAlongPath(
            combo, chopped_cycloid,
            run_time = 4
        ))
        dot = Dot(combo.get_center())
        self.play(Transform(square, dot))
        self.play(
            Transform(snells, new_snells),
            Transform(rest, colon)
        )
        self.wait()
        return colon

    def get_marks(self, point1, point2):
        vert_line = Line(2*DOWN, 2*UP)
        tangent_line = vert_line.copy()
        theta = TexMobject("\\theta")
        theta.scale(0.5)
        angle = angle_of_vector(point1 - point2)
        tangent_line.rotate(
            angle - tangent_line.get_angle()
        )
        angle_from_vert = angle - np.pi/2
        for mob in vert_line, tangent_line:
            mob.shift(point1 - mob.get_center())
        arc = Arc(angle_from_vert, start_angle = np.pi/2)
        arc.scale(self.arc_radius)
        arc.shift(point1)
        vect_angle = angle_from_vert/2 + np.pi/2
        vect = rotate_vector(RIGHT, vect_angle)
        theta.center()
        theta.shift(point1)
        theta.shift(1.5*self.arc_radius*vect)
        return arc, theta, vert_line, tangent_line


    def show_equation(self, chopped_cycloid, ref_mob):
        point2, point1 = chopped_cycloid.points[-2:]
        arc, theta, vert_line, tangent_line = self.get_marks(
            point1, point2
        )
        equation = TexMobject([
            "\\sin(\\theta)",
            "\\over \\sqrt{y}",            
        ])
        sin, sqrt_y = equation.split()
        equation.next_to(ref_mob)
        const = TexMobject(" = \\text{constant}")
        const.next_to(equation)
        ceil_point = np.array(point1)
        ceil_point[1] = self.top[1]
        brace = Brace(
            Mobject(Point(point1), Point(ceil_point)),
            RIGHT
        )
        y_mob = TexMobject("y").next_to(brace)

        self.play(
            GrowFromCenter(sin),
            ShowCreation(arc), 
            GrowFromCenter(theta)
        )
        self.play(ShowCreation(vert_line))
        self.play(ShowCreation(tangent_line))
        self.wait()
        self.play(
            GrowFromCenter(sqrt_y),
            GrowFromCenter(brace),
            GrowFromCenter(y_mob)
        )
        self.wait()
        self.play(Transform(
            Point(const.get_left()), const
        ))
        self.wait()



















