import math
from typing import Sequence
import unittest
import random

def wallis(n):
    pie = 2.0
    for w in range(1,n+1):
        pie *= (4*w*w)/(4*w*w - 1 )
    return pie

        
def monte_carlo(noOfDarts):
    pie = float()
    distance = float()
    circleChance =  0.0
    origin = [0.0,0.0]
    point = [0.0,0.0]
    for m in range(1,noOfDarts + 1):
        point[0] = random.random()
        point[1] = random.random()
        distance = math.dist(point,origin)
        if distance < 1.0:
            circleChance += 1.0  
    return 4*circleChance/(noOfDarts )    



class TestWallis(unittest.TestCase):
    def test_low_iters(self):
        for i in range(0, 5):
            pi = wallis(i)
            self.assertTrue(abs(pi - math.pi) > 0.15, msg=f"Estimate with just {i} iterations is {pi} which is too accurate.\n")
            
    def test_high_iters(self):
        for i in range(500, 600):
            pi = wallis(i)
            self.assertTrue(abs(pi - math.pi) < 0.01, msg=f"Estimate with even {i} iterations is {pi} which is not accurate enough.\n")


class TestMC(unittest.TestCase):
    def test_randomness(self):
        pi0 = monte_carlo(15000)
        pi1 = monte_carlo(15000)
        
        self.assertNotEqual(pi0, pi1, "Two different estimates for PI are exactly the same. This is almost impossible.")

        self.assertFalse(abs(pi0 - pi1) > 0.05, "Two different estimates of PI are too different. This should not happen")

    def test_accuracy(self):
        for i in range(500, 600):
            pi = monte_carlo(i)
            self.assertTrue(abs(pi - math.pi) < 0.4, msg=f"Estimate with even {i} iterations is {pi} which is not accurate enough.\n")
        
    
if __name__ == "__main__":
    unittest.main()
