#  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
#  ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
#  ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
#  ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
#  ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
#  ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
#  ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
#  ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
#  ┃ This file is part of the Perspective library, distributed under the terms ┃
#  ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
#  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import random
import perspective as psp

client = psp.Server().new_local_client()
Table = client.table

data = {"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"], "c": [True, False, True, False]}


class TestPorts(object):
    def test_make_port_sequential(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

    def test_make_port_sequential_and_update(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

        for i in range(1, 11):
            table.update({"a": [i], "b": ["a"], "c": [True]}, port_id=i)

        view = table.view()
        result = view.to_columns()

        assert result == {
            "a": [1, 2, 3, 4] + [i for i in range(1, 11)],
            "b": ["a", "b", "c", "d"] + ["a" for i in range(10)],
            "c": [True, False, True, False] + [True for i in range(10)],
        }

    def test_arbitary_port_updates(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

        port = random.randint(0, 10)

        table.update(data, port_id=port)

        assert table.size() == 8

        assert table.view().to_columns() == {
            "a": [1, 2, 3, 4] * 2,
            "b": ["a", "b", "c", "d"] * 2,
            "c": [True, False, True, False] * 2,
        }

    def test_ports_should_only_notify_if_they_have_a_queued_update(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

        view = table.view()
        ports_to_update = [random.randint(0, 10) for i in range(5)]

        def callback(port_id):
            assert port_id in ports_to_update

        view.on_update(callback)

        for port in ports_to_update:
            table.update(data, port_id=port)

    def test_ports_should_have_unique_deltas(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

        view = table.view()
        ports_to_update = [random.randint(0, 10) for i in range(5)]
        unique_data = {
            port: [{"a": port, "b": str(port), "c": True}] for port in ports_to_update
        }

        def callback(port_id, delta):
            assert port_id in ports_to_update
            _t = Table(delta)
            _v = _t.view()
            assert _v.to_records() == unique_data[port]
            _v.delete()
            _t.delete()

        view.on_update(callback, mode="row")

        for port in ports_to_update:
            table.update(unique_data[port], port_id=port)

    def test_ports_should_queue_updates_properly(self):
        table = Table(data)
        port_ids = []

        for i in range(10):
            port_ids.append(table.make_port())

        assert port_ids == list(range(1, 11))

        view = table.view()
        ports_to_update = [random.randint(0, 10) for i in range(5)]

        def callback(port_id):
            assert port_id in ports_to_update

        view.on_update(callback)

        for port in ports_to_update:
            table.update(data, port_id=port)

    def test_ports_multiple_tables_with_different_ports(self):
        server = Table(data)
        client = Table(data)

        for i in range(random.randint(5, 15)):
            # reserve an arbitary number of ports
            server.make_port()

        # port for client is now far above the ports "ON" the client, as the
        # client ports will begin creation at 1.
        server_port_for_client = server.make_port()
        client_port = client.make_port()

        server_view = server.view()
        client_view = client.view()

        # when the client updates, check whether the port id matches that
        # of the server, and complete the test.
        def client_callback(port_id, delta):
            if port_id == client_port:
                print("UPDATING SERVER")
                server.update(delta, port_id=server_port_for_client)

        # when the server updates, pass the update back to the client
        def server_callback(port_id, delta):
            print("UPDATING CLIENT")
            assert port_id == server_port_for_client
            assert server.size() == 8
            server_view.delete()
            server.delete()
            client_view.delete()
            client.delete()

        client_view.on_update(client_callback, mode="row")
        server_view.on_update(server_callback, mode="row")

        client.update(data, port_id=client_port)
