from manim import *
from manim_voiceover import VoiceoverScene
from manim_voiceover.services.gtts import GTTSService
from vectors import Vector
from more_itertools import chunked

from pyrr import Vector3

def create_button_triangle():
    button = SVGMobject("resources/ps_color_button_triangle.svg").scale(0.25)
    return button

def create_button_square():
    button = SVGMobject("resources/ps_color_button_square.svg").scale(0.25)
    return button

def create_button_x():
    button = SVGMobject("resources/ps_color_button_x.svg").scale(0.25)
    return button

def create_button_r1():
    button = SVGMobject("resources/ps5_r1.svg").scale(0.25)
    return button

def lerp(a: float, b: float, t: float) -> float:
    """Linear interpolate on the scale given by a to b, using t as the point on that scale.
    Examples
    --------
        50 == lerp(0, 100, 0.5)
        4.2 == lerp(1, 5, 0.8)
    """
    return (1 - t) * a + t * b

class PHTickless(VoiceoverScene):
    def construct(self):

        self.set_speech_service(GTTSService("en", transcription_model='base'))
        Text.set_default(font="Noto Sans")

        with self.voiceover(
            """
            In Project Heartbeat mere milliseconds can mean the difference between a record breaking run or an absolute failure.
            As a result, we are in an everlasting quest to lower latency and increase input consistency.
            With Project Heartbeat 2's new game engine, we've replaced the input system and developed cutting-edge rhythm game technology.
            """
         ):
            self.wait_for_voiceover()
        
        tickless_title = Text("Tickless system")
        tickless_subtitle = Text("Rhythm gaming... evolved?", font_size=DEFAULT_FONT_SIZE/2)
        
        with self.voiceover(
            """
            The game's logic is now completely dettached from the framerate, through the new advanced tickless system developed specifically for Project Heartbeat.
            """
        ):
            tickless_subtitle.next_to(tickless_title, DOWN)
            self.play(Write(tickless_title), Write(tickless_subtitle))
            self.wait_for_voiceover()
            self.wait()
            self.play(Unwrite(tickless_title), Unwrite(tickless_subtitle))
        with self.voiceover(
            """
            In previous versions of Project Heartbeat, <bookmark mark='input_check'/>your input used to only get checked once at the start of each frame.
            Any <bookmark mark='button_press'/>button presses you did after that got snapped to the start of the next frame.
            <bookmark mark='lag_incident'/>With the tight and fast gameplay of Project Heartbeat, this could mean the difference between getting a FINE rating and failing a perfect run.
            <bookmark mark='extra_buttons'/> With the new subtick system this no longer matters, <bookmark mark='show_tickless'/>the game now knows when you've pressed every single note and is able to precisely evaluate it.
            <bookmark mark='show_tickless'/> Effectively acting as if you were running an infinite frame rate.
            """
        ):
            frame_rects = []
            frame_numbers = []
            FRAMES_TO_SHOW = 5
            GRAPH_WIDTH = 4
            for i in range(FRAMES_TO_SHOW):
                frame_rct = Rectangle(fill_opacity=1.0, width=(GRAPH_WIDTH/FRAMES_TO_SHOW)*2, height=1, stroke_color=PURPLE_D, fill_color=PURPLE_C)
                frame_rects.append(frame_rct)
            Group(*frame_rects).arrange()

            for i in range(FRAMES_TO_SHOW):
                frame_nmb = Text(str(i+1), font_size=DEFAULT_FONT_SIZE/2, color=BLACK)
                frame_numbers.append(frame_nmb)
                frame_nmb.move_to(frame_rects[i].get_center())
            self.play(*[Create(r) for r in frame_rects] + [Write(n) for n in frame_numbers])

            animations = []

            new_frame_rects = []
            BIG_FRAME_PERCENTAGE = 0.85
            for i in range(FRAMES_TO_SHOW):
                width = GRAPH_WIDTH*2
                if i == 2:
                    width = BIG_FRAME_PERCENTAGE * width
                else:
                    width = ((1.0 - BIG_FRAME_PERCENTAGE) * width) / (FRAMES_TO_SHOW-1)
                rect = frame_rects[i].copy()
                rect.stretch_to_fit_width(width = width)
                stroke = PURPLE_D if i != 2 else PURPLE_B
                fill = PURPLE_C if i != 2 else PURPLE_A
                rect.set_style(stroke_color=stroke, fill_color=fill)
                new_frame_rects.append(rect)
                animations.append(Transform(frame_rects[i], rect))
            Group(*new_frame_rects).arrange()

            for i in range(FRAMES_TO_SHOW):
                animations.append(frame_numbers[i].animate.move_to(new_frame_rects[i].get_center()))
            
            big_frame = new_frame_rects[2]

            corner_time_start = Text("0 ms", font_size=DEFAULT_FONT_SIZE/4).next_to(big_frame.get_critical_point(normalize(UP+LEFT)), UP)
            corner_time_end = Text("16.666... ms", font_size=DEFAULT_FONT_SIZE/4).next_to(big_frame.get_critical_point(normalize(UP+RIGHT)), UP)

            animations += [
                Write(corner_time_start),
                Write(corner_time_end)
            ]

            self.wait_until_bookmark("input_check")

            self.play(*animations)


            frame_start_arrow_end = big_frame.get_critical_point(normalize(DOWN+LEFT))
            frame_start_arrow = Arrow(frame_start_arrow_end + DOWN, frame_start_arrow_end)
            read_inputs_text = Text("Read inputs", font_size=DEFAULT_FONT_SIZE/4)
            read_inputs_text.next_to(frame_start_arrow, DOWN)

            frame_rect_end = big_frame.get_critical_point(normalize(DOWN+RIGHT))
            
            BUTTON_PRESS_ARROW_TIMES = [
                0.25, 0.45, 0.76, 0.95
            ]
            BUTTON_PRESS_ICONS = [
                create_button_square(),
                create_button_triangle(),
                create_button_r1(),
                create_button_x()
            ]

            button_press_arrows = []
            button_presses_animations = []
            button_press_braces = []
            button_press_braces_animations = []


            brace_start_point = big_frame.get_critical_point(normalize(UP+LEFT))
            brace_end_point = big_frame.get_critical_point(normalize(UP+RIGHT))
            prev_pos = np.array([])

            for (t, ic) in zip(BUTTON_PRESS_ARROW_TIMES, BUTTON_PRESS_ICONS):
                button_press_arrow_end = interpolate_arrays(frame_start_arrow_end, frame_rect_end, t)
                button_press_arrow = Arrow(button_press_arrow_end + DOWN, button_press_arrow_end)
                button = ic
                button.next_to(button_press_arrow, DOWN)
                button_presses_animations += [Create(button), Create(button_press_arrow)]
                button_press_arrows.append(button_press_arrow)
                
                time_label = Text(f'{(1/60.0 * 1000.0 * t):.4f} ms', font_size=DEFAULT_FONT_SIZE/4)
                time_label.next_to(button, DOWN)
                button_presses_animations.append(Write(time_label))
                
                brace_p = interpolate_arrays(brace_start_point, brace_end_point, t)
                if prev_pos.any():
                    br = BraceBetweenPoints(prev_pos, brace_p, direction=UP, buff=0.5)
                    tx = Text(f"Subtick {(int(len(button_press_braces)/2+1))}", font_size=DEFAULT_FONT_SIZE/4)
                    brt = br.put_at_tip(tx)
                    button_press_braces.append(br)
                    button_press_braces.append(tx)
                    button_press_braces_animations.append(GrowFromCenter(br))
                    button_press_braces_animations.append(Write(tx))
                prev_pos = brace_p

            lag_brace = BraceBetweenPoints(interpolate_arrays(frame_start_arrow_end, frame_rect_end, BUTTON_PRESS_ARROW_TIMES[0]), frame_rect_end, buff=0.1)
            lag_brace_t = Text("Delayed until the start of next frame!", font_size=DEFAULT_FONT_SIZE/4)
            lag_brace.put_at_tip(lag_brace_t)

            self.play(Create(frame_start_arrow), Write(read_inputs_text))
            self.wait_until_bookmark("button_press")

            phantom_button = BUTTON_PRESS_ICONS[0].copy()
            phantom_arrow = Arrow(frame_rect_end + DOWN, frame_rect_end)
            phantom_button.next_to(phantom_arrow, DOWN)

            self.play(*button_presses_animations[:2])
            self.wait_until_bookmark("lag_incident")

            self.play(GrowFromCenter(lag_brace), Write(lag_brace_t), Create(phantom_button), Create(phantom_arrow))
            self.wait_until_bookmark("extra_buttons")
            self.play(FadeOut(lag_brace), FadeOut(lag_brace_t), Uncreate(phantom_button), Uncreate(phantom_arrow))

            self.play(button_presses_animations[2])
            for a1, a2, a3 in chunked(button_presses_animations[3:], n=3):
                self.play(*[a1, a2, a3])
            self.wait_until_bookmark("show_tickless")

            self.play(*button_press_braces_animations)
