from typing import Optional
from math import *
import matplotlib.pyplot as plot
from numpy import linspace
from rich import print

def newton(f, df, u_0, ε=1e-12) -> Optional[tuple[float, int]]:
    """
    >>> newton(
    ...     lambda x: x**3 - 2*x - 5,
    ...     lambda x: 3*(x**2) - 2,
    ...     u_0=3, ε=1e-12
    ... )
    (2.0945514815423265, 6)
    """
    assert ε > 0
    MAX_ITER = 100
    next_u = lambda u_prev: u_prev - f(u_prev)/df(u_prev)
    iters = 1

    u_prev = u_0
    u_next = next_u(u_prev)

    while abs(u_next - u_prev) > ε:
        u_prev = u_next
        u_next = next_u(u_prev)
        iters += 1

        if iters > MAX_ITER:
            return None

    return u_next, iters

def graph_function(f, domain: tuple[int, int]) -> None:
    """
    >>> graph_function(
    ...     lambda x: x**3 - 2*x - 5,
    ...     (-3, 3)
    ... )
    """
    plot.figure(0)
    x = linspace(*domain, 256)
    plot.plot(x, list(map(f, x)))
    plot.grid()
    plot.show()

def newton_chireux(f, df, u, ε=1e-12):
    assert ε > 0
    MAX_ITERS = 100

    iters = 0
    while abs(f(u)) >= ε:
        u -= f(u) / df(u)
        n += 1
        if n > MAX_ITERS:
            return None
    return u, n


def zeros(f, df, domain: tuple[int, int], tries: int = 100, ε=1e-12):
    """
    >>> len(zeros(
    ...     lambda x: 3*sin(4*x) + x**2 - 2,
    ...     lambda x: 3*4*cos(4*x) + 2*x,
    ...     domain=(-3, 3)
    ... )) == 6
    True
    """
    from random import random
    assert ε > 0
    MAX_ITERS = 100
    found = []
    a, b = domain

    for _ in range(tries):
        u = a + random() * (b - a)
        x = newton(f, df, u)
        if x is not None and round(x[0], 12) not in found:
            found.append(round(x[0], 12))

    return found

def airy_spot():
    """
    >>> airy_spot()
    """
    import scipy.integrate
    f = lambda x:  scipy.integrate.quad(lambda t: sqrt(1 - t**2) * cos(x * t), -1, 1)[0]
    df = lambda x: scipy.integrate.quad(lambda t: t * sqrt(1 - t**2) * sin(x * t), -1, 1)[0]
    graph_function(f, (-100, 100))
    

