# ----------------------------------------------------------------------------
# Copyright (c) FIRST 2017. All Rights Reserved.
# Open Source Software - may be modified and shared by FRC teams. The code
# must be accompanied by the FIRST BSD license file in the root directory of
# the project.
# ----------------------------------------------------------------------------

#
# These tests are adapted from ntcore's test suite
#

import pytest

from _pynetworktables._impl.constants import (
    NT_NOTIFY_IMMEDIATE,
    NT_NOTIFY_LOCAL,
    NT_NOTIFY_NEW,
    NT_NOTIFY_DELETE,
    NT_NOTIFY_UPDATE,
    NT_NOTIFY_FLAGS,
)

from _pynetworktables._impl.entry_notifier import EntryNotifier
from _pynetworktables._impl.value import Value


@pytest.fixture
def notifier():
    n = EntryNotifier(verbose=True)
    n.start()
    yield n
    n.stop()


def generateNotifications(notifier):
    # All flags combos that can be generated by Storage
    flags = [
        # "normal" notifications
        NT_NOTIFY_NEW,
        NT_NOTIFY_DELETE,
        NT_NOTIFY_UPDATE,
        NT_NOTIFY_FLAGS,
        NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS,
        # immediate notifications are always "new"
        NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW,
        # local notifications can be of any flag combo
        NT_NOTIFY_LOCAL | NT_NOTIFY_NEW,
        NT_NOTIFY_LOCAL | NT_NOTIFY_DELETE,
        NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE,
        NT_NOTIFY_LOCAL | NT_NOTIFY_FLAGS,
        NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS,
    ]

    # Generate across keys
    keys = ["/foo/bar", "/baz", "/boo"]

    val = Value.makeDouble(1)

    # Provide unique key indexes for each key
    keyindex = 5
    for key in keys:
        for flag in flags:
            notifier.notifyEntry(keyindex, key, val, flag)

        keyindex += 1


def test_PollEntryMultiple(notifier):
    poller1 = notifier.createPoller()
    poller2 = notifier.createPoller()
    poller3 = notifier.createPoller()
    h1 = notifier.addPolledById(poller1, 6, NT_NOTIFY_NEW)
    h2 = notifier.addPolledById(poller2, 6, NT_NOTIFY_NEW)
    h3 = notifier.addPolledById(poller3, 6, NT_NOTIFY_UPDATE)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results1, timed_out = notifier.poll(poller1, 0)
    assert not timed_out
    results2, timed_out = notifier.poll(poller2, 0)
    assert not timed_out
    results3, timed_out = notifier.poll(poller3, 0)
    assert not timed_out

    assert len(results1) == 2
    for h, result in results1:
        print(result)
        assert h == h1

    assert len(results2) == 2
    for h, result in results2:
        print(result)
        assert h == h2

    assert len(results3) == 2
    for h, result in results3:
        print(result)
        assert h == h3


def test_PollEntryBasic(notifier):
    poller = notifier.createPoller()
    g1 = notifier.addPolledById(poller, 6, NT_NOTIFY_NEW)
    g2 = notifier.addPolledById(poller, 6, NT_NOTIFY_DELETE)
    g3 = notifier.addPolledById(poller, 6, NT_NOTIFY_UPDATE)
    g4 = notifier.addPolledById(poller, 6, NT_NOTIFY_FLAGS)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    timed_out = False
    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out

    g1count = 0
    g2count = 0
    g3count = 0
    g4count = 0
    for h, result in results:
        print(h, result)
        assert result.name == "/baz"
        assert result.value == Value.makeDouble(1)
        assert result.local_id == 6

        if h == g1:
            g1count += 1
            assert (result.flags & NT_NOTIFY_NEW) != 0
        elif h == g2:
            g2count += 1
            assert (result.flags & NT_NOTIFY_DELETE) != 0
        elif h == g3:
            g3count += 1
            assert (result.flags & NT_NOTIFY_UPDATE) != 0
        elif h == g4:
            g4count += 1
            assert (result.flags & NT_NOTIFY_FLAGS) != 0
        else:
            assert False, "unknown listener index"

    assert g1count == 2
    assert g2count == 1  # NT_NOTIFY_DELETE
    assert g3count == 2
    assert g4count == 2


def test_PollEntryImmediate(notifier):
    poller = notifier.createPoller()
    notifier.addPolledById(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE)
    notifier.addPolledById(poller, 6, NT_NOTIFY_NEW)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out
    print(results)
    assert len(results) == 4


def test_PollEntryLocal(notifier):
    poller = notifier.createPoller()
    notifier.addPolledById(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL)
    notifier.addPolledById(poller, 6, NT_NOTIFY_NEW)

    assert notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out
    print(results)
    assert len(results) == 6


def test_PollPrefixMultiple(notifier):
    poller1 = notifier.createPoller()
    poller2 = notifier.createPoller()
    poller3 = notifier.createPoller()
    h1 = notifier.addPolled(poller1, "/foo", NT_NOTIFY_NEW)
    h2 = notifier.addPolled(poller2, "/foo", NT_NOTIFY_NEW)
    h3 = notifier.addPolled(poller3, "/foo", NT_NOTIFY_UPDATE)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results1, timed_out = notifier.poll(poller1, 0)
    assert not timed_out
    results2, timed_out = notifier.poll(poller2, 0)
    assert not timed_out
    results3, timed_out = notifier.poll(poller3, 0)
    assert not timed_out

    assert len(results1) == 2
    for h, result in results1:
        print(result)
        assert h == h1

    assert len(results2) == 2
    for h, result in results2:
        print(result)
        assert h == h2

    assert len(results3) == 2
    for h, result in results3:
        print(result)
        assert h == h3


def test_PollPrefixBasic(notifier):
    poller = notifier.createPoller()
    g1 = notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW)
    g2 = notifier.addPolled(poller, "/foo", NT_NOTIFY_DELETE)
    g3 = notifier.addPolled(poller, "/foo", NT_NOTIFY_UPDATE)
    g4 = notifier.addPolled(poller, "/foo", NT_NOTIFY_FLAGS)
    notifier.addPolled(poller, "/bar", NT_NOTIFY_NEW)
    notifier.addPolled(poller, "/bar", NT_NOTIFY_DELETE)
    notifier.addPolled(poller, "/bar", NT_NOTIFY_UPDATE)
    notifier.addPolled(poller, "/bar", NT_NOTIFY_FLAGS)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out

    g1count = 0
    g2count = 0
    g3count = 0
    g4count = 0
    for h, result in results:
        print(result)
        assert result.name.startswith("/foo")
        assert result.value == Value.makeDouble(1)

        if h == g1:
            g1count += 1
            assert (result.flags & NT_NOTIFY_NEW) != 0
        elif h == g2:
            g2count += 1
            assert (result.flags & NT_NOTIFY_DELETE) != 0
        elif h == g3:
            g3count += 1
            assert (result.flags & NT_NOTIFY_UPDATE) != 0
        elif h == g4:
            g4count += 1
            assert (result.flags & NT_NOTIFY_FLAGS) != 0
        else:
            assert False, "unknown listener index"

    assert g1count == 2
    assert g2count == 1  # NT_NOTIFY_DELETE
    assert g3count == 2
    assert g4count == 2


def test_PollPrefixImmediate(notifier):
    poller = notifier.createPoller()
    notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE)
    notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW)

    assert not notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out
    print(results)
    assert len(results) == 4


def test_PollPrefixLocal(notifier):
    poller = notifier.createPoller()
    notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_LOCAL)
    notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW)

    assert notifier.m_local_notifiers

    generateNotifications(notifier)

    assert notifier.waitForQueue(1.0)

    results, timed_out = notifier.poll(poller, 0)
    assert not timed_out
    print(results)
    assert len(results) == 6
