"""
A vertex in a tree_.

.. _tree: https://en.wikipedia.org/wiki/Tree_(graph_theory)
"""
import os

from .dictionary import Dictionary
from .metadata import Metadata


class Vertex(Dictionary):

    def __init__(self, name, parent, read_only, **metadata):
        """A vertex in a tree_.

        Parameters
        ----------
        name : :class:`str`
            The name of this vertex.
        parent : :class:`~msl.io.group.Group`
            The parent of this vertex.
        read_only : :class:`bool`
            Whether this vertex is in read-only mode.
        **metadata
            Key-value pairs that are used to create the :class:`~msl.io.metadata.Metadata`
            for this :class:`~msl.io.vertex.Vertex`.
        """
        super(Vertex, self).__init__(read_only)

        if not name:
            raise ValueError('The vertex name must be a non-empty string')

        if parent is not None:
            if '/' in name:
                raise ValueError('The vertex name cannot contain the "/" character')

            # use a path name similar to a UNIX file system
            if parent.name.endswith('/'):
                name = parent.name + name
            else:
                name = parent.name + '/' + name

            # notify all ancestors that this vertex was created
            i = 0
            ancestor = parent
            name_split = name.split('/')
            while ancestor is not None:
                i += 1
                key = '/' + '/'.join(name_split[-i:])
                if key in ancestor._mapping:
                    raise ValueError('The name of this vertex, {!r}, is not unique'.format(key))
                ancestor._mapping[key] = self
                ancestor = ancestor._parent

        self._name = name
        self._parent = parent
        self._metadata = Metadata(read_only, name, **metadata)

    def __delitem__(self, item):
        self._raise_if_read_only()
        if item and not item[0] == '/':
            item = '/' + item

        try:
            popped = self._mapping.pop(item)
        except KeyError:
            pass  # raise a more detailed error message below
        else:

            # use recursion to delete the reference to
            # `popped` from the head of this Vertex
            head, tail = os.path.split(item)
            if head != '/':
                assert self[head].pop(tail) is popped

            def notify_ancestors(obj):
                # delete all references to `obj` from the
                # ancestors of this Vertex
                ancestor = self._parent
                while ancestor is not None:
                    for k, v in list(ancestor.items()):
                        if obj is v:
                            del ancestor._mapping[k]
                    ancestor = ancestor._parent

            notify_ancestors(popped)

            # delete all descendant of this Vertex
            # (this is necessary if the popped item is a Group)
            for name, vertex in list(self.items()):
                if vertex.name.startswith(popped.name):
                    vertex = self._mapping.pop(name)
                    notify_ancestors(vertex)

            return

        self._raise_key_error(item)

    @property
    def read_only(self):
        """:class:`bool`: Whether this :class:`~msl.io.vertex.Vertex` is in read-only mode.

        Setting this value will also update all sub-:class:`~msl.io.group.Group`\\s
        and sub-:class:`~msl.io.dataset.Dataset`\\s to be in the same mode.
        """
        return self._read_only

    @read_only.setter
    def read_only(self, value):
        val = bool(value)

        self._read_only = val
        self._metadata.read_only = val

        # update all descendants of this vertex
        for obj in self._mapping.values():
            obj.read_only = val

    @property
    def name(self):
        """:class:`str`: The name of this :class:`~msl.io.vertex.Vertex`."""
        return self._name

    @property
    def parent(self):
        """:class:`~msl.io.group.Group`: The parent of this :class:`~msl.io.vertex.Vertex`."""
        return self._parent

    @property
    def metadata(self):
        """:class:`~msl.io.metadata.Metadata`: The metadata associated with this :class:`~msl.io.vertex.Vertex`."""
        return self._metadata

    def add_metadata(self, **metadata):
        """Add key-value pairs to the :class:`~msl.io.metadata.Metadata` for this :class:`~msl.io.vertex.Vertex`."""
        self._metadata.update(**metadata)
