#!/usr/bin/env python
"""
Example of asynchronous video processing in FMUS-VID.

This script demonstrates how to use asynchronous methods for video processing,
which allows for better resource utilization and responsiveness.
"""

import sys
import os
import time
import asyncio
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

# Add the parent directory to the path so we can import fmusvid
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from fmusvid import Video
from fmusvid.core.video import VideoInfo

async def process_video_async(input_path, output_path, effects):
    """
    Process a video asynchronously with the given effects.

    Args:
        input_path: Path to input video
        output_path: Path to save output video
        effects: List of effects to apply (strings like "grayscale", "blur", etc.)
    """
    print(f"Processing {input_path} asynchronously...")

    # Load video
    video = Video(input_path)

    # Print video info
    info = video.info()
    print(f"Video info: {info}")

    # Apply effects
    for effect in effects:
        if effect == "grayscale":
            video = video.grayscale()
            print("Applied grayscale effect")
        elif effect.startswith("blur"):
            # Extract blur radius if specified (e.g., "blur5" for radius 5)
            radius = 3.0  # default
            if len(effect) > 4:
                try:
                    radius = float(effect[4:])
                except ValueError:
                    pass
            video = video.blur(radius)
            print(f"Applied blur effect with radius {radius}")
        elif effect.startswith("brightness"):
            # Extract brightness factor if specified
            factor = 1.2  # default
            if len(effect) > 10:
                try:
                    factor = float(effect[10:])
                except ValueError:
                    pass
            video = video.brightness(factor)
            print(f"Applied brightness effect with factor {factor}")
        elif effect.startswith("contrast"):
            # Extract contrast factor if specified
            factor = 1.2  # default
            if len(effect) > 8:
                try:
                    factor = float(effect[8:])
                except ValueError:
                    pass
            video = video.contrast(factor)
            print(f"Applied contrast effect with factor {factor}")
        elif effect.startswith("trim"):
            # Format is "trim_start_end" (e.g., "trim_1_5")
            parts = effect.split("_")
            if len(parts) >= 3:
                try:
                    start = float(parts[1])
                    end = float(parts[2])
                    video = video.trim(start, end)
                    print(f"Trimmed video from {start}s to {end}s")
                except (ValueError, IndexError):
                    print(f"Invalid trim format: {effect}")
            else:
                print(f"Invalid trim format: {effect}")

    # Define progress callback
    def progress_callback(progress):
        # Only show progress updates at certain intervals to avoid flooding the console
        if int(progress * 100) % 10 == 0:
            print(f"Saving progress: {progress:.1%}")

    # Save the video asynchronously
    print(f"Saving to {output_path}...")
    await video.save_async(output_path, progress_callback=progress_callback)

    print(f"Finished processing {input_path} to {output_path}")
    return output_path

async def batch_process_videos(video_tasks):
    """
    Process multiple videos concurrently.

    Args:
        video_tasks: List of (input_path, output_path, effects) tuples
    """
    tasks = []
    for input_path, output_path, effects in video_tasks:
        task = asyncio.create_task(
            process_video_async(input_path, output_path, effects)
        )
        tasks.append(task)

    # Wait for all tasks to complete
    results = await asyncio.gather(*tasks)
    return results

async def create_video_summary(video_paths, output_path):
    """
    Create a summary grid of multiple videos.

    Args:
        video_paths: List of paths to videos
        output_path: Path to save the summary video
    """
    print("Creating video summary...")

    # Load all videos
    videos = [Video(path) for path in video_paths]

    # Create a grid of videos
    grid = Video.grid(videos, rows=2, cols=2)

    # Save the grid
    print(f"Saving grid to {output_path}...")
    await grid.save_async(
        output_path,
        progress_callback=lambda p: print(f"Grid saving progress: {p:.1%}")
    )

    print(f"Video summary saved to {output_path}")

async def main_async():
    """Main async function to run the example."""
    print("FMUS-VID Asynchronous Processing Example")
    print("========================================")

    # Create test directory if it doesn't exist
    os.makedirs("test_outputs", exist_ok=True)

    # Hardcoded sample videos to try
    sample_video_paths = [
        os.path.join("test_media", "test_video.mp4"),
        os.path.join("test_media", "sample.mp4"),
        os.path.join("test_media", "sample2.mp4")
    ]

    # Find the first existing sample video
    video_path = None
    for path in sample_video_paths:
        if os.path.exists(path):
            video_path = path
            break

    # If no sample videos found, create a simple test video
    if video_path is None:
        print("No sample videos found. Creating a simple test video...")

        # Create test_media directory if it doesn't exist
        os.makedirs("test_media", exist_ok=True)

        # Create a simple test video with a moving gradient
        test_video_path = os.path.join("test_media", "generated_test.mp4")

        # Import necessary libraries
        import cv2
        import numpy as np

        # Video properties
        width, height = 640, 480
        fps = 30
        duration = 5  # seconds

        # Create video writer
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        out = cv2.VideoWriter(test_video_path, fourcc, fps, (width, height))

        # Generate frames with moving gradient (vectorized for speed)
        x_coords = np.arange(width)
        y_coords = np.arange(height)
        xx, yy = np.meshgrid(x_coords, y_coords)

        for i in range(int(fps * duration)):
            # Create frame with moving gradient using vectorized operations
            b = ((0.5 + 0.5 * np.sin(0.1 * i + xx * 0.01)) * 255).astype(np.uint8)
            g = ((0.5 + 0.5 * np.sin(0.1 * i + yy * 0.01)) * 255).astype(np.uint8)
            r = ((0.5 + 0.5 * np.cos(0.1 * i + (xx + yy) * 0.01)) * 255).astype(np.uint8)

            # Stack channels to create frame
            frame = np.dstack((b, g, r))

            # Write frame
            out.write(frame)

        out.release()
        print(f"Created test video: {test_video_path}")
        video_path = test_video_path

    print(f"\nUsing video: {video_path}")

    # Define batch processing tasks
    video_tasks = [
        (
            video_path,
            os.path.join("test_outputs", "grayscale.mp4"),
            ["grayscale"]
        ),
        (
            video_path,
            os.path.join("test_outputs", "blur.mp4"),
            ["blur5"]
        ),
        (
            video_path,
            os.path.join("test_outputs", "bright.mp4"),
            ["brightness1.5"]
        ),
        (
            video_path,
            os.path.join("test_outputs", "trimmed.mp4"),
            ["trim_1_5"]
        )
    ]

    # Process all videos concurrently
    print("\nProcessing multiple videos concurrently...")
    start_time = time.time()
    processed_videos = await batch_process_videos(video_tasks)
    elapsed = time.time() - start_time

    print(f"\nAll videos processed in {elapsed:.2f} seconds")

    # Create a summary video from all processed videos
    await create_video_summary(
        processed_videos,
        os.path.join("test_outputs", "summary_grid.mp4")
    )

    print("\nAll processing complete! Check the test_outputs directory.")

def main():
    """Run the async example."""
    asyncio.run(main_async())

if __name__ == "__main__":
    main()
