#!/usr/bin/env python3
"""
Funky comparison tests: PIL vs TextStrip system.
Tests colors, opacity, fonts, sizes, edge positions, clipping, overlaps, etc.
"""

import numpy as np
import jax.numpy as jnp
from PIL import Image, ImageDraw, ImageFont
from streaming.jax_typography import (
    render_text_strip, place_text_strip_jax, _load_font
)

FONTS = {
    'sans': '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
    'bold': '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
    'serif': '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf',
    'serif_bold': '/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf',
    'mono': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',
    'mono_bold': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf',
    'narrow': '/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf',
    'italic': '/usr/share/fonts/truetype/freefont/FreeSansOblique.ttf',
}


def render_pil(text, x, y, font_path=None, font_size=36, frame_size=(400, 100),
               fill=(255, 255, 255), bg=(0, 0, 0), opacity=1.0,
               stroke_width=0, stroke_fill=None, anchor="la",
               multiline=False, line_spacing=4, align="left"):
    """Render with PIL directly, including color/opacity."""
    frame = np.full((frame_size[1], frame_size[0], 3), bg, dtype=np.uint8)
    # For opacity, render to RGBA then composite
    txt_layer = Image.new('RGBA', frame_size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(txt_layer)
    font = _load_font(font_path, font_size)

    if stroke_fill is None:
        stroke_fill = (0, 0, 0)

    # PIL fill with alpha for opacity
    alpha_int = int(round(opacity * 255))
    fill_rgba = (*fill, alpha_int)
    stroke_rgba = (*stroke_fill, alpha_int) if stroke_width > 0 else None

    if multiline:
        draw.multiline_text((x, y), text, fill=fill_rgba, font=font,
                           stroke_width=stroke_width, stroke_fill=stroke_rgba,
                           spacing=line_spacing, align=align, anchor=anchor)
    else:
        draw.text((x, y), text, fill=fill_rgba, font=font,
                  stroke_width=stroke_width, stroke_fill=stroke_rgba, anchor=anchor)

    # Composite onto background
    bg_img = Image.fromarray(frame).convert('RGBA')
    result = Image.alpha_composite(bg_img, txt_layer)
    return np.array(result.convert('RGB'))


def render_strip(text, x, y, font_path=None, font_size=36, frame_size=(400, 100),
                 fill=(255, 255, 255), bg=(0, 0, 0), opacity=1.0,
                 stroke_width=0, stroke_fill=None, anchor="la",
                 multiline=False, line_spacing=4, align="left"):
    """Render with TextStrip system."""
    frame = jnp.full((frame_size[1], frame_size[0], 3), jnp.array(bg, dtype=jnp.uint8), dtype=jnp.uint8)

    strip = render_text_strip(
        text, font_path, font_size,
        stroke_width=stroke_width, stroke_fill=stroke_fill,
        anchor=anchor, multiline=multiline, line_spacing=line_spacing, align=align
    )
    strip_img = jnp.asarray(strip.image)
    color = jnp.array(fill, dtype=jnp.float32)

    result = place_text_strip_jax(
        frame, strip_img, x, y,
        strip.baseline_y, strip.bearing_x,
        color, opacity,
        anchor_x=strip.anchor_x, anchor_y=strip.anchor_y,
        stroke_width=strip.stroke_width
    )
    return np.array(result)


def compare(name, tolerance=0, **kwargs):
    """Compare PIL and TextStrip rendering."""
    pil = render_pil(**kwargs)
    strip = render_strip(**kwargs)

    diff = np.abs(pil.astype(np.int16) - strip.astype(np.int16))
    max_diff = diff.max()
    pixels_diff = (diff > 0).any(axis=2).sum()

    if max_diff == 0:
        print(f"  PASS: {name}")
        return True

    if tolerance > 0:
        best_diff = diff.copy()
        for dy in range(-tolerance, tolerance + 1):
            for dx in range(-tolerance, tolerance + 1):
                if dy == 0 and dx == 0:
                    continue
                shifted = np.roll(np.roll(strip, dy, axis=0), dx, axis=1)
                sdiff = np.abs(pil.astype(np.int16) - shifted.astype(np.int16))
                best_diff = np.minimum(best_diff, sdiff)
        if best_diff.max() == 0:
            print(f"  PASS: {name} (within {tolerance}px tolerance)")
            return True

    print(f"  FAIL: {name}")
    print(f"        Max diff: {max_diff}, Pixels different: {pixels_diff}")
    Image.fromarray(pil).save(f"/tmp/pil_{name}.png")
    Image.fromarray(strip).save(f"/tmp/strip_{name}.png")
    diff_vis = np.clip(diff * 10, 0, 255).astype(np.uint8)
    Image.fromarray(diff_vis).save(f"/tmp/diff_{name}.png")
    print(f"        Saved: /tmp/pil_{name}.png /tmp/strip_{name}.png /tmp/diff_{name}.png")
    return False


def test_colors():
    """Test various text colors on various backgrounds."""
    print("\n--- Colors ---")
    results = []

    # White on black (baseline)
    results.append(compare("color_white_on_black",
        text="Hello", x=20, y=30, fill=(255, 255, 255), bg=(0, 0, 0)))

    # Red on black
    results.append(compare("color_red",
        text="Red Text", x=20, y=30, fill=(255, 0, 0), bg=(0, 0, 0)))

    # Green on black
    results.append(compare("color_green",
        text="Green!", x=20, y=30, fill=(0, 255, 0), bg=(0, 0, 0)))

    # Blue on black
    results.append(compare("color_blue",
        text="Blue sky", x=20, y=30, fill=(0, 100, 255), bg=(0, 0, 0)))

    # Yellow on dark gray
    results.append(compare("color_yellow_on_gray",
        text="Yellow", x=20, y=30, fill=(255, 255, 0), bg=(40, 40, 40)))

    # Magenta on white
    results.append(compare("color_magenta_on_white",
        text="Magenta", x=20, y=30, fill=(255, 0, 255), bg=(255, 255, 255)))

    # Subtle: gray text on slightly lighter gray
    results.append(compare("color_subtle_gray",
        text="Subtle", x=20, y=30, fill=(128, 128, 128), bg=(64, 64, 64)))

    # Orange on deep blue
    results.append(compare("color_orange_on_blue",
        text="Warm", x=20, y=30, fill=(255, 165, 0), bg=(0, 0, 80)))

    return results


def test_opacity():
    """Test different opacity levels."""
    print("\n--- Opacity ---")
    results = []

    results.append(compare("opacity_100",
        text="Full", x=20, y=30, opacity=1.0))

    results.append(compare("opacity_75",
        text="75%", x=20, y=30, opacity=0.75))

    results.append(compare("opacity_50",
        text="Half", x=20, y=30, opacity=0.5))

    results.append(compare("opacity_25",
        text="Quarter", x=20, y=30, opacity=0.25))

    results.append(compare("opacity_10",
        text="Ghost", x=20, y=30, opacity=0.1))

    # Opacity on colored background
    results.append(compare("opacity_on_colored_bg",
        text="Overlay", x=20, y=30, fill=(255, 255, 255), bg=(100, 0, 0),
        opacity=0.5))

    # Color + opacity combo
    results.append(compare("opacity_red_on_green",
        text="Blend", x=20, y=30, fill=(255, 0, 0), bg=(0, 100, 0),
        opacity=0.6))

    return results


def test_fonts():
    """Test different fonts and sizes."""
    print("\n--- Fonts & Sizes ---")
    results = []

    for label, path in FONTS.items():
        results.append(compare(f"font_{label}",
            text="Quick Fox", x=20, y=30, font_path=path, font_size=28,
            frame_size=(300, 80)))

    # Tiny text
    results.append(compare("size_tiny",
        text="Tiny text at 12px", x=10, y=15, font_size=12,
        frame_size=(200, 40)))

    # Big text
    results.append(compare("size_big",
        text="BIG", x=20, y=10, font_size=72,
        frame_size=(300, 100)))

    # Huge text
    results.append(compare("size_huge",
        text="XL", x=10, y=10, font_size=120,
        frame_size=(300, 160)))

    return results


def test_anchors():
    """Test all anchor combinations."""
    print("\n--- Anchors ---")
    results = []

    # All horizontal x vertical combos
    for h in ['l', 'm', 'r']:
        for v in ['a', 'm', 's', 'd']:
            anchor = h + v
            results.append(compare(f"anchor_{anchor}",
                text="Anchor", x=200, y=50, anchor=anchor,
                frame_size=(400, 100)))

    return results


def test_strokes():
    """Test various stroke widths and colors."""
    print("\n--- Strokes ---")
    results = []

    for sw in [1, 2, 3, 4, 6, 8]:
        results.append(compare(f"stroke_w{sw}",
            text="Stroke", x=30, y=20, font_size=40,
            stroke_width=sw, stroke_fill=(0, 0, 0),
            frame_size=(300, 80)))

    # Colored strokes
    results.append(compare("stroke_red",
        text="Red outline", x=20, y=20, font_size=36,
        stroke_width=3, stroke_fill=(255, 0, 0),
        frame_size=(350, 80)))

    results.append(compare("stroke_white_on_black",
        text="Glow", x=20, y=20, font_size=40,
        fill=(255, 255, 255), stroke_width=4, stroke_fill=(0, 0, 255),
        frame_size=(250, 80)))

    # Stroke with bold font
    results.append(compare("stroke_bold",
        text="Bold+Stroke", x=20, y=20,
        font_path=FONTS['bold'], font_size=36,
        stroke_width=3, stroke_fill=(0, 0, 0),
        frame_size=(400, 80)))

    # Stroke + colored text on colored bg
    results.append(compare("stroke_colored_on_bg",
        text="Party", x=20, y=20, font_size=48,
        fill=(255, 255, 0), bg=(50, 0, 80),
        stroke_width=3, stroke_fill=(255, 0, 0),
        frame_size=(300, 80)))

    return results


def test_edge_clipping():
    """Test text at frame edges - clipping behavior."""
    print("\n--- Edge Clipping ---")
    results = []

    # Text at very left edge
    results.append(compare("clip_left_edge",
        text="LEFT", x=0, y=30, frame_size=(200, 80)))

    # Text partially off right edge
    results.append(compare("clip_right_edge",
        text="RIGHT SIDE", x=150, y=30, frame_size=(200, 80)))

    # Text at top edge
    results.append(compare("clip_top",
        text="TOP", x=20, y=0, frame_size=(200, 80)))

    # Text at bottom edge - partially clipped
    results.append(compare("clip_bottom",
        text="BOTTOM", x=20, y=55, font_size=40,
        frame_size=(200, 80)))

    # Large text overflowing all sides from center
    results.append(compare("clip_overflow_center",
        text="HUGE", x=75, y=40, font_size=100, anchor="mm",
        frame_size=(150, 80)))

    # Corner placement
    results.append(compare("clip_corner_br",
        text="Corner", x=350, y=70, font_size=36,
        frame_size=(400, 100)))

    return results


def test_multiline_fancy():
    """Test multiline with various styles."""
    print("\n--- Multiline Fancy ---")
    results = []

    # Right-aligned (1px tolerance: same sub-pixel issue as center alignment)
    results.append(compare("multi_right",
        text="Right\nAligned\nText", x=380, y=20,
        frame_size=(400, 150),
        multiline=True, anchor="ra", align="right",
        tolerance=1))

    # Center + stroke
    results.append(compare("multi_center_stroke",
        text="Title\nSubtitle", x=200, y=20,
        font_size=32, frame_size=(400, 120),
        multiline=True, anchor="ma", align="center",
        stroke_width=2, stroke_fill=(0, 0, 0),
        tolerance=1))

    # Wide line spacing
    results.append(compare("multi_wide_spacing",
        text="Line A\nLine B\nLine C", x=20, y=10,
        frame_size=(300, 200),
        multiline=True, line_spacing=20))

    # Zero extra spacing
    results.append(compare("multi_tight_spacing",
        text="Tight\nPacked\nLines", x=20, y=10,
        frame_size=(300, 150),
        multiline=True, line_spacing=0))

    # Many lines
    results.append(compare("multi_many_lines",
        text="One\nTwo\nThree\nFour\nFive\nSix", x=20, y=5,
        font_size=20, frame_size=(200, 200),
        multiline=True, line_spacing=4))

    # Bold multiline with stroke
    results.append(compare("multi_bold_stroke",
        text="BOLD\nSTROKE", x=20, y=10,
        font_path=FONTS['bold'], font_size=48,
        stroke_width=3, stroke_fill=(200, 0, 0),
        frame_size=(350, 150), multiline=True))

    # Multiline on colored bg with opacity
    results.append(compare("multi_opacity_on_bg",
        text="Semi\nTransparent", x=20, y=10,
        fill=(255, 255, 0), bg=(0, 50, 100), opacity=0.7,
        frame_size=(300, 120), multiline=True))

    return results


def test_special_chars():
    """Test special characters and edge cases."""
    print("\n--- Special Characters ---")
    results = []

    # Numbers and symbols
    results.append(compare("chars_numbers",
        text="0123456789", x=20, y=30, frame_size=(300, 80)))

    results.append(compare("chars_punctuation",
        text="Hello, World! (v2.0)", x=10, y=30, frame_size=(350, 80)))

    results.append(compare("chars_symbols",
        text="@#$%^&*+=", x=20, y=30, frame_size=(300, 80)))

    # Single character
    results.append(compare("chars_single",
        text="X", x=50, y=30, font_size=48, frame_size=(100, 80)))

    # Very long text (clipped)
    results.append(compare("chars_long",
        text="The quick brown fox jumps over the lazy dog", x=10, y=30,
        font_size=24, frame_size=(400, 80)))

    # Mixed case
    results.append(compare("chars_mixed_case",
        text="AaBbCcDdEeFf", x=10, y=30, frame_size=(350, 80)))

    return results


def test_combos():
    """Complex combinations of features."""
    print("\n--- Combos ---")
    results = []

    # Big bold stroke + color + opacity
    results.append(compare("combo_all_features",
        text="EPIC", x=20, y=10,
        font_path=FONTS['bold'], font_size=64,
        fill=(255, 200, 0), bg=(20, 0, 40), opacity=0.85,
        stroke_width=4, stroke_fill=(180, 0, 0),
        frame_size=(350, 100)))

    # Small mono on busy background
    results.append(compare("combo_mono_code",
        text="fn main() {}", x=10, y=15,
        font_path=FONTS['mono'], font_size=16,
        fill=(0, 255, 100), bg=(30, 30, 30),
        frame_size=(250, 50)))

    # Serif italic multiline with stroke
    results.append(compare("combo_serif_italic_multi",
        text="Once upon\na time...", x=20, y=10,
        font_path=FONTS['italic'], font_size=28,
        stroke_width=1, stroke_fill=(80, 80, 80),
        frame_size=(300, 120), multiline=True))

    # Narrow font, big stroke, center anchored
    results.append(compare("combo_narrow_stroke_center",
        text="NARROW", x=150, y=40,
        font_path=FONTS['narrow'], font_size=44,
        stroke_width=5, stroke_fill=(0, 0, 0),
        anchor="mm", frame_size=(300, 80)))

    # Multiple strips on same frame (simulated by sequential placement)
    results.append(compare("combo_opacity_blend",
        text="Layered", x=20, y=30,
        fill=(255, 0, 0), bg=(0, 0, 255), opacity=0.5,
        font_size=48, frame_size=(300, 80)))

    return results


def test_multi_strip_overlay():
    """Test placing multiple strips on the same frame."""
    print("\n--- Multi-Strip Overlay ---")
    results = []

    frame_size = (400, 150)
    bg = (20, 20, 40)

    # PIL version - multiple draw calls
    font1 = _load_font(FONTS['bold'], 48)
    font2 = _load_font(None, 24)
    font3 = _load_font(FONTS['mono'], 18)

    pil_frame = np.full((frame_size[1], frame_size[0], 3), bg, dtype=np.uint8)
    txt_layer = Image.new('RGBA', frame_size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(txt_layer)
    draw.text((20, 10), "TITLE", fill=(255, 255, 0, 255), font=font1,
              stroke_width=2, stroke_fill=(0, 0, 0, 255))
    draw.text((20, 70), "Subtitle here", fill=(200, 200, 200, 255), font=font2)
    draw.text((20, 110), "code_snippet()", fill=(0, 255, 128, 200), font=font3)
    bg_img = Image.fromarray(pil_frame).convert('RGBA')
    pil_result = np.array(Image.alpha_composite(bg_img, txt_layer).convert('RGB'))

    # Strip version - multiple placements
    frame = jnp.full((frame_size[1], frame_size[0], 3), jnp.array(bg, dtype=jnp.uint8), dtype=jnp.uint8)

    s1 = render_text_strip("TITLE", FONTS['bold'], 48, stroke_width=2, stroke_fill=(0, 0, 0))
    s2 = render_text_strip("Subtitle here", None, 24)
    s3 = render_text_strip("code_snippet()", FONTS['mono'], 18)

    frame = place_text_strip_jax(
        frame, jnp.asarray(s1.image), 20, 10,
        s1.baseline_y, s1.bearing_x,
        jnp.array([255, 255, 0], dtype=jnp.float32), 1.0,
        anchor_x=s1.anchor_x, anchor_y=s1.anchor_y,
        stroke_width=s1.stroke_width)

    frame = place_text_strip_jax(
        frame, jnp.asarray(s2.image), 20, 70,
        s2.baseline_y, s2.bearing_x,
        jnp.array([200, 200, 200], dtype=jnp.float32), 1.0,
        anchor_x=s2.anchor_x, anchor_y=s2.anchor_y,
        stroke_width=s2.stroke_width)

    frame = place_text_strip_jax(
        frame, jnp.asarray(s3.image), 20, 110,
        s3.baseline_y, s3.bearing_x,
        jnp.array([0, 255, 128], dtype=jnp.float32), 200/255,
        anchor_x=s3.anchor_x, anchor_y=s3.anchor_y,
        stroke_width=s3.stroke_width)

    strip_result = np.array(frame)

    diff = np.abs(pil_result.astype(np.int16) - strip_result.astype(np.int16))
    max_diff = diff.max()
    pixels_diff = (diff > 0).any(axis=2).sum()

    if max_diff <= 1:
        print(f"  PASS: multi_overlay (max_diff={max_diff})")
        results.append(True)
    else:
        print(f"  FAIL: multi_overlay")
        print(f"        Max diff: {max_diff}, Pixels different: {pixels_diff}")
        Image.fromarray(pil_result).save("/tmp/pil_multi_overlay.png")
        Image.fromarray(strip_result).save("/tmp/strip_multi_overlay.png")
        diff_vis = np.clip(diff * 10, 0, 255).astype(np.uint8)
        Image.fromarray(diff_vis).save("/tmp/diff_multi_overlay.png")
        print(f"        Saved: /tmp/pil_multi_overlay.png /tmp/strip_multi_overlay.png /tmp/diff_multi_overlay.png")
        results.append(False)

    return results


def main():
    print("=" * 60)
    print("Funky TextStrip vs PIL Comparison")
    print("=" * 60)

    all_results = []
    all_results.extend(test_colors())
    all_results.extend(test_opacity())
    all_results.extend(test_fonts())
    all_results.extend(test_anchors())
    all_results.extend(test_strokes())
    all_results.extend(test_edge_clipping())
    all_results.extend(test_multiline_fancy())
    all_results.extend(test_special_chars())
    all_results.extend(test_combos())
    all_results.extend(test_multi_strip_overlay())

    print("\n" + "=" * 60)
    passed = sum(all_results)
    total = len(all_results)
    print(f"Results: {passed}/{total} passed")
    if passed == total:
        print("ALL TESTS PASSED!")
    else:
        failed = [i for i, r in enumerate(all_results) if not r]
        print(f"FAILED: {total - passed} tests (indices: {failed})")
    print("=" * 60)


if __name__ == "__main__":
    main()
