import random
from multiprocessing import Queue
from time import perf_counter_ns as timer
import sys

def puntsQuadrant(punts):
    """
    :param Queue  punts: cua de punts dins del quadrat 
             :math:`((0,0), (1,0), (1,1), (0,1))`
             excepte un punt sentinella (2.0, 2.0). Un punt és un tuple
             :math:`(x, y)` tal que :math:`0\le x \le 1.0` i  
             :math:`0\le y \le 1.0` o
             :math:`(2.0,2.0)` (punt sentinella) 

    :returns: tuple (nombre de punts dins quadrant, nombre de punts dins quadrat)
    Donada una cua de punts dins del quadrat, compta els punts que cauen dins del quadrant inscrit. Quan llegeix el sentinella, retorna (nombre de punts dins quadrant, nombre de punts llegits). El nombre de punts llegits, excepte el sentinella, estan dins del quadrat.


    """
    n_quadrant = 0
    n_quadrat = 0
    x, y = punts.get()
    while (x != 2.0) or (y != 2.0):
        n_quadrat += 1
        r = (x ** 2 + y **2) ** 0.5
        if r < 1:
            n_quadrant += 1
        x, y = punts.get()
    return (n_quadrant, n_quadrat)

def puntsQuadrat(n, punts):
    """
    :param int n: nombre de punts
    :param Queue  punts: cua de punts dins del quadrat 
             :math:`((0,0), (1,0), (1,1), (0,1))`
             Un punt és un tuple 
             :math:`(x, y)` tal que :math:`0\le x \le 1.0` i  
             :math:`0\le y \le 1.0` o
             :math:`(2.0,2.0)` (punt sentinella) 

    Calcula aleatòriament una cua de `n` punts dins del quadrat. Modifica `punts`

    """
    for i in range(n):
        punts.put((random.random(), random.random()))




    
def afegeixSentinella(punts):
    """
    :param Queue punts: cua de punts

    Afegeix el punt sentinella (2.0, 2.0)  a la cua `punts`.
    """
    punts.put((2.0, 2.0))

def puntsQuadrantQuadrat(n_quadrat):
    r"""
    :param int n: nombre de punts a calcular
    
    :returns: tuple (nombre de punts dins quadrant, nombre de punts dins quadrat)
    :rtype: tuple (int, int)

    A partir d'una generació distribuïda uniformement de `n` punts inclosos en el quadrat `q` de coordenades :math:`((0,0), (1,0), (1,1), (0,1))` (és a dir  :math:`\forall (x, y): \vert 0\le x \le 1` i :math:`0\le y \le 1`) calcula el nombre d'incidents en el quadrant inscrit en el quadrat

    """
    n_quadrant = 0
    for _ in range(n_quadrat):
        x, y = random.random(), random.random()
        r = (x ** 2 + y **2) ** 0.5
        if r < 1:
            n_quadrant += 1
    return (n_quadrant, n_quadrat)

def pi_segons(n_quadrant, n_quadrat):
    """
    :param int n_quadrant: nombre de punts dins del quadrant
    :param int n_quadrat: nombre de punts dins del quadrant
    :returns:  :math:`4 {n\_quadrant \over n\_quadrat}`
    :rtype: float

    Avalua pi segons el nombre de punts inicidents en el quadrat i el quadrant

    """
    return 4 * n_quadrant / n_quadrat

def escriuResultats(inici, fi, pi):
    """
    :param int inici: temps inici tros de programa a temporitzar en nanosegons
    :param int fi: temps d'acabem del tros de programa temporitzat en nanosegons
    :param float pi: valor estimat de pi

    Escriu pel canal estandard de sortida el temps emprat pel programa (:math:`fi - inici`) i el valor de pi calculat
    """
    print(f'temps realització: {fi - inici} ns')
    print(f'estimació de π: {pi}')
    
def main(n):
    """
    :param int n: nombre de punts per estimar el valor de pi

    Calcula el nombre pi pel mètode de montecarlo. Escriu pel canal estàndard 
    de sortida el temps emprat en el càlcul en nanosegons i el valor de pi 
    calculat
    """
    print('Càlcul seqüencial via cua de punts')
    inici = timer()
    punts = Queue()
    puntsQuadrat(n, punts)
    afegeixSentinella(punts)
    nqnt, nqt = puntsQuadrant(punts)
    pi = pi_segons(nqnt, nqt)
    fi = timer() 
    escriuResultats(inici, fi, pi)
    print('Càlcul seqüencial sense cua de punts')
    inici = timer()
    nqnt, nqt = puntsQuadrantQuadrat(n)
    pi = pi_segons(nqnt, nqt)
    fi = timer() 
    escriuResultats(inici, fi, pi)
   
if __name__=='__main__':
    main(int(sys.argv[1]))

