"""Independent finite checks of the paper's constructions and limitations."""
import unittest
from itertools import permutations
from example import (FixedFamily,Antimatroid,Digraph,Reaction,ReactionSystem,
    MarkerRealization,PairedRealization,threshold_family,four_element_certificate,
    all_antimatroids,subsets,ELEMENTS,FEASIBLE_SETS,CATALYST_ARCS)


def ordering_oracle(system,selected):
    """Food generation from all literal reaction orderings, not closure iterations."""
    for order in permutations(selected):
        available=set(system.food)
        for e in order:
            reaction=system.reactions[e]
            if not reaction.reactants<=available: break
            available.update(reaction.products)
        else: return True
    return False


class RealizationTests(unittest.TestCase):
    def test_worked_layers_and_blockers(self):
        model=MarkerRealization(Antimatroid(ELEMENTS,FEASIBLE_SETS),Digraph(ELEMENTS,CATALYST_ARCS))
        model.audit()
        self.assertEqual(model.system.fixed_family(),frozenset(map(frozenset,[(),('a',),('a','b','c')])))
        self.assertEqual(set(model.markers.blockers['b']),{frozenset({'a'}),frozenset({'a','c'})})
        self.assertEqual(len(model.markers.records),5)
        self.assertTrue(model.system.food_generated({'a','b'}));self.assertFalse(model.system.is_raf({'a','b'}))
        self.assertFalse(model.system.food_generated({'a','c'}))
        self.assertEqual(len(model.system.closure_stages(ELEMENTS))-1,3)
        for s in subsets(ELEMENTS): self.assertEqual(model.system.food_generated(s),ordering_oracle(model.system,s))

    def test_all_small_antimatroids_against_order_oracle(self):
        counts=[]
        for n in range(4):
            count=0
            for family in all_antimatroids(n):
                count+=1;graph=Digraph(family.elements,((e,e) for e in family.elements))
                for minimal in (False,True):
                    model=MarkerRealization(family,graph,minimal)
                    for s in subsets(sorted(family.elements)):
                        self.assertEqual(s in family.members,ordering_oracle(model.system,s))
                        self.assertEqual(model.system.maximal_raf(s),family.interior(s))
            counts.append(count)
        self.assertEqual(counts,[1,2,6,35])

    def test_paired_all_union_closed_families_on_three_elements(self):
        elements=('a','b','c');sets=tuple(subsets(elements));count=0
        for mask in range(1<<7):
            members=[frozenset(),*(s for i,s in enumerate(sets[1:]) if mask>>i&1)]
            if any(a|b not in members for a in members for b in members): continue
            family=FixedFamily(elements,members);count+=1
            for minimal in (False,True):
                model=PairedRealization(family,minimal);model.audit()
                self.assertEqual(model.system.fixed_family(),frozenset(model.encode(s) for s in members))
        self.assertEqual(count,61)

    def test_threshold_and_unreachable_elements(self):
        model=four_element_certificate();model.audit()
        self.assertEqual(model.system.fixed_family(),frozenset(s for s in subsets('abcd') if not s or len(s)>=3))
        paired=PairedRealization(threshold_family(5,3))
        self.assertEqual(paired.audit()['nonempty_rafs'],16)
        # Empty blocker has no producer, making an absent element unreachable.
        family=Antimatroid(('a',),((),));same=MarkerRealization(family,Digraph(('a',),(('a','a'),)))
        self.assertEqual(same.markers.blockers['a'],(frozenset(),))
        self.assertFalse(same.system.food_generated({'a'}))

    def test_catalyst_semantics_and_food_loop(self):
        model=ReactionSystem([Reaction('r',{'f'},{'x'},{'x'})])
        self.assertTrue(model.is_raf({'r'})) # structural closure permits self-catalysis
        model=ReactionSystem([Reaction('a',{'f'},{'x'},{'f'}),Reaction('b',{'x'},{'y'},{'x'})])
        self.assertEqual(model.product_graph().arcs,{('a','a'),('a','b')})
        # A mutually required product cycle cannot bootstrap from food.
        cycle=ReactionSystem([Reaction('a',{'y'},{'x'},{'x'}),Reaction('b',{'x'},{'y'},{'y'})])
        self.assertFalse(cycle.food_generated({'a','b'}));self.assertEqual(cycle.maximal_raf({'a','b'}),set())

    def test_input_validation_and_budgets(self):
        with self.assertRaises(ValueError): FixedFamily('ab',((),('a',),('b',)))
        with self.assertRaises(ValueError): Antimatroid('ab',((),('a','b')))
        with self.assertRaises(ValueError): Digraph('a',(('b','a'),))
        with self.assertRaises(ValueError): list(all_antimatroids(5))
        with self.assertRaises(ValueError): MarkerRealization(FixedFamily('a',((),)),Digraph('a',()))
        with self.assertRaises(ValueError): threshold_family(9,3)


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