py_object = object
import pixie.vm.object as object
from pixie.vm.object import affirm
from pixie.vm.primitives import nil, true, false
from pixie.vm.numbers import Integer
import pixie.vm.stdlib as proto
from pixie.vm.code import extend, as_var
from rpython.rlib.rarithmetic import r_uint, intmask
import rpython.rlib.jit as jit
import pixie.vm.rt as rt


class Node(object.Object):
    _type = object.Type(u"pixie.stdlib.PersistentVectorNode")

    def __init__(self, edit, array=None):
        self._edit = edit
        self._array = [None] * 32 if array is None else array


EMPTY_NODE = Node(None)


class PersistentVector(object.Object):
    _type = object.Type(u"pixie.stdlib.PersistentVector")

    def __init__(self, meta, cnt, shift, root, tail):
        self._meta = meta
        self._cnt = cnt
        self._shift = shift
        self._root = root
        self._tail = tail

    def meta(self):
        return self._meta

    def with_meta(self, meta):
        return PersistentVector(meta, self._cnt, self._shift, self._root, self._tail)

    def tailoff(self):
        if self._cnt < 32:
            return 0
        return ((self._cnt - 1) >> 5) << 5

    def array_for(self, i):
        if 0 <= i < self._cnt:
            if i >= self.tailoff():
                return self._tail

            node = self._root
            level = self._shift
            while level > 0:
                assert isinstance(node, Node)
                node = node._array[(i >> level) & 0x01f]
                level -= 5
            assert isinstance(node, Node)
            return node._array

        affirm(False, u"Index out of Range")

    def nth(self, i, not_found=None):
        if 0 <= i < self._cnt:
            node = self.array_for(r_uint(i))
            return node[i & 0x01f]

        if not_found is None:
            affirm(False, u"Index out of Range")
        else:
            return not_found

    def conj(self, val):
        assert self._cnt < r_uint(0xFFFFFFFF)

        if self._cnt - self.tailoff() < 32:
            new_tail = self._tail[:]
            new_tail.append(val)
            return PersistentVector(self._meta, self._cnt + 1, self._shift, self._root, new_tail)

        root = self._root
        assert isinstance(root, Node)
        tail_node = Node(root._edit, self._tail)
        new_shift = self._shift

        if (self._cnt >> 5) > (r_uint(1) << self._shift):
            root = self._root
            assert isinstance(root, Node)
            new_root = Node(root._edit)
            new_root._array[0] = self._root
            root = self._root
            assert isinstance(root, Node)
            new_root._array[1] = new_path(root._edit, self._shift, tail_node)
            new_shift += 5

        else:
            new_root = self.push_tail(self._shift, self._root, tail_node)

        return PersistentVector(self._meta, self._cnt + 1, new_shift, new_root, [val])

    def push_tail(self, level, parent, tail_node):
        subidx = ((self._cnt - 1) >> level) & 0x01f
        assert isinstance(parent, Node)
        ret = Node(parent._edit, parent._array[:])

        root = self._root
        assert isinstance(root, Node)

        if (level == 5):
            node_to_insert = tail_node
        else:
            child = parent._array[subidx]
            if child is not None:
                node_to_insert = self.push_tail(level - 5, child, tail_node)
            else:
                node_to_insert = new_path(root._edit, level - 5, tail_node)

        ret._array[subidx] = node_to_insert
        return ret

    def pop(self):
        affirm(self._cnt != 0, u"Can't pop an empty vector")

        if self._cnt == 1:
            return EMPTY

        if self._cnt - self.tailoff() > 1:
            size = len(self._tail) - 1
            assert size >= 0  # for translation
            new_tail = self._tail[:size]
            return PersistentVector(self._meta, self._cnt - 1, self._shift, self._root, new_tail)

        new_tail = self.array_for(self._cnt - 2)

        new_root = self.pop_tail(self._shift, self._root)
        new_shift = self._shift
        if new_root is None:
            new_root = EMPTY_NODE

        if self._shift > 5 and new_root._array[1] is None:
            new_root = new_root._array[0]
            new_shift -= 5

        return PersistentVector(self._meta, self._cnt - 1, new_shift, new_root, new_tail)

    def pop_tail(self, level, node):
        sub_idx = ((self._cnt - 1) >> level) & 0x01f
        if level > 5:
            assert isinstance(node, Node)
            new_child = self.pop_tail(level - 5, node._array[sub_idx])
            if new_child is None or sub_idx == 0:
                return None
            else:
                root = self._root
                assert isinstance(root, Node)
                ret = Node(root._edit, node._array[:])
                ret._array[sub_idx] = new_child
                return ret

        elif sub_idx == 0:
            return None

        else:
            root = self._root
            assert isinstance(root, Node)
            assert isinstance(node, Node)
            ret = Node(root._edit, node._array[:])
            ret._array[sub_idx] = None
            return ret

    def assoc_at(self, idx, val):
        if idx >= 0 and idx < self._cnt:
            if idx >= self.tailoff():
                new_tail = self._tail[:]
                new_tail[idx & 0x01f] = val
                return PersistentVector(self._meta, self._cnt, self._shift, self._root, new_tail)
            return PersistentVector(self._meta, self._cnt, self._shift, do_assoc(self._shift, self._root, idx, val), self._tail)
        if idx == self._cnt:
            return self.conj(val)
        else:
            object.runtime_error(u"index out of range",
                                 u"pixie.stdlib/OutOfRangeException")


def do_assoc(lvl, node, idx, val):
    assert isinstance(node, Node)
    ret = Node(node._edit, node._array[:])
    if lvl == 0:
        ret._array[idx & 0x01f] = val
    else:
        subidx = (idx >> lvl) & 0x01f
        ret._array[subidx] = do_assoc(lvl - 5, node._array[subidx], idx, val)
    return ret


def new_path(edit, level, node):
    if level == 0:
        return node
    ret = Node(edit)
    ret._array[0] = new_path(edit, level - 5, node)
    return ret


edited = u"edited"


class TransientVector(object.Object):
    _type = object.Type(u"pixie.stdlib.TransientVector")

    def __init__(self, cnt, shift, root, tail):
        self._cnt = cnt
        self._shift = shift
        self._root = root
        self._tail = tail

    @staticmethod
    def editable_root(node):
        assert isinstance(node, Node)
        return Node(edited, node._array[:])

    def ensure_editable(self):
        root = self._root
        assert isinstance(root, Node)
        affirm(root._edit is not None, u"Transient used after call to persist!")

    def ensure_node_editable(self, node):
        assert isinstance(node, Node)
        root = self._root
        assert isinstance(root, Node)
        if node._edit is root._edit:
            return node

        root = self._root
        assert isinstance(root, Node)
        return Node(root._edit, node._array[:])

    def tailoff(self):
        if self._cnt < 32:
            return 0
        return ((self._cnt - 1) >> 5) << 5

    def persistent(self):
        self.ensure_editable()

        root = self._root
        assert isinstance(root, Node)

        root._edit = None
        trimmed = [None] * (self._cnt - self.tailoff())
        list_copy(self._tail, 0, trimmed, 0, len(trimmed))
        return PersistentVector(nil, self._cnt, self._shift, self._root, trimmed)

    @staticmethod
    def editable_tail(tl):
        ret = [None] * 32
        list_copy(tl, 0, ret, 0, len(tl))
        return ret

    def conj(self, val):
        self.ensure_editable()
        i = self._cnt

        if i - self.tailoff() < 32:
            self._tail[i & 0x01f] = val
            self._cnt += 1
            return self

        root = self._root
        assert isinstance(root, Node)

        tail_node = Node(root._edit, self._tail)
        self._tail = [None] * 32
        self._tail[0] = val
        new_shift = self._shift

        if (self._cnt >> 5) > (r_uint(1) << self._shift):
            new_root = Node(root._edit)
            new_root._array[0] = self._root
            new_root._array[1] = new_path(root._edit, self._shift, tail_node)
            new_shift += 5

        else:
            new_root = self.push_tail(self._shift, self._root, tail_node)

        self._root = new_root
        self._shift = new_shift
        self._cnt += 1
        return self

    def push_tail(self, level, parent, tail_node):
        parent = self.ensure_node_editable(parent)

        root = self._root
        assert isinstance(root, Node)

        sub_idx = ((self._cnt - 1) >> level) & 0x01f

        ret = parent
        if level == 5:
            node_to_insert = tail_node
        else:
            child = parent._array[sub_idx]
            if child is not None:
                node_to_insert = self.push_tail(level - 5, child, tail_node)
            else:
                node_to_insert = new_path(root._edit, level - 5, tail_node)

        ret._array[sub_idx] = node_to_insert
        return ret

    def array_for(self, i):
        if 0 <= i < self._cnt:
            if i >= self.tailoff():
                return self._tail

            node = self._root
            level = self._shift
            while level > 0:
                assert isinstance(node, Node)
                node = node._array[(i >> level) & 0x01f]
                level -= 5
            return node._array

        affirm(False, u"Index out of Range")

    def editable_array_for(self, i):
        if i >= 0 and i < self._cnt:
            if i >= self.tailoff():
                return self._tail
            node = self._root
            level = self._shift
            while level > 0:
                node = self.ensure_node_editable(node._array[(i >> level) & 0x1f])

                level -= 5
            return node._array

        affirm(False, u"Index out of bounds")

    def nth(self, i, not_found=nil):
        self.ensure_editable()
        if 0 <= i < self._cnt:
            node = self.array_for(r_uint(i))
            return node[i & 0x01f]

        return not_found

    def pop(self):
        self.ensure_editable()
        affirm(self._cnt != 0, u"Can't pop an empty vector")

        i = self._cnt - 1

        if i & 0x01f > 0:
            self._cnt -= 1
            return self

        new_tail = self.editable_array_for(self._cnt - 1)

        new_root = self.pop_tail(self._shift, self._root)
        new_shift = self._shift

        root = self._root
        assert isinstance(root, Node)

        if new_root is None:
            new_root = Node(root._edit)

        if self._shift > 5 and new_root._array[1] is None:
            new_root = self.ensure_node_editable(new_root._array[0])
            new_shift -= 5

        self._root = new_root
        self._shift = new_shift
        self._cnt -= 1
        self._tail = new_tail

        return self

    def pop_tail(self, level, node):
        node = self.ensure_node_editable(node)
        sub_idx = ((self._cnt - 2) >> level) & 0x01f

        if level > 5:
            new_child = self.pop_tail(level - 5, node._array[sub_idx])
            if new_child is None and sub_idx == 0:
                return None
            else:
                ret = node
                ret._array[sub_idx] = new_child
                return ret

        elif sub_idx == 0:
            return None
        else:
            ret = node
            ret._array[sub_idx] = None
            return ret


@jit.unroll_safe
def list_copy(from_lst, from_loc, to_list, to_loc, count):
    from_loc = r_uint(from_loc)
    to_loc = r_uint(to_loc)
    count = r_uint(count)

    i = r_uint(0)
    while i < count:
        to_list[to_loc + i] = from_lst[from_loc + i]
        i += 1
    return to_list


@extend(proto._count, PersistentVector)
def _count(self):
    assert isinstance(self, PersistentVector)
    return rt.wrap(intmask(self._cnt))


@extend(proto._nth, PersistentVector)
def _nth(self, idx):
    assert isinstance(self, PersistentVector)
    return self.nth(idx.int_val())

@extend(proto._nth_not_found, PersistentVector)
def _nth_not_found(self, idx, not_found):
    assert isinstance(self, PersistentVector)
    return self.nth(idx.int_val(), not_found)


@extend(proto._val_at, PersistentVector)
def _val_at(self, key, not_found):
    assert isinstance(self, PersistentVector)
    if isinstance(key, Integer):
        return self.nth(key.int_val(), not_found)
    else:
        return not_found


@extend(proto._eq, PersistentVector)
def _eq(self, obj):
    assert isinstance(self, PersistentVector)
    if self is obj:
        return true
    elif isinstance(obj, PersistentVector):
        if self._cnt != obj._cnt:
            return false
        for i in range(0, intmask(self._cnt)):
            if not rt.eq(self.nth(i), obj.nth(i)):
                return false
        return true
    else:
        if obj is nil or not rt._satisfies_QMARK_(proto.ISeqable, obj):
            return false
        seq = rt.seq(obj)
        for i in range(0, intmask(self._cnt)):
            if seq is nil or not rt.eq(self.nth(i), rt.first(seq)):
                return false
            seq = rt.next(seq)
        if seq is not nil:
            return false
        return true


@extend(proto._contains_key, PersistentVector)
def _contains_key(self, key):
    assert isinstance(self, PersistentVector)
    if not isinstance(key, Integer):
        return false
    else:
        return true if key.int_val() >= 0 and key.int_val() < intmask(self._cnt) else false


@extend(proto._conj, PersistentVector)
def _conj(self, v):
    assert isinstance(self, PersistentVector)
    return self.conj(v)


@extend(proto._push, PersistentVector)
def _push(self, v):
    assert isinstance(self, PersistentVector)
    return self.conj(v)


@extend(proto._pop, PersistentVector)
def _pop(self):
    assert isinstance(self, PersistentVector)
    return self.pop()


@extend(proto._assoc, PersistentVector)
def _assoc(self, idx, val):
    assert isinstance(self, PersistentVector)
    affirm(isinstance(idx, Integer), u"key must be an integer")
    return self.assoc_at(r_uint(idx.int_val()), val)


@extend(proto._meta, PersistentVector)
def _meta(self):
    assert isinstance(self, PersistentVector)
    return self.meta()


@extend(proto._with_meta, PersistentVector)
def _with_meta(self, meta):
    assert isinstance(self, PersistentVector)
    return self.with_meta(meta)


_reduce_driver = jit.JitDriver(name="pixie.stdlib.PersistentVector_reduce",
                               greens=["f"],
                               reds="auto")


@extend(proto._reduce, PersistentVector)
def _reduce(self, f, init):
    assert isinstance(self, PersistentVector)
    i = 0
    while i < self._cnt:
        array = self.array_for(i)
        for j in range(len(array)):
            _reduce_driver.jit_merge_point(f=f)

            init = f.invoke([init, array[j]])
            if rt.reduced_QMARK_(init):
                return rt.deref(init)

        step = len(array)
        i += step
    return init


@as_var("vector")
def vector__args(args):
    acc = rt._transient(EMPTY)
    for x in range(len(args)):
        acc = rt._conj_BANG_(acc, args[x])
    return rt._persistent_BANG_(acc)


@extend(proto._transient, PersistentVector)
def _transient(self):
    assert isinstance(self, PersistentVector)
    return TransientVector(self._cnt, self._shift, TransientVector.editable_root(self._root), TransientVector.editable_tail(self._tail))


@extend(proto._persistent_BANG_, TransientVector)
def _persistent(self):
    assert isinstance(self, TransientVector)
    return self.persistent()


@extend(proto._conj_BANG_, TransientVector)
def _conj(self, val):
    assert isinstance(self, TransientVector)
    return self.conj(val)


@extend(proto._pop_BANG_, TransientVector)
def _pop(self):
    assert isinstance(self, TransientVector)
    return self.pop()


@extend(proto._push_BANG_, TransientVector)
def _push(self, val):
    assert isinstance(self, TransientVector)
    return self.conj(val)


@extend(proto._count, TransientVector)
def _count(self):
    assert isinstance(self, TransientVector)
    return rt.wrap(intmask(self._cnt))

proto.IVector.add_satisfies(PersistentVector._type)

EMPTY = PersistentVector(nil, r_uint(0), r_uint(5), EMPTY_NODE, [])
