#  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
#  ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
#  ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
#  ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
#  ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
#  ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
#  ┃ 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 sys
from random import randint, choice
import perspective as psp

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


class CustomObjectStore(object):
    def __init__(self, value):
        self._value = value

    def _psp_dtype_(self):
        return "object"

    def __int__(self):
        return int(self._value)

    def __repr__(self):
        return "test" if self._value == 1 else "test{}".format(self._value)


def run():
    t = CustomObjectStore(1)
    t2 = CustomObjectStore(2)

    assert sys.getrefcount(t) == 2
    assert sys.getrefcount(t2) == 2

    data = {"a": [0], "b": [t]}
    assert sys.getrefcount(t) == 3

    tbl = Table(data, index="a")
    assert sys.getrefcount(t) == 4

    assert tbl.schema() == {"a": int, "b": object}
    assert tbl.size() == 1
    assert tbl.view().to_columns() == {"a": [0], "b": [t]}

    # Count references
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    print("t:", id(t))
    assert sys.getrefcount(t) == 4

    print(sys.getrefcount(t2), "should be", 2)
    print("t2:", id(t2))
    assert sys.getrefcount(t2) == 2

    tbl.update([{"a": i, "b": None} for i in range(1, 6)])

    assert tbl.size() == 6
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    assert sys.getrefcount(t) == 4

    print()
    tbl.update([data])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    assert sys.getrefcount(t) == 4

    print()
    tbl.update([data])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    assert sys.getrefcount(t) == 4

    print()
    tbl.update([data])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    assert sys.getrefcount(t) == 4

    print()
    tbl.update([{"a": 1, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
    print(sys.getrefcount(t), "should be", 5)
    assert sys.getrefcount(t) == 5

    print()
    tbl.update([{"a": 1, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
    print(sys.getrefcount(t), "should be", 5)
    assert sys.getrefcount(t) == 5

    print()
    tbl.update([{"a": 3, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    assert sys.getrefcount(t) == 6

    print()
    tbl.update([{"a": 3, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    assert sys.getrefcount(t) == 6

    print()
    tbl.update([{"a": 5, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 4for the table
    print(sys.getrefcount(t), "should be", 7)
    # assert sys.getrefcount(t) == 7
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    # clear some, overwrite some with same
    tbl.update([{"a": 0, "b": t}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 4 for the table
    print(sys.getrefcount(t), "should be", 7)
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 1, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 1, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 1, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 1, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 2, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 4)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        True,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 2, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 2, "b": t2}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 4)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        True,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 2, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 3 for the table
    print(sys.getrefcount(t), "should be", 6)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        True,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 3, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
    print(sys.getrefcount(t), "should be", 5)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        False,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 3, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 2 for the table
    print(sys.getrefcount(t), "should be", 5)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        False,
        False,
        True,
    ]

    print()
    tbl.update([{"a": 5, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        False,
        False,
        False,
    ]

    print()
    tbl.update([{"a": 5, "b": None}])
    # 1 for `t`, 1 for `data`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t), "should be", 4)
    # 1 for `t2`, 1 for argument to sys.getrefcount, and 1 for the table
    print(sys.getrefcount(t2), "should be", 3)
    print(tbl.view().to_columns()["b"])
    assert list(_ is not None for _ in tbl.view().to_columns()["b"]) == [
        True,
        True,
        False,
        False,
        False,
        False,
    ]

    print()
    tbl.clear()
    assert tbl.size() == 0
    assert tbl.view().to_columns() == {}
    # 1 for `t`, one for `data`, one for argument to sys.getrefcount
    print(sys.getrefcount(t), "should be", 3)
    assert sys.getrefcount(t) == 3


def run2():
    t = CustomObjectStore(1)
    t_ref_count = 2
    assert sys.getrefcount(t) == t_ref_count

    indexes = set([0])

    tbl = Table({"a": [0], "b": [t]}, index="a")
    assert sys.getrefcount(t) == 3
    t_ref_count += 1

    assert tbl.schema() == {"a": int, "b": object}
    assert tbl.size() == 1
    assert tbl.view().to_columns() == {"a": [0], "b": [t]}

    # seed a few to check
    tbl.remove([1])
    tbl.remove([1])
    tbl.remove([1])

    for _ in range(10):
        pick = randint(1, 2) if indexes else 1
        if pick == 1:
            ind = randint(1, 10)
            while ind in indexes:
                ind = randint(1, 100)

            print(
                "adding", ind, "refcount", t_ref_count, "should be", sys.getrefcount(t)
            )
            tbl.update({"a": [ind], "b": [t]})
            t_ref_count += 1
            indexes.add(ind)
            assert sys.getrefcount(t) == t_ref_count

        else:
            ind = choice(list(indexes))
            indexes.remove(ind)
            tbl.remove([ind])
            t_ref_count -= 1
            print(
                "removing",
                ind,
                "refcount",
                t_ref_count,
                "should be",
                sys.getrefcount(t),
            )
            assert sys.getrefcount(t) == t_ref_count

        print(t_ref_count)
        print(tbl.view().to_columns())

    assert sys.getrefcount(t) == t_ref_count

    print()
    tbl.clear()
    assert tbl.size() == 0
    assert tbl.view().to_columns() == {}
    # 1 for `t`, one for `data`, one for argument to sys.getrefcount
    print(sys.getrefcount(t), "should be", 2)
    assert sys.getrefcount(t) == 2
