import unittest
from fractions import Fraction as F
from material import *
from certificate import *


def cert(J=F(2),e=F(1,20),H=F(1,5)):
    return MaterialCertificate(F(10),e,F(9,10),H,DirectReserve(J))


class ScientificTests(unittest.TestCase):
    def test_window_order_and_material(self):
        w=Window(F(8),F(2),uptake=F(2),collection=F(6));rows=w.replay()
        self.assertEqual((w.final_mobile,w.final_reserve),(0,4))
        self.assertEqual(rows[-1]['mobile']+rows[-1]['reserve']+rows[-1]['collected']+rows[-1]['lost'],10)
        with self.assertRaises(ValueError):Window(F(3),F(0),release=F(1),uptake=F(1))
        with self.assertRaises(ValueError):Recovery(F(1),F(1,2))
        # C,H,O balance of the paper's accounting witness, not a kinetic claim.
        left=[4*3+2*2,4*6+2*4,4*3+2*2]
        right=[3*4+4,3*8+2*2+2*2,3*2+4*2+2]
        self.assertEqual(left,right)

    def test_sharp_attainment_degenerate_endpoints(self):
        for B in map(F,[0,2,10]):
            for J in map(F,[0,1,10]):
                for q1,q2 in [(F(0),F(0)),(F(1),F(5)),(F(12),F(0)),(F(12),F(15))]:
                    for e,s in [(F(0),F(0)),(F(0),F(1)),(F(1),F(1))]:
                        c=MaterialCertificate(B,e,s,F(1,5),DirectReserve(J))
                        for split in [F(0),B/2,B]:
                            h=c.witness(q1,q2,split);h.ledger()
                            self.assertEqual(h.fresh,c.evaluate(q1,q2)['lower']);self.assertTrue(c.valid_for(h))

    def test_direct_vs_initial_reserve(self):
        h=History(Window(F(8),F(2),uptake=F(2),collection=F(6)),Window(F(0),F(18,5),release=F(18,5),collection=F(18,5)),Recovery())
        self.assertEqual(h.fresh,0);self.assertFalse(cert(H=F(0)).valid_for(h))
        self.assertEqual(cert(H=F(0)).evaluate(6,F(18,5))['lower'],F(17,10))
        self.assertEqual(cert(F(4),H=F(0)).evaluate(6,F(18,5))['lower'],0)
        c=MaterialCertificate(F(10),F(1,20),F(9,10),F(0),UptakeBound(F(2),F(2)))
        self.assertTrue(c.valid_for(h));self.assertEqual(c.evaluate(6,F(18,5))['lower'],0)
        # Uptake premise does not assert r <= R+T when fresh material is stored.
        h=History(Window(F(0),F(0),fresh=F(3)),Window(F(0),F(27,10)),Recovery())
        c=MaterialCertificate(F(0),F(1,20),F(9,10),F(0),UptakeBound(F(0),F(0)))
        self.assertTrue(c.valid_for(h));self.assertGreater(h.first.final_reserve,0)

    def test_observation_decisions_and_upper(self):
        o=Observation(F(6),F(4),F(1,5),F(1,5));c=cert()
        self.assertEqual(report(o,[c],F(8,5))['lower'],F(169,100))
        self.assertEqual(report(o,[c],F(169,100))['verdict'],'at or above')
        self.assertEqual(report(o,[c],F(8,5),F(1))['verdict'],'incompatible')
        self.assertEqual(report(Observation(F(0),F(0),F(0),F(0)),[c],F(8,5),F(1))['verdict'],'below')
        self.assertEqual(report(Observation(F(-1,10),F(0),F(1,5),F(0)),[c],F(8,5))['verdict'],'unresolved')
        self.assertEqual(report(Observation(F(-1),F(0),F(1,5),F(0)),[c],F(8,5))['verdict'],'incompatible')
        h=c.witness(F(29,5),F(19,5));more=h.extra_unobserved_formation(100)
        self.assertEqual(more.second.collection,h.second.collection);self.assertEqual(more.fresh-h.fresh,100)
        self.assertTrue(c.valid_for(more))

    def test_uptake_attainment_and_sensitivity(self):
        for rb,tb in [(F(0),F(0)),(F(2),F(9,85)),(F(20),F(0)),(F(1),F(10))]:
            c=MaterialCertificate(F(10),F(1,20),F(9,10),F(1,5),UptakeBound(rb,tb));h=c.witness(F(29,5),F(19,5))
            self.assertTrue(c.valid_for(h));self.assertEqual(c.evaluate(F(29,5),F(19,5))['lower'],h.fresh)
        a=cert(F(5)).evaluate(F(29,5),F(19,5));b=cert(F(6)).evaluate(F(29,5),F(19,5))
        self.assertEqual(a['lower'],b['lower'])
        self.assertEqual(cert(F(2)).evaluate(F(29,5),F(19,5))['lower']-cert(F(21,10)).evaluate(F(29,5),F(19,5))['lower'],F(17,200))

    def test_matched_wash_and_joint_budget(self):
        for e in [F(0),F(1,20),F(1,2),F(9,10)]:
            Q2=2*e+F(18,10)+F(21,10)
            self.assertEqual(cert(e=e).evaluate(6,Q2)['lower'],F(19,10))
        for delta in [F(0),F(1,10),F(1,5)]:
            boundary=F(193,210)+F(17,42)*delta
            self.assertEqual(cert(2+delta).evaluate(F(29,5),F(17,10)+F(21,10)*boundary)['lower'],F(38,25))
        self.assertEqual(cert().evaluate(F(29,5),F(17,10)+F(21,10)*F(9,10))['lower'],F(37,25))
        self.assertEqual(cert(F(179,85)).evaluate(F(29,5),F(19,5))['lower'],F(8,5))

    def test_amount_and_collection_accounting(self):
        error=amount_error(2,3,F(1,20),F(1,10));self.assertEqual(error,F(71,200))
        for dv in [F(-1,20),F(1,20)]:
            for dc in [F(-1,10),F(1,10)]:self.assertLessEqual(abs((2+dv)*(3+dc)-6),error)
        V,v,c=F(10),F(3),F(2)
        self.assertEqual(V*c,(V-v)*c+v*c)


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