import gc

import pytest

import counter


def test_construct_and_bump():
    c = counter.Counter("hits")
    assert c.name == "hits"
    assert c.count == 0
    assert c.bump() == 1
    assert c.bump(10) == 11
    assert c.bump(n=4) == 15
    c.reset()
    assert c.count == 0


def test_repr():
    assert repr(counter.Counter("hits")) == "<counter.Counter 'hits' count=0>"


def test_count_is_readonly():
    c = counter.Counter()
    with pytest.raises(AttributeError):
        c.count = 5


def test_name_validated():
    c = counter.Counter()
    with pytest.raises(TypeError):
        c.name = 42
    with pytest.raises(AttributeError):
        del c.name


def test_bump_errors():
    c = counter.Counter()
    with pytest.raises(counter.Error):
        c.bump(-1)
    with pytest.raises(TypeError):
        c.bump(1, 2)
    with pytest.raises(TypeError):
        c.bump(bogus=1)
    with pytest.raises(TypeError):
        c.bump(1, n=2)


def test_equality():
    a, b = counter.Counter("a"), counter.Counter("b")
    assert a == b
    b.bump()
    assert a != b
    assert a != 0  # NotImplemented -> falls back to identity


def test_module_level_factory():
    c = counter.make("from-factory")
    assert isinstance(c, counter.Counter)
    with pytest.raises(counter.Error):
        counter.make(1)


def test_traverse_reports_exactly_the_object_fields():
    payload = [1, 2, 3]
    c = counter.Counter("n", payload)
    refs = gc.get_referents(c)  # this calls tp_traverse
    assert type(c) in refs, "a heap type must visit Py_TYPE(self)"
    assert "n" in refs
    assert payload in refs
    assert len(refs) == 3


def _live_counters():
    return sum(1 for o in gc.get_objects() if type(o) is counter.Counter)


def test_cycles_are_collected():
    gc.collect()
    before = _live_counters()
    for _ in range(200):
        c = counter.Counter("cyclic")
        c.payload = c  # self-reference: only the cycle collector can free this
        del c
    gc.collect()
    # If tp_traverse or tp_clear were wrong these would leak forever.
    assert _live_counters() == before


def test_subclassing():
    class Sub(counter.Counter):
        def __init__(self, name):
            super().__init__(name)
            self.extra = 1

    s = Sub("sub")
    assert s.bump() == 1
    assert s.extra == 1
