#!/usr/bin/env python3
"""Check conservative coverage of recursive call dependencies."""
import ast
from pathlib import Path
import sys
import subprocess
import textwrap
import tempfile

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from lang.py.support import SupportChecker


def issues(source):
    return SupportChecker(ast.parse(textwrap.dedent(source))).check("f")


base = """
def f(n: int) -> int:
    return g(n)
def g(n: int) -> int:
    return n
"""
assert not issues(base)
for suffix in (
    "import other as g\n",
    "from other import g\n",
    "from other import *\n",
    "class g: pass\n",
    "if True:\n    def g(n: int): return n + 1\n",
    "del g\n",
    "g = 1\n",
):
    assert issues(base + suffix), suffix

for body in (
    'return missing(n)',
    'if False: print(n)\n    return n',
    'global counter\n    counter = n\n    return n',
    'g = n\n    return n',
    'a, b = (n,)\n    return a',
    'a, *b = n, n + 1\n    return a',
    '(a, b), c = (n, n), n\n    return c',
):
    assert issues(base.replace('return n', body)), body
with_default = base.replace('g(n: int)', 'g(n: int = 0)')
assert not issues(with_default)
assert not issues(with_default.replace('return g(n)', 'return g()'))
assert not issues(base.replace('g(n: int)', 'g(n: list[int])'))
assert issues(base.replace('g(n: int)', 'g(n: dict[int, int])'))
assert not issues(base.replace('return n', 'return f(n - 1) if n else 0'))
range_source = base.replace('return g(n)', 'for k in range(n, 0, -1):\n        n = g(n)\n    return n')
assert not issues(range_source)
for suffix in ('range = g\n', 'from other import range\n', 'from other import *\n', 'del range\n'):
    assert issues(range_source + suffix), suffix
for replacement in ('range()', 'range(n, 0, -1, 2)', 'range(n, stop=0)', 'iter(n)'):
    assert issues(range_source.replace('range(n, 0, -1)', replacement)), replacement
assert issues(range_source.replace('def f(n: int)', 'def f(n: int, range: int)'))
assert not issues(range_source.replace('n = g(n)', 'break'))
assert not issues(range_source.replace('n = g(n)', 'continue'))
for transfer in ('break', 'continue'):
    assert issues(f'def f(n: int):\n    {transfer}\n')
    assert issues(f'def f(n: int):\n    for k in range(n):\n        pass\n    else:\n        {transfer}\n')
    assert not issues(f'def f(n: int):\n    while n > 0:\n        n -= 1\n'
                      f'        for k in range(n):\n            pass\n        else:\n            {transfer}\n'
                      f'    return n\n')
sqrt_source = 'from math import isqrt as root\ndef f(n: int) -> int:\n    return root(n)\n'
assert not issues(sqrt_source)
for suffix in ('root = f\n', 'from other import root\n', 'del root\n'):
    assert issues(sqrt_source + suffix), suffix
assert issues(sqrt_source.replace('return root(n)', 'root = n\n    return root(n)'))
assert issues(sqrt_source.replace('root(n)', 'root(n, n)'))
assert issues(sqrt_source.replace('root(n)', 'root(n=n)'))
assert issues(sqrt_source.replace('from math ', 'from other '))
with tempfile.TemporaryDirectory(prefix='formalsw-math-shadow-') as directory:
    program = ast.parse(sqrt_source)
    program.formalsw_source = Path(directory) / 'probe.py'
    (Path(directory) / 'math.py').write_text('def isqrt(n): return n\n')
    assert any('shadows the modeled standard library' in issue.reason
               for issue in SupportChecker(program).check('f'))
sum_source = 'def f(n: int) -> int:\n    return sum(x for x in range(n) if x % 2 == 0)\n'
assert not issues(sum_source)
for suffix in ('sum = f\n', 'range = f\n', 'from other import sum\n', 'from other import *\n'):
    assert issues(sum_source + suffix), suffix
for expression in (
    'sum(x for x in range(n) for y in range(n))',
    'sum((x for x in range(n)), 1)',
    'sum(x for x in range(n) if missing(x))',
    'sum(x for x in range(n)) + x',
    'x + sum(x for x in range(n))',
    'sum(f(n) for f in range(n))',
    'sum(x for x in range(n, stop=1))',
):
    source = sum_source.replace('sum(x for x in range(n) if x % 2 == 0)', expression)
    assert issues(source), expression
assert issues(sum_source.replace('def f(n: int)', 'def f(n: int, sum: int)'))
assert not issues(sum_source.replace('sum(x for x in range(n) if x % 2 == 0)',
                                   'sum(n for n in range(n)) + n'))
subprocess.run([sys.executable, str(Path(__file__).with_name('loop_control.py'))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name('string_checks'))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name('bytes_checks'))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name('power_checks'))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("default_checks"))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("heap_call_checks"))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("math_gcd_checks"))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("math_module_checks"))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("range_sum_checks"))], check=True)
subprocess.run([sys.executable, str(Path(__file__).with_name("bit_length_checks"))], check=True)
print("Recursive call and loop-control coverage passed")
