#!/usr/bin/env python3
from moviepy import ColorClip, CompositeVideoClip
from moviepy import Effect
from moviepy.video.fx import Scroll

class ScrollFixed(Effect):
    """Effect that scrolls horizontally or vertically a clip, e.g. to make end credits

    Parameters
    ----------
    w, h
      The width and height of the final clip. Default to clip.w and clip.h

    x_speed, y_speed
      The speed of the scroll in the x and y directions.

    x_start, y_start
      The starting position of the scroll in the x and y directions.


    apply_to
      Whether to apply the effect to the mask too.
    """

    def __init__(
        self,
        w=None,
        h=None,
        x_speed=0,
        y_speed=0,
        x_start=0,
        y_start=0,
        apply_to="mask"
    ):
        self.w = w
        self.h = h
        self.x_speed = x_speed
        self.y_speed = y_speed
        self.x_start = x_start
        self.y_start = y_start
        self.apply_to = apply_to

    def apply(self, clip):
        """Apply the effect to the clip."""
        if self.h is None:
            self.h = clip.h

        if self.w is None:
            self.w = clip.w


        #If the clip is bigger than the desired output then there is something to scroll over
        #       and we want to stop when we have scrolled through the whole content or we have 
        #       run out of time
        # otherwise there is nothing to scroll over
        x_max = 0
        y_max = 0

        if clip.w > self.w:
            x_max = (clip.w - 1) - self.w

        if clip.h > self.h:
            y_max = (clip.h - 1) - self.h

        def filter(get_frame, t):
            x = int(max(0, min(x_max, self.x_start + round(self.x_speed * t))))
            y = int(max(0, min(y_max, self.y_start + round(self.y_speed * t))))
            return get_frame(t)[y : y + self.h, x : x + self.w]

        return clip.transform(filter, apply_to=self.apply_to)

def test_Scroll(ScrollUnderTest:Effect=Scroll,speed_multiplier:float=1.0,output_filename:str="test_scroll_effect.mp4"):
    # Testing Scroll effect with 9 ColorClips in a 3x3 grid.  
    # 0 Red    - 3 Magenta  - 6 Orange
    # 1 Green  - 4 Cyan     - 7 Purple
    # 2 Blue   - 5 Yellow   - 8 Pink
    
    # Video dimensions
    COLOR_CLIP_WIDTH = 1280
    COLOR_CLIP_HEIGHT = 720
    FINAL_WIDTH = 1280
    FINAL_HEIGHT = 720
    
    # Create 9 ColorClips with different colors
    colors = [
        (255, 0, 0),    # Red
        (0, 255, 0),    # Green
        (0, 0, 255),    # Blue
        (255, 0, 255),  # Magenta
        (0, 255, 255),  # Cyan
        (255, 255, 0),  # Yellow
        (255, 128, 0),  # Orange
        (128, 0, 255),  # Purple
        (255, 192, 203), # Pink
    ]
    
    color_clips = []
    for i, color in enumerate(colors):
        clip = ColorClip(
            size=(COLOR_CLIP_WIDTH, COLOR_CLIP_HEIGHT),
            color=color,
            duration=10  # 10 second duration
        )
        color_clips.append(clip)
        print(f"Created ColorClip {i+1}: RGB{color}")
    
    # Position clips in 3 columns, 3 rows
    # Column 0: clips 0, 1, 2 (positions x=0)
    # Column 1: clips 3, 4, 5 (positions x=1280)
    # Column 2: clips 6, 7, 8 (positions x=2560)
    positioned_clips = []
    
    for i, clip in enumerate(color_clips):
        col = i % 3  # Column (0, 1, or 2)
        row = i // 3  # Row (0, 1, or 2)
        
        x_pos = col * COLOR_CLIP_WIDTH
        y_pos = row * COLOR_CLIP_HEIGHT
        
        positioned_clip = clip.with_position((x_pos, y_pos))
        positioned_clips.append(positioned_clip)
        print(f"Positioned clip {i+1} at ({x_pos}, {y_pos}) - Column {col}, Row {row}")
    
    # Create composite clip (3840 x 2160)
    composite_width = COLOR_CLIP_WIDTH * 3  # 3840
    composite_height = COLOR_CLIP_HEIGHT * 3  # 2160
    
    print(f"\nCreating composite clip: {composite_width}x{composite_height}")
    composite_clip = CompositeVideoClip(
        positioned_clips,
        size=(composite_width, composite_height)
    )
    
    # Create scroll effect
    # We want to scroll from top-left to bottom-right
    # Start at top-left corner (0, 0)
    # End at bottom-right corner (2560, 1440) - showing the bottom-right area
    
    scroll_distance_x = composite_width - FINAL_WIDTH  # 3840 - 1280 = 2560
    scroll_distance_y = composite_height - FINAL_HEIGHT  # 2160 - 720 = 1440
    
    scroll_duration = 10  # 10 seconds
    x_speed = speed_multiplier * scroll_distance_x / scroll_duration  # 256 pixels per second
    y_speed = speed_multiplier * scroll_distance_y / scroll_duration  # 144 pixels per second
    
    print(f"\nScroll parameters:")
    print(f"  Scroll distance X: {scroll_distance_x} pixels")
    print(f"  Scroll distance Y: {scroll_distance_y} pixels")
    print(f"  Scroll duration: {scroll_duration} seconds")
    print(f"  X speed: {x_speed} pixels/second with speed_multiplier x{speed_multiplier} applied")
    print(f"  Y speed: {y_speed} pixels/second with speed_multiplier x{speed_multiplier} applied")
    
    # Apply scroll effect
    scrolled_clip = composite_clip.with_effects([
        ScrollUnderTest(
            w=FINAL_WIDTH,
            h=FINAL_HEIGHT,
            x_speed=x_speed,
            y_speed=y_speed,
            x_start=0,
            y_start=0
        )
    ])
    
    # Verify the last frame before writing
    print(f"\nVerifying last frame...")
    last_frame_time = scrolled_clip.duration - 0.1  # Get frame just before the end
    last_frame = scrolled_clip.get_frame(last_frame_time)
    
    # Get center pixel
    center_x = FINAL_WIDTH // 2
    center_y = FINAL_HEIGHT // 2
    center_pixel = last_frame[center_y, center_x]
    
    print(f"Last frame center pixel at ({center_x}, {center_y}): RGB{tuple(center_pixel)}")
    
    expected_pink = (255, 192, 203)
    # Convert center_pixel to int to avoid uint8 overflow issues
    center_pixel_int = [int(center_pixel[i]) for i in range(3)]
    is_pink = all(abs(center_pixel_int[i] - expected_pink[i]) < 10 for i in range(3))
    if is_pink:
        print("✅ VERIFICATION PASSED: Center pixel is pink (or close to pink)")
    else:
        print("❌ VERIFICATION FAILED: Center pixel is not pink!")
        print(f"  Expected: RGB{expected_pink}")
        print(f"  Actual: RGB{tuple(center_pixel)}")
    
    # Write the final video
    print(f"Writing video to {output_filename} to inspect")
    
    scrolled_clip.write_videofile(
        output_filename,
        fps=24,
        logger=None
    )
    
    # Clean up
    for clip in color_clips:
        clip.close()
    composite_clip.close()
    scrolled_clip.close()

if __name__ == "__main__":
    print("Making videos...")

    print("The video should:")
    print("1. Start at top-left (Red)")
    print("2. Scroll diagonally through the 3x3 grid")
    print("3. Move through: Red → Green → Blue → Magenta → Cyan → Yellow → Orange → Purple → Pink")
    print("4. End at bottom-right (Pink)")

    output_filename = "test_perfect_speed_with_bug.mp4"
    test_Scroll(Scroll,1.0,output_filename)
    
    output_filename = "test_perfect_speed_without_bug.mp4"
    test_Scroll(ScrollFixed,1.0,output_filename)

    output_filename = "test_slow_speed_with_bug.mp4"
    test_Scroll(Scroll,0.8,output_filename)
    
    output_filename = "test_slow_speed_without_bug.mp4"
    test_Scroll(ScrollFixed,0.8,output_filename)

    output_filename = "test_fast_speed_with_bug.mp4"
    test_Scroll(Scroll,1.5,output_filename)

    output_filename = "test_fast_speed_without_bug.mp4"
    test_Scroll(ScrollFixed,3.0,output_filename)
