import numpy as np
from utils import block

class Tests(object):
    def __init__(self, x):
        """
        x is the PRN series generated by Tausworthe
        """
        self.x = x

    @block
    def test_Chi2_goodness_iid(self):
        E = np.ones(5, dtype=int)*200
        O = np.zeros(5, dtype=int)

        for i in range(5):
            lb = 0.2*i
            O[i] = ((self.x >= lb) & (self.x < lb+0.2)).sum()

        Chi = np.sum(np.square(E-O)/E)
        Chi_base = 9.49

        print(f"""Now performing the Chi-squared goodness test on the i.i.d:\n
        interval    [0.0,0.2]    (0.2,0.4]    (0.4,0.6]    (0.6,0.8]    (0.8,1.0]
        E_i          {E[0]}          {E[1]}          {E[2]}          {E[3]}          {E[4]} 
        O_i          {O[0]}          {O[1]}          {O[2]}          {O[3]}          {O[4]} 
        Chi = {Chi}
        Chi_0.05_4 = 9.49
            """)

        if Chi < Chi_base:
            print("We failed to reject the null hypothesis, the generated PRNs are approximately uniform.")
        else:
            print("We reject the null hypothesis, check the generation of the PRNs.")

    def count_associate_runs(self):
        c = True if self.x[1] >= self.x[0] else False
        res = 1
        for i in range(2, len(self.x)):
            if c and self.x[i] < self.x[i-1] or (not c and self.x[i] >= self.x[i-1]):
                c = not c
                res += 1
        return res

    def count_mean_runs(self):
        mu = np.mean(self.x)
        c = True if self.x[0] >= mu else False
        res = 1
        for i in range(1, len(self.x)):
            if c and self.x[i] < mu or (not c and self.x[i] >= mu):
                c = not c
                res += 1
        return res

    @block
    def test_up_and_down(self):
        A = self.count_associate_runs()
        n = len(self.x)
        mu = (2*n-1.)/3.
        ss = (16.*n-20.)/90.
        Z0 = (A-mu)/np.sqrt(ss)
        Z_base = 1.96
        print(f"""Now performing the Up and Down test for independence:\n
        A = {A}
        Z0 = {Z0}
        Z_0.05/2 = 1.96
            """)

        if np.abs(Z0) < Z_base:
            print("We failed to reject the null hypothesis, the generated PRNs are approximately independent.")
        else:
            print("We reject the null hypothesis, check the generation of the PRNs.")

    @block 
    def test_above_below_mean(self):
        B = self.count_mean_runs()
        n = len(self.x)
        n1 = (self.x >= 0.5).sum()
        n2 = n - n1
        nn = 2*n1*n2
        mu = nn/n + 0.5
        ss = nn*(nn-n)/n/n/(n-1)
        Z0 = (B-mu)/np.sqrt(ss)
        Z_base = 1.96
        print(f"""Now performing the Above and Below the Mean test for independence:\n
        B = {B}
        Z0 = {Z0}
        Z_0.05/2 = 1.96
            """)

        if np.abs(Z0) < Z_base:
            print("We failed to reject the null hypothesis, the generated PRNs are approximately independent.")
        else:
            print("We reject the null hypothesis, check the generation of the PRNs.")


    @block
    def test_correlation(self):
        corr = np.corrcoef(self.x[:-1], self.x[1:])[0][1]
        n = len(self.x)
        mu = 0.0
        ss = (13.*n-19.)/np.square(n-1.)

        Z0 = (corr-mu)/np.sqrt(ss)
        Z_base = 1.96
        print(f"""Now performing Correlation test for independence:\n
        lag-1 correlation = {corr}
        Z0 = {Z0}
        Z_0.05/2 = 1.96
            """)

        if np.abs(Z0) < Z_base:
            print("We failed to reject the null hypothesis, the generated PRNs are approximately independent.")
        else:
            print("We reject the null hypothesis, check the generation of the PRNs.")
