# -*- coding: utf-8 -*-

#
# omdict - Ordered Multivalue Dictionary.
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: Build Amazing Things (Unlicense)

from __future__ import absolute_import

from six.moves import zip_longest

_absent = object()  # Marker that means no parameter was provided.


class itemnode(object):

    """
    Dictionary key:value items wrapped in a node to be members of itemlist, the
    doubly linked list defined below.
    """

    def __init__(self, prev=None, next=None, key=_absent, value=_absent):
        self.prev = prev
        self.next = next
        self.key = key
        self.value = value


class itemlist(object):

    """
    Doubly linked list of itemnodes.

    This class is used as the key:value item storage of orderedmultidict.
    Methods below were only added as needed for use with orderedmultidict, so
    some otherwise common list methods may be missing.
    """

    def __init__(self, items=[]):
        self.root = itemnode()
        self.root.next = self.root.prev = self.root
        self.size = 0

        for key, value in items:
            self.append(key, value)

    def append(self, key, value):
        tail = self.root.prev if self.root.prev is not self.root else self.root
        node = itemnode(tail, self.root, key=key, value=value)
        tail.next = node
        self.root.prev = node
        self.size += 1
        return node

    def removenode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
        self.size -= 1
        return self

    def clear(self):
        for node, key, value in self:
            self.removenode(node)
        return self

    def items(self):
        return list(self.iteritems())

    def keys(self):
        return list(self.iterkeys())

    def values(self):
        return list(self.itervalues())

    def iteritems(self):
        for node, key, value in self:
            yield key, value

    def iterkeys(self):
        for node, key, value in self:
            yield key

    def itervalues(self):
        for node, key, value in self:
            yield value

    def reverse(self):
        for node, key, value in self:
            node.prev, node.next = node.next, node.prev
        self.root.prev, self.root.next = self.root.next, self.root.prev
        return self

    def __len__(self):
        return self.size

    def __iter__(self):
        current = self.root.next
        while current and current is not self.root:
            # Record current.next here in case current.next changes after the
            # yield and before we return for the next iteration. For example,
            # methods like reverse() will change current.next() before yield
            # gets executed again.
            nextnode = current.next
            yield current, current.key, current.value
            current = nextnode

    def __contains__(self, item):
        """
        Params:
          item: Can either be a (key,value) tuple or an itemnode reference.
        """
        node = key = value = _absent
        if hasattr(item, '__len__') and callable(item.__len__):
            if len(item) == 2:
                key, value = item
            elif len(item) == 3:
                node, key, value = item
        else:
            node = item

        if node is not _absent or _absent not in [key, value]:
            for selfnode, selfkey, selfvalue in self:
                if ((node is _absent and key == selfkey and value == selfvalue)
                   or (node is not _absent and node == selfnode)):
                    return True
        return False

    def __getitem__(self, index):
        # Only support direct access to the first or last element, as this is
        # all orderedmultidict needs for now.
        if index == 0 and self.root.next is not self.root:
            return self.root.next
        elif index == -1 and self.root.prev is not self.root:
            return self.root.prev
        raise IndexError(index)

    def __delitem__(self, index):
        self.removenode(self[index])

    def __eq__(self, other):
        for (n1, key1, value1), (n2, key2, value2) in zip_longest(self, other):
            if key1 != key2 or value1 != value2:
                return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

    def __nonzero__(self):
        return self.size > 0

    def __str__(self):
        return '[%s]' % self.items()
