import unittest
import math as m
import sys
import os

import numpy as np
from scipy import integrate
from scipy.special import beta

#only necessary while the module is not installed in pckgs
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from applied_stats import continuous_distributions as stats
from applied_stats import mle


# defining a random array and sample size for reproducible testing
rng = np.random.default_rng(1905)
X_continuous = rng.random(100) * 100
X_discrete = np.round(X_continuous, 0)
n = len(X_continuous)

# testing distribution calculations / attributes / methods
class Test_Distributions(unittest.TestCase):

    def test_norm(self):

        #test an instance
        a = stats.Norm_rv(0,1)
        self.assertIsInstance(a, stats.Norm_rv)

        #test the probability calculation
        a.probability_calc()
        self.assertAlmostEqual(a.left_probability, 0.5)

        #test that it is a pdf by integrating, it must = 1
        f = lambda x: ((1/(a.sigma*m.sqrt(2*m.pi)))*
                       m.e**((-1/2)*((x-a.mean)/a.sigma)**2))
        a.probability, a.error_est = integrate.quad(f,-np.inf, np.inf)
        self.assertAlmostEqual(a.probability, 1)

        #testing attributes
        self.assertEqual(a.mean,0)
        self.assertEqual(a.variance,1)
        self.assertTrue(a.variance < np.infty)
        
        #two tailed test 
        a_two_tail = stats.Norm_rv(10,4,left_cv=7, right_cv=13)
        a_two_tail.probability_calc(two_tail=True, cv_probability=True)
        self.assertAlmostEqual(round(a_two_tail.total_probability,5), 0.13361)
        
    def test_chisq(self):

        #test an instance
        b = stats.ChiSq_rv(4)
        self.assertIsInstance(b, stats.ChiSq_rv)

        #test the probability calculation
        #will need to adjust for two tail
        b.probability_calc()
        self.assertAlmostEqual(round(b.right_probability,5), .40601)

        #test that it is a pdf by integrating, it must = 1
        #TODO: rewrite to use function from .py file
        f = lambda x: ((1/(m.gamma(b.df/2)*2**(b.df/2)))
                       *x**((b.df/2)-1)*m.e**(-x/2))
        b.probability, b.error_est = integrate.quad(f,0,np.inf)
        self.assertAlmostEqual(b.probability, 1)

        #test some attributes
        self.assertEqual(b.df, 4)
        self.assertEqual(b.mean, 4)
        self.assertEqual(b.variance, 8)

        #TODO: add a two tailed test case

    def test_t(self):

        #test an instance
        c = stats.t_rv(5,crit_value=1)
        self.assertIsInstance(c, stats.t_rv)

        #test the probability calculation
        c.probability_calc()
        self.assertAlmostEqual(round(c.probability,5), 0.18161)

        #test that it is a pdf by integrating, it must = 1
        f = lambda x: (m.gamma((c.df+1)/2) / (m.sqrt(m.pi * c.df) *
                       m.gamma(c.df / 2) * (1 + ((x**2)/c.df))
                       **((c.df + 1) / 2)))
        c.probability, c.error_est = integrate.quad(f,-np.inf,np.inf)
        self.assertAlmostEqual(c.probability, 1)

        #test some attributes
        self.assertEqual(c.df, 5)
        self.assertEqual(c.mean, 0)
        self.assertEqual(c.variance, 5/3)

    def test_F(self):

        #test an instance
        d = stats.F_rv(5, 5)
        self.assertIsInstance(d, stats.F_rv)


        #test the probability calculation
        d.probability_calc()
        self.assertAlmostEqual(round(d.right_probability,2), 0.5)

        #test that it is a pdf by integrating, it must = 1
        f =  lambda x: ((d.v_2**(d.v_2/2) * d.v_1**(d.v_1/2) *
                         x**(d.v_1/2 -1))/
                        ((d.v_2 +d.v_1*x)**((d.v_1 + d.v_2)/2) *
                        beta(d.v_1/2, d.v_2/2)))
        d.probability, d.error_est = integrate.quad(f,0,np.inf)
        self.assertAlmostEqual(d.probability, 1)

        #test some attributes
        self.assertEqual(d.v_1, 5)
        self.assertEqual(d.v_2, 5)
        self.assertEqual(round(d.mean,3), 1.667)
        self.assertEqual(round(d.variance,3), 30.769)

# testing the MLE module to ensure accurate calculations
class Test_MLE(unittest.TestCase):

 #continuous distributions

    def test_uniform(self):

        a, e = mle.uniform(X_continuous)
        self.assertEqual(round(a,4), 0.0735) #alpha
        self.assertEqual(round(e,4), 99.0877) #beta

    def test_exponential(self):

        b = round(mle.exponential(X_continuous),4)
        self.assertEqual(b, 52.1989) #theta

    def test_normal(self):

        c, d = mle.normal(X_continuous)
        self.assertEqual(round(c,4), 52.1989) #mu
        self.assertEqual(round(d,4), 747.7962) #sigma^2

# discrete distributions

    def test_binomial(self):
        b = mle.binomial(100, X_discrete)
        self.assertEqual(round(b,4), 0.5222) #p-hat

    def test_geometric(self):
        c = round(mle.geometric(X_discrete),5)
        self.assertEqual(c, 0.01915)#p-hat

    def test_poisson(self):
        d = mle.poisson(X_discrete)
        self.assertEqual(d, 52.22)#lambda-hat

class Test_Hypotheses(unittest.TestCase):
    
    def test_gen_test(self):
        pass
    
class Test_conf_intervals(unittest.TestCase):
    
    def test_norm_ci(self):
        pass

if __name__ == '__main__':
    unittest.main()
