#!/usr/bin/env python3
"""
Test the S-expression effect interpreter.
"""

import numpy as np
import sys
from pathlib import Path

# Add parent to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from sexp_effects import (
    get_interpreter,
    load_effects_dir,
    run_effect,
    list_effects,
    parse,
)


def test_parser():
    """Test S-expression parser."""
    print("Testing parser...")

    # Simple expressions
    assert parse("42") == 42
    assert parse("3.14") == 3.14
    assert parse('"hello"') == "hello"
    assert parse("true") == True

    # Lists
    assert parse("(+ 1 2)")[0].name == "+"
    assert parse("(+ 1 2)")[1] == 1

    # Nested
    expr = parse("(define x (+ 1 2))")
    assert expr[0].name == "define"

    print("  Parser OK")


def test_interpreter_basics():
    """Test basic interpreter operations."""
    print("Testing interpreter basics...")

    interp = get_interpreter()

    # Math
    assert interp.eval(parse("(+ 1 2)")) == 3
    assert interp.eval(parse("(* 3 4)")) == 12
    assert interp.eval(parse("(- 10 3)")) == 7

    # Comparison
    assert interp.eval(parse("(< 1 2)")) == True
    assert interp.eval(parse("(> 1 2)")) == False

    # Let binding
    assert interp.eval(parse("(let ((x 5)) x)")) == 5
    assert interp.eval(parse("(let ((x 5) (y 3)) (+ x y))")) == 8

    # Lambda
    result = interp.eval(parse("((lambda (x) (* x 2)) 5)"))
    assert result == 10

    # If
    assert interp.eval(parse("(if true 1 2)")) == 1
    assert interp.eval(parse("(if false 1 2)")) == 2

    print("  Interpreter basics OK")


def test_primitives():
    """Test image primitives."""
    print("Testing primitives...")

    interp = get_interpreter()

    # Create test image
    img = np.zeros((100, 100, 3), dtype=np.uint8)
    img[50, 50] = [255, 128, 64]

    interp.global_env.set('test_img', img)

    # Width/height
    assert interp.eval(parse("(width test_img)")) == 100
    assert interp.eval(parse("(height test_img)")) == 100

    # Pixel
    pixel = interp.eval(parse("(pixel test_img 50 50)"))
    assert pixel == [255, 128, 64]

    # RGB
    color = interp.eval(parse("(rgb 100 150 200)"))
    assert color == [100, 150, 200]

    # Luminance
    lum = interp.eval(parse("(luminance (rgb 100 100 100))"))
    assert abs(lum - 100) < 1

    print("  Primitives OK")


def test_effect_loading():
    """Test loading effects from .sexp files."""
    print("Testing effect loading...")

    # Load all effects
    effects_dir = Path(__file__).parent / "effects"
    load_effects_dir(str(effects_dir))

    effects = list_effects()
    print(f"  Loaded {len(effects)} effects: {', '.join(sorted(effects))}")

    assert len(effects) > 0
    print("  Effect loading OK")


def test_effect_execution():
    """Test running effects on images."""
    print("Testing effect execution...")

    # Create test image
    img = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)

    # Load effects
    effects_dir = Path(__file__).parent / "effects"
    load_effects_dir(str(effects_dir))

    # Test each effect
    effects = list_effects()
    passed = 0
    failed = []

    for name in sorted(effects):
        try:
            result, state = run_effect(name, img.copy(), {'_time': 0.5}, {})
            assert isinstance(result, np.ndarray)
            assert result.shape == img.shape
            passed += 1
            print(f"    {name}: OK")
        except Exception as e:
            failed.append((name, str(e)))
            print(f"    {name}: FAILED - {e}")

    print(f"  Passed: {passed}/{len(effects)}")
    if failed:
        print(f"  Failed: {[f[0] for f in failed]}")

    return passed, failed


def test_ascii_fx_zone():
    """Test ascii_fx_zone effect with zone expressions."""
    print("Testing ascii_fx_zone...")

    interp = get_interpreter()

    # Load the effect
    effects_dir = Path(__file__).parent / "effects"
    load_effects_dir(str(effects_dir))

    # Create gradient test frame
    frame = np.zeros((120, 160, 3), dtype=np.uint8)
    for x in range(160):
        frame[:, x] = int(x / 160 * 255)
    frame = np.stack([frame[:,:,0]]*3, axis=2)

    # Test 1: Basic without expressions
    result, _ = run_effect('ascii_fx_zone', frame, {'cols': 20}, {})
    assert result.shape == frame.shape
    print("    Basic run: OK")

    # Test 2: With zone-lum expression
    expr = parse('(* zone-lum 180)')
    result, _ = run_effect('ascii_fx_zone', frame, {
        'cols': 20,
        'char_hue': expr
    }, {})
    assert result.shape == frame.shape
    print("    Zone-lum expression: OK")

    # Test 3: With multiple expressions
    scale_expr = parse('(+ 0.5 (* zone-lum 0.5))')
    rot_expr = parse('(* zone-row-norm 30)')
    result, _ = run_effect('ascii_fx_zone', frame, {
        'cols': 20,
        'char_scale': scale_expr,
        'char_rotation': rot_expr
    }, {})
    assert result.shape == frame.shape
    print("    Multiple expressions: OK")

    # Test 4: With numeric literals
    result, _ = run_effect('ascii_fx_zone', frame, {
        'cols': 20,
        'char_hue': 90,
        'char_scale': 1.2
    }, {})
    assert result.shape == frame.shape
    print("    Numeric literals: OK")

    # Test 5: Zone position expressions
    col_expr = parse('(* zone-col-norm 360)')
    result, _ = run_effect('ascii_fx_zone', frame, {
        'cols': 20,
        'char_hue': col_expr
    }, {})
    assert result.shape == frame.shape
    print("    Zone position expression: OK")

    print("  ascii_fx_zone OK")


def main():
    print("=" * 60)
    print("S-Expression Effect Interpreter Tests")
    print("=" * 60)

    test_parser()
    test_interpreter_basics()
    test_primitives()
    test_effect_loading()
    test_ascii_fx_zone()
    passed, failed = test_effect_execution()

    print("=" * 60)
    if not failed:
        print("All tests passed!")
    else:
        print(f"Tests completed with {len(failed)} failures")
    print("=" * 60)


if __name__ == "__main__":
    main()
