import numpy as np
from typing import Callable


# def f(x: float) -> float:
#     return x ** 4 + x ** 3 + x ** 2 - 1


def g(x: float) -> float:
    return x ** 2 - x - 1


def h(x: float) -> float:
    return x ** 2 - 25


def z(x: float) -> float:
    return x ** 3 - x ** 2 - 1


def rec_regula_falsi(start: float, stop: float, func: Callable[[float], float],
                     debug: bool) -> float:
    cN = (start * func(stop) - stop * func(start)) / (func(stop) - func(start))

    if np.sign(func(cN)) != np.sign(func(start)):
        stop = cN
    elif np.sign(func(cN)) != np.sign(func(stop)):
        start = cN

    if np.abs(func(stop) - func(start)) > 1e-9:
        if debug:
            print(f"x = {cN}\tf(x) = {func(cN)}")
        # Testing the y-axis
        if np.abs(func(cN)) < 1e-9:
            return cN

        return rec_regula_falsi(start, stop, func, debug)
    else:
        return cN


def iter_regula_falsi(init_start: float, init_stop: float, func:
                      Callable[[float], float], debug: bool) -> float:
    cN = (init_start * func(init_stop) - init_stop *
          func(init_start)) / (func(init_stop) - func(init_start))
    start = init_start
    stop = init_stop

    iter = 0

    while np.abs(func(stop) - func(start)) > 1e-9 or np.abs(func(cN)) > 1e-9:
        if debug:
            print(f"Iter = {iter}\tx = {cN}\tf(x) = {func(cN)}")

        cN = (start * func(stop) - stop * func(start)) / \
            (func(stop) - func(start))

        if np.sign(func(cN)) != np.sign(func(start)):
            stop = cN
        elif np.sign(func(cN)) != np.sign(func(stop)):
            start = cN

        iter += 1

    return cN


def rec_bisection(start: float, stop: float, func: Callable[[float], float],
                  debug: bool) -> float:
    cN = (stop + start) / 2

    if np.sign(func(cN)) != np.sign(func(start)):
        stop = cN
    elif np.sign(func(cN)) != np.sign(func(stop)):
        start = cN

    if np.abs(stop - start) > 1e-9:
        if debug:
            print(f"x = {cN}\tf(x) = {func(cN)}")

        return rec_bisection(start, stop, func, debug)
    else:
        return cN


def iter_bisection(init_start: float, init_stop: float, func:
                   Callable[[float], float], debug: bool) -> float:
    cN = (init_stop + init_start) / 2
    start = init_start
    stop = init_stop

    iter = 0

    while np.abs(stop - start) > 1e-8:
        if debug:
            print(f"Iter = {iter}\tx = {cN}\tf(x) = {func(cN)}")

        cN = (stop + start) / 2

        if np.sign(func(cN)) != np.sign(func(start)):
            stop = cN
        elif np.sign(func(cN)) != np.sign(func(stop)):
            start = cN

        iter += 1

    return cN


def iter_newton_raphson(init_guess: float, f: Callable[[float], float],
                        dfdx: Callable[[float], float], debug: bool) -> float:

    prev = init_guess
    curr = init_guess

    iter = 0

    while np.abs(f(curr)) > 1e-9:
        if debug:
            print(f"Iter = {iter}\tx = {curr}\tf(x) = {f(curr)}")
        curr = prev - f(prev) / dfdx(prev)
        prev = curr
        iter += 1

    return curr


if __name__ == "__main__":
    # print(f"Bissection = {bissect_method(0.5, 2, g)}")
    # print(f"Regula falsi = {regula_falsi(-3, 7, h)}")
    # print(f"Bissection = {bissect_method(-3, 7, h)}")
    # print(f"Regula falsi = {rec_regula_falsi(0, 3, z)}")
    # print(f"Iter bisection= {iter_bisection(0.5, 2, g, False)}")
    # print(f"Rec bisection= {rec_bisection(0.5, 2, g, False)}")
    # print(f"Iter regula falsi= {iter_regula_falsi(0.5, 2, g, False)}")
    # print(f"Rec regula falsi = {rec_regula_falsi(0.5, 2, g, False)}")

    def f(x):
        return x ** 3 - 2 * x ** 2 + 1

    def fprime(x):
        return 3 * x ** 2 - 4 * x + 1

    print(f"Newton = {iter_newton_raphson(0, f, fprime, True)}")