#!/usr/bin/env python3
"""
TENGWAR Test Suite
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from tengwar import tokenize, parse, Interpreter
from tengwar.errors import TengwarError, ProofError
from tengwar.interpreter import *

passed = 0
failed = 0

def test(name, source, expected):
    global passed, failed
    interp = Interpreter()
    try:
        result = interp.run_source(source)
        actual = repr(result)
        if actual == expected:
            print(f"  ✓ {name}")
            passed += 1
        else:
            print(f"  ✗ {name}: expected {expected}, got {actual}")
            failed += 1
    except Exception as e:
        print(f"  ✗ {name}: {e}")
        failed += 1

def test_error(name, source, error_type=TengwarError):
    global passed, failed
    interp = Interpreter()
    try:
        interp.run_source(source)
        print(f"  ✗ {name}: expected error, got none")
        failed += 1
    except error_type:
        print(f"  ✓ {name}")
        passed += 1
    except Exception as e:
        print(f"  ✗ {name}: expected {error_type.__name__}, got {type(e).__name__}: {e}")
        failed += 1

print("=== TENGWAR Test Suite ===\n")

print("Literals:")
test("integer", "42", "42")
test("negative int", "-7", "-7")
test("float", "3.14", "3.14")
test("string", '"hello"', '"hello"')
test("bool true", "true", "true")
test("bool false", "false", "false")
test("unit", "∅", "∅")

print("\nArithmetic:")
test("add", "(+ 2 3)", "5")
test("sub", "(- 10 4)", "6")
test("mul", "(* 6 7)", "42")
test("div", "(/ 15 3)", "5")
test("mod", "(% 17 5)", "2")
test("nested", "(+ (* 2 3) (- 10 4))", "12")
test("float add", "(+ 1.5 2.5)", "4.0")

print("\nComparison:")
test("eq true", "(= 5 5)", "true")
test("eq false", "(= 5 6)", "false")
test("neq", "(!= 5 6)", "true")
test("lt", "(< 3 5)", "true")
test("gt", "(> 5 3)", "true")
test("lte", "(<= 5 5)", "true")
test("gte", "(>= 5 4)", "true")

print("\nLogic:")
test("and", "(& true true)", "true")
test("and false", "(& true false)", "false")
test("or", "(| false true)", "true")
test("not", "(! false)", "true")

print("\nStrings:")
test("concat", '(+ "hello " "world")', '"hello world"')
test("len", '(len "hello")', "5")
test("upper", '(upper "hello")', '"HELLO"')
test("lower", '(lower "HELLO")', '"hello"')
test("contains", '(contains "hello world" "world")', "true")
test("replace", '(replace "hello" "l" "r")', '"herro"')

print("\nBindings:")
test("bind", "(>> (+ 1 2) → #x #x)", "3")
test("define", "(>> (:= foo 42) foo)", "42")

print("\nLambda:")
test("lambda call", "((λ #x (+ #x 1)) 5)", "6")
test("lambda 2 args", "((λ #x #y (+ #x #y)) 3 4)", "7")
test("closure", "(>> (:= adder (λ #x (λ #y (+ #x #y)))) ((adder 5) 3))", "8")
test("bind lambda", "(>> (λ #x (* #x #x)) → #sq (#sq 5))", "25")

print("\nConditional:")
test("if true", "(? true 1 2)", "1")
test("if false", "(? false 1 2)", "2")
test("if expr", "(? (> 5 3) \"yes\" \"no\")", '"yes"')

print("\nRecursion:")
test("factorial", "(>> (↺ f (λ #n (? (<= #n 1) 1 (* #n (f (- #n 1)))))) (f 5))", "120")
test("fibonacci", "(>> (↺ fib (λ #n (? (<= #n 1) #n (+ (fib (- #n 1)) (fib (- #n 2)))))) (fib 10))", "55")

print("\nVectors:")
test("vector lit", "⟦1 2 3⟧", "⟦1 2 3⟧")
test("vec len", "(len ⟦1 2 3⟧)", "3")
test("vec get", "(get ⟦10 20 30⟧ 1)", "20")
test("vec push", "(push ⟦1 2⟧ 3)", "⟦1 2 3⟧")
test("vec map", "(map (λ #x (* #x 2)) ⟦1 2 3⟧)", "⟦2 4 6⟧")
test("vec filter", "(filter (λ #x (> #x 2)) ⟦1 2 3 4 5⟧)", "⟦3 4 5⟧")
test("vec reduce", "(reduce (λ #a #b (+ #a #b)) 0 ⟦1 2 3 4 5⟧)", "15")
test("range", "(range 1 6)", "⟦1 2 3 4 5⟧")
test("reverse", "(reverse ⟦1 2 3⟧)", "⟦3 2 1⟧")
test("sort", "(sort ⟦3 1 2⟧)", "⟦1 2 3⟧")
test("head", "(head ⟦1 2 3⟧)", "1")
test("tail", "(tail ⟦1 2 3⟧)", "⟦2 3⟧")
test("empty?", "(empty? ⟦⟧)", "true")
test("zip", "(zip ⟦1 2 3⟧ ⟦4 5 6⟧)", "⟦⟨1 4⟩ ⟨2 5⟩ ⟨3 6⟩⟧")

print("\nTuples:")
test("tuple", "⟨1 2 3⟩", "⟨1 2 3⟩")
test("fst", "(fst ⟨1 2⟩)", "1")
test("snd", "(snd ⟨1 2⟩)", "2")
test("nth", "(nth ⟨10 20 30⟩ 2)", "30")

print("\nSequence:")
test("seq", "(>> 1 2 3)", "3")
test("seq with bind", "(>> 5 → #x (* #x 2) → #y (+ #y 1))", "11")

print("\nParallel:")
test("parallel", "(∥ (+ 1 2) (* 3 4) (- 10 5))", "⟨3 12 5⟩")

print("\nPattern Match:")
test("match lit", '(~ 1 0 "zero" 1 "one" _ "other")', '"one"')
test("match wildcard", '(~ 99 0 "zero" 1 "one" _ "other")', '"other"')
test("match bind", "(~ 42 #x (+ #x 1))", "43")

print("\nMutable State:")
test("mutate", "(>> (μ #c 0) (set! #c 5) (deref #c))", "5")

print("\nProofs:")
test("proof pass", "(⊢ (= 2 2))", "true")
test_error("proof fail", "(⊢ (= 2 3))", ProofError)

print("\nHigher-order:")
test("compose", "(>> (:= f (λ #x (+ #x 1))) (:= g (λ #x (* #x 2))) ((compose f g) 5))", "11")
test("apply-twice", "(>> (:= dbl (λ #x (* #x 2))) (:= twice (λ #f #x (#f (#f #x)))) (twice dbl 3))", "12")

print("\nType Checks:")
test("int?", "(int? 42)", "true")
test("str?", '(str? "hi")', "true")
test("fn?", "(fn? (λ #x #x))", "true")
test("vec?", "(vec? ⟦1 2⟧)", "true")

print("\nASCII Fallbacks:")
test("ascii lambda", "((\\  #x (+ #x 1)) 10)", "11")
test("ascii arrow", "(>> (+ 1 2) -> #x #x)", "3")
test("ascii parallel", "(|| (+ 1 2) (* 3 4))", "⟨3 12⟩")
test("ascii tuple", "<| 1 2 3 |>", "⟨1 2 3⟩")
test("ascii vector", "[| 1 2 3 |]", "⟦1 2 3⟧")
test("ascii proof", "(|- (= 1 1))", "true")


print("\nShort Lambdas:")
test("short lambda filter", "(filter {> _ 3} ⟦1 2 3 4 5⟧)", "⟦4 5⟧")
test("short lambda map", "(map {+ _ 10} ⟦1 2 3⟧)", "⟦11 12 13⟧")
test("short lambda field access", '(map {_.1} ⟦⟨"a" 1⟩ ⟨"b" 2⟩⟧)', "⟦1 2⟧")
test("short lambda comparison", "(filter {>= _ 70} ⟦45 78 92 61⟧)", "⟦78 92⟧")

print("\nBare Operators:")
test("bare + reduce", "(reduce + 0 ⟦1 2 3 4 5⟧)", "15")
test("bare * reduce", "(reduce * 1 ⟦1 2 3 4 5⟧)", "120")

print("\nDot Indexing:")
test("dot tuple", "(>> ⟨10 20 30⟩ → t t.1)", "20")
test("dot vector", "(>> ⟦10 20 30⟧ → v v.0)", "10")
test("dot chain", '(>> ⟦⟨"a" 1⟩ ⟨"b" 2⟩⟧ → data data.1.0)', '"b"')

print("\nNew Builtins:")
test("sum", "(sum ⟦1 2 3 4 5⟧)", "15")
test("product", "(product ⟦1 2 3 4 5⟧)", "120")
test("count", "(count {> _ 3} ⟦1 2 3 4 5 6⟧)", "3")
test("any true", "(any {> _ 10} ⟦1 2 15⟧)", "true")
test("any false", "(any {> _ 20} ⟦1 2 15⟧)", "false")
test("all true", "(all {> _ 0} ⟦1 2 3⟧)", "true")
test("all false", "(all {> _ 2} ⟦1 2 3⟧)", "false")


print(f"Results: {passed} passed, {failed} failed, {passed+failed} total")
if failed > 0:
    sys.exit(1)
else:
    print("All tests passed! ✓")
